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.
Files changed (3) hide show
  1. package/README.md +3 -3
  2. package/dist/cli.js +53 -51
  3. 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
- # Pipe from stdin
10
- cat report.html | npx htmlship publish -
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`, `fetch_html`, `update_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.2";
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/pastes", { body });
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/pastes/${encodeURIComponent(slug)}`);
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/pastes/${encodeURIComponent(slug)}`,
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/pastes/${encodeURIComponent(slug)}`,
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/pastes/${encodeURIComponent(parentSlug)}/version`,
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 text;
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 paste later \u2014 save it.",
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 paste = await c.publish(html, {
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: paste.url,
226
- slug: paste.slug,
227
- owner_key: paste.owner_key,
228
- expires_at: paste.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 paste's metadata. To view the rendered HTML, open the `url` in a browser \u2014 the content is served from a sandboxed subdomain.",
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 paste's short identifier.")
248
+ slug: z.string().describe("The page's short identifier.")
247
249
  }
248
250
  },
249
251
  async ({ slug }) => {
250
252
  try {
251
- const paste = await c.get(slug);
253
+ const page = await c.get(slug);
252
254
  return {
253
- content: [{ type: "text", text: JSON.stringify(paste, null, 2) }]
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(`paste '${slug}' not found`));
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 paste. Requires the original owner_key.",
268
+ description: "Replace the HTML for an existing page. Requires the original owner_key.",
267
269
  inputSchema: {
268
- slug: z.string().describe("The paste's short identifier."),
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 paste = await c.update(slug, html, owner_key, { title: title ?? null });
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: paste.url, slug: paste.slug, updated_at: paste.updated_at },
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 paste.").argument("<slug>", "Paste slug").option("--owner-key <key>", "Owner key (defaults to local store)").option("-y, --yes", "Skip confirmation").action(async function(slug, opts) {
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 paste '${slug}'? [y/N] `);
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 paste.").argument("<slug>", "Paste slug").action(async function(slug) {
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 paste = await client.get(slug);
424
- process.stdout.write(`${JSON.stringify(paste, null, 2)}
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(`paste '${slug}' not found`);
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 pastes whose owner keys are saved locally.").option("--limit <n>", "max entries to show", "20").action(async function(opts) {
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 pastes in ${keys.file}.
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 paste = await client.get(slug);
457
+ const page = await client.get(slug);
456
458
  return {
457
459
  slug,
458
460
  url: info.url,
459
- title: paste.title ?? info.title ?? "",
460
- size: `${paste.size_bytes}B`,
461
- views: String(paste.view_count),
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 paste").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) {
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 paste;
539
+ let page;
538
540
  try {
539
- paste = await client.publish(html, {
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(paste.slug, {
549
- owner_key: paste.owner_key,
550
- url: paste.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(`${paste.url}
556
+ process.stdout.write(`${page.url}
555
557
  `);
556
558
  return;
557
559
  }
558
- process.stdout.write(`${paste.url}
560
+ process.stdout.write(`${page.url}
559
561
  `);
560
- process.stderr.write(`slug: ${paste.slug}
562
+ process.stderr.write(`slug: ${page.slug}
561
563
  `);
562
- process.stderr.write(`owner_key: ${paste.owner_key} (saved to ${keys.file})
564
+ process.stderr.write(`owner_key: ${page.owner_key} (saved to ${keys.file})
563
565
  `);
564
- if (paste.expires_at) {
565
- process.stderr.write(`expires: ${paste.expires_at}
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(paste.url);
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 paste.").argument("<slug>", "Paste 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) {
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 paste = await client.update(slug, html, ownerKey, { title: opts.title ?? null });
591
- process.stdout.write(`${paste.url}
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.2",
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": "./dist/cli.js"
29
+ "htmlship": "dist/cli.js"
30
30
  },
31
31
  "files": [
32
32
  "dist",