@aiquants/html-to-markdown 0.1.7 → 0.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.
@@ -3,6 +3,8 @@ import { a as getAugmentedNamespace, c as commonjsGlobal, b as getDefaultExportF
3
3
  import { randomUUID } from "node:crypto";
4
4
  import cors from "cors";
5
5
  import express from "express";
6
+ import { promises } from "fs";
7
+ import { join, resolve, dirname } from "path";
6
8
  import yargsParser from "yargs-parser";
7
9
  var bytes = { exports: {} };
8
10
  /*!
@@ -6416,10 +6418,10 @@ function requireRawBody() {
6416
6418
  if (done) {
6417
6419
  return readStream(stream, encoding, length, limit, wrap(done));
6418
6420
  }
6419
- return new Promise(function executor(resolve, reject) {
6421
+ return new Promise(function executor(resolve2, reject) {
6420
6422
  readStream(stream, encoding, length, limit, function onRead(err, buf) {
6421
6423
  if (err) return reject(err);
6422
- resolve(buf);
6424
+ resolve2(buf);
6423
6425
  });
6424
6426
  });
6425
6427
  }
@@ -7157,6 +7159,41 @@ class StreamableHTTPServerTransport {
7157
7159
  }
7158
7160
  }
7159
7161
  }
7162
+ function generateFilename(url, outputFormat) {
7163
+ if (url && url !== "direct-html-input") {
7164
+ try {
7165
+ const urlObj = new URL(url);
7166
+ let filename = urlObj.pathname.split("/").pop() || "page";
7167
+ filename = filename.replace(/\.[^.]*$/, "");
7168
+ filename = filename.replace(/[<>:"/\\|?*]/g, "_");
7169
+ if (!filename || filename.match(/^\.+$/)) {
7170
+ filename = "page";
7171
+ }
7172
+ return filename;
7173
+ } catch {
7174
+ return "page";
7175
+ }
7176
+ }
7177
+ return "page";
7178
+ }
7179
+ async function saveContentToFile(content, savePath, saveDirectory, url, outputFormat) {
7180
+ if (!savePath && !saveDirectory) {
7181
+ return null;
7182
+ }
7183
+ let finalPath;
7184
+ if (savePath) {
7185
+ finalPath = resolve(savePath);
7186
+ } else if (saveDirectory) {
7187
+ const filename = generateFilename(url);
7188
+ const extension = outputFormat === "html" ? ".html" : ".md";
7189
+ finalPath = resolve(saveDirectory, `${filename}${extension}`);
7190
+ } else {
7191
+ return null;
7192
+ }
7193
+ await promises.mkdir(dirname(finalPath), { recursive: true });
7194
+ await promises.writeFile(finalPath, content, "utf-8");
7195
+ return finalPath;
7196
+ }
7160
7197
  class StreamableHtmlToMarkdownMcpServer {
7161
7198
  constructor() {
7162
7199
  this.version = getPackageVersion();
@@ -7198,6 +7235,14 @@ class StreamableHtmlToMarkdownMcpServer {
7198
7235
  description: "Output format: 'markdown' (default), 'html', or 'both'",
7199
7236
  enum: ["markdown", "html", "both"],
7200
7237
  default: "markdown"
7238
+ },
7239
+ save_path: {
7240
+ type: "string",
7241
+ description: "Full path (including filename) where to save the converted content. Cannot be used with save_directory."
7242
+ },
7243
+ save_directory: {
7244
+ type: "string",
7245
+ description: "Directory path where to save the converted content with auto-generated filename. Cannot be used with save_path."
7201
7246
  }
7202
7247
  },
7203
7248
  anyOf: [{ required: ["url"] }, { required: ["html_content"] }]
@@ -7294,10 +7339,19 @@ class StreamableHtmlToMarkdownMcpServer {
7294
7339
  */
7295
7340
  async *handleHtmlToMarkdown(args, _context) {
7296
7341
  try {
7297
- const { url, html_content, locale = "en-US", output_format = "markdown" } = args;
7342
+ const { url, html_content, locale = "en-US", output_format = "markdown", save_path, save_directory } = args;
7298
7343
  if (!url && !html_content) {
7299
7344
  throw new Error("url または html_content のいずれかを提供する必要があります。");
7300
7345
  }
7346
+ if (save_path && typeof save_path !== "string") {
7347
+ throw new Error("'save_path' must be a string");
7348
+ }
7349
+ if (save_directory && typeof save_directory !== "string") {
7350
+ throw new Error("'save_directory' must be a string");
7351
+ }
7352
+ if (save_path && save_directory) {
7353
+ throw new Error("Cannot specify both 'save_path' and 'save_directory'");
7354
+ }
7301
7355
  if (url) {
7302
7356
  try {
7303
7357
  new URL(url);
@@ -7314,28 +7368,69 @@ class StreamableHtmlToMarkdownMcpServer {
7314
7368
  locale,
7315
7369
  htmlContent: html_content
7316
7370
  });
7371
+ yield {
7372
+ type: "progress",
7373
+ progress: 50,
7374
+ message: "変換が完了しました。ファイル保存を実行中..."
7375
+ };
7376
+ const savedPaths = [];
7377
+ if (save_path || save_directory) {
7378
+ switch (output_format) {
7379
+ case "markdown": {
7380
+ const savedPath = await saveContentToFile(result.markdown, save_path, save_directory, url, "markdown");
7381
+ if (savedPath) savedPaths.push(savedPath);
7382
+ break;
7383
+ }
7384
+ case "html": {
7385
+ const savedPath = await saveContentToFile(result.html, save_path, save_directory, url, "html");
7386
+ if (savedPath) savedPaths.push(savedPath);
7387
+ break;
7388
+ }
7389
+ case "both": {
7390
+ const markdownPath = save_path ? save_path.endsWith(".md") ? save_path : `${save_path}.md` : save_directory ? join(save_directory, `${generateFilename(url)}.md`) : void 0;
7391
+ if (markdownPath) {
7392
+ const savedMarkdownPath = await saveContentToFile(result.markdown, markdownPath, void 0, url, "markdown");
7393
+ if (savedMarkdownPath) savedPaths.push(savedMarkdownPath);
7394
+ }
7395
+ const htmlPath = save_path ? save_path.endsWith(".html") ? save_path : `${save_path}.html` : save_directory ? join(save_directory, `${generateFilename(url)}.html`) : void 0;
7396
+ if (htmlPath) {
7397
+ const savedHtmlPath = await saveContentToFile(result.html, htmlPath, void 0, url, "html");
7398
+ if (savedHtmlPath) savedPaths.push(savedHtmlPath);
7399
+ }
7400
+ break;
7401
+ }
7402
+ }
7403
+ }
7317
7404
  yield {
7318
7405
  type: "progress",
7319
7406
  progress: 100,
7320
- message: "変換が完了しました。"
7407
+ message: "処理が完了しました。"
7321
7408
  };
7322
- let content;
7409
+ let responseText = "";
7323
7410
  switch (output_format) {
7324
7411
  case "markdown":
7325
- content = [{ type: "text", text: result.markdown }];
7412
+ responseText = result.markdown;
7326
7413
  break;
7327
7414
  case "html":
7328
- content = [{ type: "text", text: result.html }];
7415
+ responseText = result.html;
7329
7416
  break;
7330
7417
  case "both":
7331
- content = [
7332
- { type: "text", text: result.markdown },
7333
- { type: "text", text: result.html }
7334
- ];
7418
+ responseText = `=== Markdown ===
7419
+ ${result.markdown}
7420
+
7421
+ === HTML ===
7422
+ ${result.html}`;
7335
7423
  break;
7336
7424
  default:
7337
7425
  throw new Error(`Invalid output format: ${output_format}`);
7338
7426
  }
7427
+ if (savedPaths.length > 0) {
7428
+ responseText += `
7429
+
7430
+ === Files Saved ===
7431
+ ${savedPaths.map((path) => `- ${path}`).join("\n")}`;
7432
+ }
7433
+ const content = [{ type: "text", text: responseText }];
7339
7434
  yield {
7340
7435
  type: "result",
7341
7436
  result: { content }
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- "use strict";require("./mcp-server-streamable-Bu7QAr3W.cjs").runStreamableMcpServer().catch(e=>{process.exit(1)});
2
+ "use strict";require("./mcp-server-streamable-B3R3qi85.cjs").runStreamableMcpServer().catch(e=>{process.exit(1)});
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { r as runStreamableMcpServer } from "./mcp-server-streamable-CQD5zK30.js";
2
+ import { r as runStreamableMcpServer } from "./mcp-server-streamable-C6-qOLdK.js";
3
3
  runStreamableMcpServer().catch((error) => {
4
4
  console.error("Failed to start streamable MCP server:", error);
5
5
  process.exit(1);
package/dist/mcp.cjs CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- const e=new(require("happy-dom").Window);Object.assign(global,{document:e.document,window:e,self:e});require("./mcp-server-D4Mj1IQ9.cjs").runMcpServer().catch(e=>{process.exit(1)});
2
+ const e=new(require("happy-dom").Window);Object.assign(global,{document:e.document,window:e,self:e});require("./mcp-server-B5lrAMe3.cjs").runMcpServer().catch(e=>{process.exit(1)});
package/dist/mcp.js CHANGED
@@ -6,7 +6,7 @@ Object.assign(global, {
6
6
  window,
7
7
  self: window
8
8
  });
9
- import { r as runMcpServer } from "./mcp-server-CpmnW1p8.js";
9
+ import { r as runMcpServer } from "./mcp-server-Ct2TYHcU.js";
10
10
  runMcpServer().catch((error) => {
11
11
  console.error("Failed to start MCP server:", error);
12
12
  process.exit(1);
@@ -1,4 +1,4 @@
1
- import { HtmlToMarkdownOptions } from './types.js';
1
+ import { HtmlToMarkdownOptions } from './types.ts';
2
2
  /**
3
3
  * Converts the HTML content of a given URL or HTML string to Markdown.
4
4
  * 指定されたURLまたはHTML文字列のHTMLコンテンツをMarkdownに変換します。
@@ -1,4 +1,4 @@
1
- import { MessageKey } from './types.js';
1
+ import { MessageKey } from './types.ts';
2
2
  /**
3
3
  * Gets a localized message.
4
4
  * ローカライズされたメッセージを取得します。
@@ -2,8 +2,8 @@
2
2
  * Main export module for htmlToMarkdown library.
3
3
  * htmlToMarkdown ライブラリのメインエクスポートモジュール
4
4
  */
5
- export { htmlToMarkdown } from './core.js';
6
- export { getMessage } from './i18n.js';
7
- export type { HtmlToMarkdownOptions } from './types.js';
8
- export { createMcpServer, runMcpServer } from './mcp-server.js';
5
+ export { htmlToMarkdown } from './core.ts';
6
+ export { getMessage } from './i18n.ts';
7
+ export type { HtmlToMarkdownOptions } from './types.ts';
8
+ export { createMcpServer, runMcpServer } from './mcp-server.ts';
9
9
  export { createStreamableMcpServer, runStreamableMcpServer } from './mcp-server-streamable.ts';
@@ -16,6 +16,8 @@ interface HtmlToMarkdownArgs {
16
16
  html_content?: string;
17
17
  locale?: "en-US" | "ja-JP";
18
18
  output_format?: "markdown" | "html" | "both";
19
+ save_path?: string;
20
+ save_directory?: string;
19
21
  }
20
22
  /**
21
23
  * Extended Tool interface with handler function.
@@ -8,6 +8,8 @@ interface HtmlToMarkdownArgs {
8
8
  html_content?: string;
9
9
  locale?: "en-US" | "ja-JP";
10
10
  output_format?: "markdown" | "html" | "both";
11
+ save_path?: string;
12
+ save_directory?: string;
11
13
  }
12
14
  /**
13
15
  * Validate and parse arguments for HTML to Markdown conversion.
@@ -2,8 +2,8 @@
2
2
  * Rehype plugins index.
3
3
  * Rehype プラグインのインデックス
4
4
  */
5
- export { rehypeAbsoluteLinks } from './rehype-absolute-links.js';
6
- export { rehypeNamedAnchors } from './rehype-named-anchors.js';
7
- export { rehypeParagraphWrapper } from './rehype-paragraph-wrapper.js';
8
- export { rehypeSanitizeHtml } from './rehype-sanitize-html.js';
9
- export { rehypeWikipediaFootnotes } from './rehype-wikipedia-footnotes.js';
5
+ export { rehypeAbsoluteLinks } from './rehype-absolute-links.ts';
6
+ export { rehypeNamedAnchors } from './rehype-named-anchors.ts';
7
+ export { rehypeParagraphWrapper } from './rehype-paragraph-wrapper.ts';
8
+ export { rehypeSanitizeHtml } from './rehype-sanitize-html.ts';
9
+ export { rehypeWikipediaFootnotes } from './rehype-wikipedia-footnotes.ts';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiquants/html-to-markdown",
3
- "version": "0.1.7",
3
+ "version": "0.2.0",
4
4
  "description": "HTML to Markdown converter",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -29,7 +29,12 @@
29
29
  "debug": "NODE_OPTIONS='--enable-source-maps' vite-node --inspect-brk=9229 main.ts --",
30
30
  "start": "vite-node main.ts",
31
31
  "build": "vite build",
32
- "prepublishOnly": "npm run build",
32
+ "typecheck": "tsc --noEmit",
33
+ "clean": "rm -rf dist",
34
+ "prepublishOnly": "npm run clean && npm run typecheck && npm run build",
35
+ "publish:patch": "npm version patch && npm publish",
36
+ "publish:minor": "npm version minor && npm publish",
37
+ "publish:major": "npm version major && npm publish",
33
38
  "lint": "biome lint app/",
34
39
  "format": "biome format --write",
35
40
  "check": "biome check --fix src/",
@@ -1 +0,0 @@
1
- const t=new(require("happy-dom").Window);Object.assign(global,{document:t.document,window:t,self:t});const r=require("./version-DTaUKNBS.cjs"),e=require("node:process"),o=require("./core-D2dlEt1l.cjs");class n{append(t){this._buffer=this._buffer?Buffer.concat([this._buffer,t]):t}readMessage(){if(!this._buffer)return null;const t=this._buffer.indexOf("\n");if(-1===t)return null;const e=this._buffer.toString("utf8",0,t).replace(/\r$/,"");return this._buffer=this._buffer.subarray(t+1),function(t){return r.JSONRPCMessageSchema.parse(JSON.parse(t))}(e)}clear(){this._buffer=void 0}}class s{constructor(t=e.stdin,r=e.stdout){this._stdin=t,this._stdout=r,this._readBuffer=new n,this._started=!1,this._ondata=t=>{this._readBuffer.append(t),this.processReadBuffer()},this._onerror=t=>{var r;null===(r=this.onerror)||void 0===r||r.call(this,t)}}async start(){if(this._started)throw new Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");this._started=!0,this._stdin.on("data",this._ondata),this._stdin.on("error",this._onerror)}processReadBuffer(){for(var t,r;;)try{const r=this._readBuffer.readMessage();if(null===r)break;null===(t=this.onmessage)||void 0===t||t.call(this,r)}catch(e){null===(r=this.onerror)||void 0===r||r.call(this,e)}}async close(){var t;this._stdin.off("data",this._ondata),this._stdin.off("error",this._onerror);0===this._stdin.listenerCount("data")&&this._stdin.pause(),this._readBuffer.clear(),null===(t=this.onclose)||void 0===t||t.call(this)}send(t){return new Promise(r=>{const e=function(t){return JSON.stringify(t)+"\n"}(t);this._stdout.write(e)?r():this._stdout.once("drain",r)})}}class a{constructor(){this.version=r.getPackageVersion(),this.server=new r.Server({name:"@aiquants/html-to-markdown",version:this.version,description:"A tool to dynamically fetch a web page from a given URL and convert it to Markdown."},{capabilities:{tools:{}}}),this.setupToolHandlers()}setupToolHandlers(){this.server.setRequestHandler(r.ListToolsRequestSchema,async()=>({tools:[{name:"html_to_markdown",description:"Convert HTML content from a URL or HTML string to Markdown format. Supports dynamic content loading and preserves table structures.",inputSchema:{type:"object",properties:{url:{type:"string",description:"The URL of the web page to convert to Markdown"},html_content:{type:"string",description:"HTML content as a string to convert to Markdown (alternative to URL)"},locale:{type:"string",description:"The locale to use for browser context (en-US or ja-JP)",enum:["en-US","ja-JP"],default:"en-US"},output_format:{type:"string",description:"Output format: 'markdown' (default), 'html', or 'both'",enum:["markdown","html","both"],default:"markdown"}},anyOf:[{required:["url"]},{required:["html_content"]}]}}]})),this.server.setRequestHandler(r.CallToolRequestSchema,async t=>{if("html_to_markdown"===t.params.name)return await this.handleHtmlToMarkdown(t.params.arguments);throw new Error(`Unknown tool: ${t.params.name}`)})}async handleHtmlToMarkdown(t){try{const r=function(t){if(!t||"object"!=typeof t)throw new Error("Arguments are required");const{url:r,html_content:e,locale:o="en-US",output_format:n="markdown"}=t;if(!r&&!e)throw new Error("Either 'url' or 'html_content' is required");if(r&&"string"!=typeof r)throw new Error("'url' must be a string");if(e&&"string"!=typeof e)throw new Error("'html_content' must be a string");if(o&&!["en-US","ja-JP"].includes(o))throw new Error("'locale' must be 'en-US' or 'ja-JP'");if(n&&!["markdown","html","both"].includes(n))throw new Error("'output_format' must be 'markdown', 'html', or 'both'");return{url:r,html_content:e,locale:o,output_format:n}}(t),{url:e,html_content:n,locale:s="en-US",output_format:a="markdown"}=r;if(e)try{new URL(e)}catch{throw new Error(`Invalid URL: ${e}`)}const i=await o.htmlToMarkdown(e||"direct-html-input",{locale:s,htmlContent:n});let c;switch(a){case"markdown":c=[{type:"text",text:i.markdown}];break;case"html":c=[{type:"text",text:i.html}];break;case"both":c=[{type:"text",text:i.markdown},{type:"text",text:i.html}];break;default:throw new Error(`Invalid output format: ${a}`)}return{content:c,isError:!1}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:String(r)}`}],isError:!0}}}async start(){const t=new s;await this.server.connect(t)}}const i=()=>new a;exports.createMcpServer=i,exports.runMcpServer=async()=>{const t=i();await t.start()};