html2pptx-local-mcp 1.1.17 → 1.1.19

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.
@@ -185,8 +185,8 @@ npx skills add https://html2pptx.app --list
185
185
  # More agents / multiple targets: use the interactive selector
186
186
  npx skills add https://html2pptx.app
187
187
 
188
- # Install remote MCP and local MCP for edit-slide
189
- npx -y html2pptx-local-mcp@latest html2pptx-install-mcp claude`;
188
+ # Claude Code users: run this one line
189
+ npx -y -p html2pptx-local-mcp@latest html2pptx-install-mcp claude`;
190
190
 
191
191
  const SKILL_INSTALL_COMMAND_JA = `# 使うエージェントのコマンドを選択
192
192
  # Claude Code
@@ -207,8 +207,8 @@ npx skills add https://html2pptx.app --list
207
207
  # その他のエージェント / 複数指定は対話形式で選択
208
208
  npx skills add https://html2pptx.app
209
209
 
210
- # Remote MCP と edit-slide 用 Local MCP をまとめて追加
211
- npx -y html2pptx-local-mcp@latest html2pptx-install-mcp claude`;
210
+ # Claude Codeユーザーはこの1行でOK
211
+ npx -y -p html2pptx-local-mcp@latest html2pptx-install-mcp claude`;
212
212
 
213
213
  const SKILL_INVOCATION_EXAMPLE = `# 例: Claude Code でスライド作成 → PPTX出力
214
214
 
@@ -239,12 +239,12 @@ const SKILL_AVAILABLE_LIST = [
239
239
  {
240
240
  name: 'edit-slide',
241
241
  description: 'ローカルHTMLスライドをlocalhost上のvisual editorで開き、localhost bridge 経由で同じファイルに保存。',
242
- capabilities: ['html2pptx_open_local_slide_editor', 'PowerPoint風UIで視覚編集', 'ローカルHTMLへの自動保存', '変更検知と競合防止'],
242
+ capabilities: ['html2pptx edit <file>', 'PowerPoint風UIで視覚編集', 'ローカルHTMLへの自動保存', '変更検知と競合防止'],
243
243
  },
244
244
  ];
245
245
 
246
- const MCP_INSTALL_EXAMPLE = `# Remote MCP と edit-slide 用 Local MCP をまとめて追加
247
- npx -y html2pptx-local-mcp@latest html2pptx-install-mcp claude`;
246
+ const MCP_INSTALL_EXAMPLE = `# Claude Codeユーザーはこの1行でOK
247
+ npx -y -p html2pptx-local-mcp@latest html2pptx-install-mcp claude`;
248
248
 
249
249
  const MCP_CONFIG_EXAMPLE = `// 方法2: 設定ファイルに手動追加
250
250
 
@@ -254,13 +254,13 @@ const MCP_CONFIG_EXAMPLE = `// 方法2: 設定ファイルに手動追加
254
254
 
255
255
  {
256
256
  "mcpServers": {
257
- "html2pptx-local": {
258
- "type": "stdio",
259
- "command": "node",
260
- "args": [
261
- "/Users/<you>/.html2pptx/bin/html2pptx-local-mcp-launcher.mjs"
262
- ],
263
- "env": {}
257
+ "html2pptx": {
258
+ "command": "npx",
259
+ "args": ["-y", "-p", "html2pptx-local-mcp@latest", "html2pptx-mcp"],
260
+ "env": {
261
+ "PPTX_STUDIO_API_KEY": "sk_live_xxxx",
262
+ "PPTX_STUDIO_BASE_URL": "http://127.0.0.1:<app-port>"
263
+ }
264
264
  }
265
265
  }
266
266
  }`;
@@ -495,7 +495,7 @@ export const DOCS_COPY = {
495
495
  {
496
496
  title: 'MCP (Model Context Protocol)',
497
497
  icon: '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="2"/><path d="M16.24 7.76a6 6 0 010 8.49m-8.48-.01a6 6 0 010-8.49m11.31-2.82a10 10 0 010 14.14m-14.14 0a10 10 0 010-14.14"/></svg>',
498
- body: 'Expose the backend to AI agents via the MCP protocol. Use the remote HTTP endpoint at /mcp for export, docs, catalog, and template publishing tools; use local stdio MCP or direct CLI for opening local HTML in edit-slide. HTML template drafts must be created through remote MCP after validation.',
498
+ body: 'Expose the backend to AI agents via the MCP protocol. Use the remote HTTP endpoint at /mcp for export, docs, catalog, and template publishing tools; use local stdio MCP only for opening local HTML in edit-slide. HTML template drafts must be created through remote MCP after validation.',
499
499
  },
500
500
  ],
501
501
  overviewCssTitle: 'Supported CSS Features',
@@ -747,8 +747,8 @@ export const DOCS_COPY = {
747
747
  skillsTitle: 'Skills Integration',
748
748
  skillsSubtitle: 'What are Skills',
749
749
  skillsDescription:
750
- 'Skills are packaged capabilities that extend AI coding agents with domain-specific knowledge and workflows. The html2pptx.app skill teaches your agent how to author slide-safe HTML, validate it against the PPTX conversion contract, optionally open the local visual editor, export through the remote MCP workflow, and publish HTML template drafts through its built-in remote MCP publishing workflow. Install once, and your agent can convert natural language instructions into production-ready PowerPoint files or creator-owned HTML drafts.',
751
- skillsHowItWorks: 'The skill bundles four core capabilities: (1) HTML authoring knowledge -- the rules for writing HTML/CSS that converts cleanly to editable PowerPoint, (2) MCP-based export automation -- connecting to the remote html2pptx.app MCP server to create jobs, poll status, and retrieve results, (3) local visual editing -- opening edit-slide through a localhost bridge when the user wants to inspect or tweak the HTML before export, and (4) template publishing -- requiring HTML drafts to go through the remote MCP validate/publish loop. The agent should ask before adding a local MCP server because that changes the user’s MCP configuration.',
750
+ 'Skills are packaged capabilities that extend AI coding agents with domain-specific knowledge and workflows. The html2pptx.app skill teaches your agent how to author slide-safe HTML, validate it against the PPTX conversion contract, optionally open the local visual editor, export through remote or local MCP workflows, and publish HTML template drafts through its built-in remote MCP publishing workflow. Install once, and your agent can convert natural language instructions into production-ready PowerPoint files or creator-owned HTML drafts.',
751
+ skillsHowItWorks: 'The skill bundles four core capabilities: (1) HTML authoring knowledge -- the rules for writing HTML/CSS that converts cleanly to editable PowerPoint, (2) MCP-based export automation -- connecting to the remote html2pptx.app MCP server or a local stdio MCP server to create jobs, poll status, and retrieve results, (3) local visual editing -- opening edit-slide through a localhost bridge when the user wants to inspect or tweak the HTML before export, and (4) template publishing -- requiring HTML drafts to go through the remote MCP validate/publish loop. The agent should ask before adding a local MCP server because that changes the user’s MCP configuration.',
752
752
  skillsCompatibility: 'Works with 18+ AI agents including Claude Code, Cursor, GitHub Copilot, Windsurf, Cline, Codex, and more. Use the skills CLI command for the specific agent you use, or the interactive selector for other supported agents.',
753
753
  skillsWorkflowTitle: 'Workflow',
754
754
  skillsWorkflow: [
@@ -756,24 +756,31 @@ export const DOCS_COPY = {
756
756
  'Agent reads the skill definition to understand the html2pptx.app HTML contract',
757
757
  'Agent generates slide-safe HTML with .slide class elements and explicit dimensions. 1600x900 is the default example',
758
758
  'Agent validates the markup against the conversion contract',
759
- 'If visual review is needed, the agent opens the local edit-slide editor through local stdio MCP, then re-reads the edited HTML from disk',
760
- 'Agent connects to remote MCP and calls html2pptx_create_export_job',
759
+ 'If visual review is needed, the agent opens the local edit-slide editor through the CLI or local stdio MCP, then re-reads the edited HTML from disk',
760
+ 'Agent connects to remote MCP or local stdio MCP and calls html2pptx_create_export_job',
761
761
  'Agent polls with html2pptx_wait_for_export_job until completed',
762
762
  'If the user wants to create an HTML template draft, the agent uses the html2pptx skill and remote MCP: infer title/tags from the HTML, run AI security preflight, validate, fix errors, save a draft, then return draftUrl for dashboard review',
763
763
  'Agent returns the completed job summary to the user',
764
764
  ],
765
765
  skillsLocalEditorTitle: 'Local Visual Editor',
766
766
  skillsLocalEditorLead:
767
- 'The edit-slide skill lets an agent open a local HTML slide deck in a PowerPoint-style visual editor served from an available loopback origin such as http://localhost:<port>. Hosted edit-slide is not allowed for local file editing. The local dev script chooses a free port and registers it in .html2pptx/edit-slide/editor-server.json inside the current project. The deck file stays on the user machine and is read/written through a localhost bridge started by the local stdio MCP tool. If the local MCP server is not already configured, the agent should explain that it will change MCP settings and ask the user before adding it.',
768
- skillsLocalEditorCode: `# Use the local MCP tool only
769
- html2pptx_open_local_slide_editor({ "filePath": "html2pptx/slides.html" })
767
+ 'The edit-slide skill lets an agent open a local HTML slide deck in a PowerPoint-style visual editor served from an available loopback origin such as http://localhost:<port>. Hosted edit-slide is not allowed for local file editing. The local dev script chooses a free port and registers it in .html2pptx/edit-slide/editor-server.json inside the current project. The deck file stays on the user machine and is read/written through a localhost bridge started by the CLI or by the local stdio MCP tool. If the local MCP server is not already configured, the agent should explain that it will change MCP settings and ask the user before adding it.',
768
+ skillsLocalEditorCode: `# From the project that contains your slide HTML
769
+ node scripts/dev-studio.mjs
770
+ npx --yes https://html2pptx.app/downloads/html2pptx-cli-0.4.0.tgz edit ./html2pptx/slides.html
771
+
772
+ # If html2pptx-cli@0.4.0+ is available from npm
773
+ npx --yes html2pptx-cli edit ./html2pptx/slides.html
774
+
775
+ # If the CLI is installed globally
776
+ html2pptx edit ./html2pptx/slides.html
770
777
 
771
- # Claude Code local MCP setup
772
- npx -y html2pptx-local-mcp@latest html2pptx-install-mcp claude`,
778
+ # Print the URL instead of opening a browser
779
+ html2pptx edit ./html2pptx/slides.html --no-open`,
773
780
  skillsLocalEditorFlowTitle: 'How the local editor flow works',
774
781
  skillsLocalEditorFlow: [
775
- { label: '1. Choose remote or local MCP', detail: 'Use remote MCP for normal PPTX export and catalog/docs tools. Use local stdio MCP or `html2pptx edit <file>` when the agent must open or edit a local `.html` / `.htm` file through edit-slide. If local MCP is missing, use direct CLI when the local editor app is running or ask before installing it.' },
776
- { label: '2. Start bridge', detail: '`html2pptx_open_local_slide_editor` starts a tiny HTTP server on `127.0.0.1`. Unless a port is specified, the OS assigns a free port. Access is scoped to the current working directory.' },
782
+ { label: '1. Choose remote or local MCP', detail: 'Use remote MCP for normal PPTX export and catalog/docs tools. Use local stdio MCP only when the agent must open or edit a local `.html` / `.htm` file through edit-slide. If local MCP is missing, ask the user before installing it.' },
783
+ { label: '2. Start bridge', detail: '`html2pptx edit <file>` or `html2pptx_open_local_slide_editor` starts a tiny HTTP server on `127.0.0.1`. Unless a port is specified, the OS assigns a free port. Access is scoped to the current working directory.' },
777
784
  { label: '3. Open editor', detail: 'The command opens `http://localhost:<editor-port>/edit-slide?file=...&bridge=http://127.0.0.1:<bridge-port>#bridgeToken=...`. The one-time token is kept in the URL fragment and the editor removes it from the address bar after startup.' },
778
785
  { label: '4. Load local file', detail: 'The browser editor presents the token and fetches the selected `.html` or `.htm` file from the localhost bridge. No marketplace draft or public page is created.' },
779
786
  { label: '5. Edit visually', detail: 'Users can click slide elements and adjust text, typography, color, size, padding, margin, radius, border, opacity, and other properties from the right panel.' },
@@ -793,10 +800,10 @@ npx -y html2pptx-local-mcp@latest html2pptx-install-mcp claude`,
793
800
  ],
794
801
  skillsLocalEditorTroubleshootingTitle: 'Common issues',
795
802
  skillsLocalEditorTroubleshooting: [
796
- { issue: 'The local editor tool is not available', fix: 'Ask before adding local MCP with `npx -y html2pptx-local-mcp@latest html2pptx-install-mcp claude`.' },
797
- { issue: '`html2pptx edit` cannot find the local editor', fix: 'Start `node scripts/dev-studio.mjs` first, or pass the active loopback URL with `--base-url http://localhost:<port>`. Hosted edit-slide is never used.' },
803
+ { issue: 'The command is not found', fix: 'Start `node scripts/dev-studio.mjs`, then use `npx --yes https://html2pptx.app/downloads/html2pptx-cli-0.4.0.tgz edit ./path/to/slides.html`, or install `html2pptx-cli@0.4.0+` globally.' },
804
+ { issue: '`npx` is not available', fix: 'Use the already-configured local stdio MCP tool `html2pptx_open_local_slide_editor`, or ask before adding local MCP. If local MCP is unavailable, install/expose npm or a global `html2pptx` CLI first.' },
798
805
  { issue: 'The editor opens but the deck does not load', fix: 'Confirm the path is relative to the directory where the command was run and the file extension is `.html` or `.htm`.' },
799
- { issue: 'Changes do not save', fix: 'Keep the local MCP session running. Stopping it stops the localhost bridge.' },
806
+ { issue: 'Changes do not save', fix: 'Keep the terminal running. Closing the `html2pptx edit` process stops the localhost bridge.' },
800
807
  { issue: 'The agent wants to add local MCP', fix: 'Confirm with the user first. Local MCP is optional and only needed for MCP-driven local edit-slide sessions; remote MCP can still export PPTX without local file access.' },
801
808
  { issue: 'Another tab is read-only', fix: 'The editor uses a local tab lock to avoid two tabs writing to the same file. Use the active tab or click the transfer edit control.' },
802
809
  ],
@@ -841,9 +848,9 @@ echo 'HTML2PPTX_API_KEY=sk_live_xxxx' >> .env`,
841
848
  mcpTitle: 'MCP Integration',
842
849
  mcpSubtitle: 'What is MCP',
843
850
  mcpDescription:
844
- 'MCP (Model Context Protocol) is an open protocol that exposes backend capabilities to AI agents through a standardized tool interface. html2pptx.app supports two MCP surfaces: the remote HTTP MCP endpoint at /mcp for export, usage, docs, templates, catalog, and HTML template publishing workflows, and the local stdio MCP server for local edit-slide sessions through a localhost bridge. Use remote MCP when the agent needs to convert HTML to PPTX or create an HTML template draft. Use local stdio MCP or `html2pptx edit <file>` when the agent must open, preview, or edit a local HTML file on the user machine; local MCP does not publish templates.',
851
+ 'MCP (Model Context Protocol) is an open protocol that exposes backend capabilities to AI agents through a standardized tool interface. html2pptx.app supports two MCP surfaces: the remote HTTP MCP endpoint at /mcp for export, usage, docs, templates, catalog, and HTML template publishing workflows, and the local stdio MCP server for export tools plus local edit-slide sessions through a localhost bridge. Use remote MCP when the agent needs to convert HTML to PPTX or create an HTML template draft. Use local stdio MCP only when the agent must open, preview, or edit a local HTML file on the user machine; local MCP does not publish templates.',
845
852
  mcpSetupTitle: 'Installation & Setup',
846
- mcpSetupLead: 'Claude Code users can run the one-command installer to add both remote MCP and local edit-slide MCP. Use the remote-only command when local visual editing is not needed.',
853
+ mcpSetupLead: 'For Claude Code, users can run one command to register both remote export tools and local edit-slide. Remote MCP follows the hosted service; local MCP uses html2pptx-local-mcp@latest so future starts pick up the newest published package.',
847
854
  mcpEditorTabs: [
848
855
  {
849
856
  id: 'claude-code',
@@ -851,19 +858,20 @@ echo 'HTML2PPTX_API_KEY=sk_live_xxxx' >> .env`,
851
858
  icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/claude/default.svg',
852
859
  recommended: true,
853
860
  steps: [
854
- { title: 'Set up the remote server (recommended)', body: 'Run the following command in your terminal.', code: `claude mcp add --transport http html2pptx https://html2pptx.app/mcp` },
855
- { title: 'Manual setup for Claude Code', body: 'If the command above does not work, run the following to add the MCP server manually.', code: `claude mcp add html2pptx --transport http https://html2pptx.app/mcp` },
856
- { title: 'One-command remote + local setup', body: 'Installs the remote MCP for exports/docs/templates and the local MCP for edit-slide. It runs from the published package, so no repository checkout is required. The local MCP is registered through a stable launcher for reliable Claude Code startup.', code: `npx -y html2pptx-local-mcp@latest html2pptx-install-mcp claude` },
861
+ { title: 'Claude Code users: run this one line', body: 'This registers remote export as `html2pptx`, then writes local edit-slide as `html2pptx-local` directly into Claude Code user config.', code: `npx -y -p html2pptx-local-mcp@latest html2pptx-install-mcp claude` },
862
+ { title: 'Manual remote setup', body: 'If you only need hosted export tools, add the remote MCP server directly.', code: `claude mcp add --scope user --transport http html2pptx https://html2pptx.app/mcp` },
863
+ { title: 'Compatible local edit-slide setup', body: 'If Claude Code stdio registration is unreliable, use the installer because it writes the local server entry directly.', code: `npx -y -p html2pptx-local-mcp@latest html2pptx-install-mcp claude` },
857
864
  ],
858
- tip: 'To make the MCP server available across all projects, install it with the --scope user flag:\nclaude mcp add --scope user --transport http html2pptx https://html2pptx.app/mcp',
865
+ tip: 'The installer registers the remote MCP with user scope and writes the local stdio MCP directly to Claude Code user config for better compatibility.',
859
866
  },
860
867
  {
861
868
  id: 'codex',
862
869
  label: 'Codex',
863
870
  icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/openai/light.svg',
864
871
  steps: [
865
- { title: 'Run the CLI command (recommended)', body: 'Run the following command in your terminal. OAuth authentication will start automatically.', code: `codex mcp add html2pptx --url https://html2pptx.app/mcp` },
866
- { title: 'Optional local stdio MCP for edit-slide', body: 'Use this when Codex needs to launch local edit-slide for a `.html` file. It runs from the published package, so no repository checkout is required. The editor app and localhost bridge use free ports. Codex should ask before adding it because it modifies the local MCP configuration.', code: `codex mcp add html2pptx-local -- npx -y html2pptx-local-mcp@latest html2pptx-mcp` },
872
+ { title: 'Run the CLI command (recommended)', body: 'Add the hosted remote MCP server. OAuth authentication will start automatically.', code: `codex mcp add html2pptx --url https://html2pptx.app/mcp` },
873
+ { title: 'Manual remote setup', body: 'If you only need hosted export tools, add the remote MCP server directly.', code: `codex mcp add html2pptx --url https://html2pptx.app/mcp` },
874
+ { title: 'Optional local stdio MCP for edit-slide', body: 'Use this only when Codex needs to launch local edit-slide for a `.html` file. It runs from the published package, so no repository checkout is required.', code: `codex mcp add html2pptx-local -- npx -y -p html2pptx-local-mcp@latest html2pptx-mcp` },
867
875
  { title: 'Manual setup via codex.json', body: 'Alternatively, create or edit codex.json in your project root.', code: `{\n "mcpServers": {\n "html2pptx": {\n "type": "url",\n "url": "https://html2pptx.app/mcp"\n }\n }\n}` },
868
876
  ],
869
877
  },
@@ -1583,8 +1591,8 @@ echo 'HTML2PPTX_API_KEY=sk_live_xxxx' >> .env`,
1583
1591
  skillsTitle: 'Skills統合ガイド',
1584
1592
  skillsSubtitle: 'Skillsとは',
1585
1593
  skillsDescription:
1586
- 'Skillsは、AIコーディングエージェントにドメイン固有の知識とワークフローを追加するパッケージ機能です。html2pptx.app のスキルをインストールすると、エージェントがスライド用HTMLの作成方法を理解し、PPTX変換契約に対する検証を行い、必要に応じてローカル Visual Editor を開き、remote MCP のワークフローでエクスポートし、内蔵のremote MCP公開ワークフローでHTMLテンプレートdraftを作成できます。自然言語の指示だけで、本番品質のPowerPointファイルや作成者所有のHTML draftを生成できます。',
1587
- skillsHowItWorks: 'スキルには4つのコア機能がバンドルされています: (1) HTMLオーサリング知識 -- 高品質なPowerPointに変換されるHTML/CSSの書き方ルール、(2) MCPベースのエクスポート自動化 -- remote MCP に接続してジョブ作成・ステータスポーリング・結果取得を行う仕組み、(3) ローカル Visual Editor -- ユーザーがPPTX出力前にHTMLを目視確認・微調整したい場合に edit-slide を localhost bridge 経由で開く仕組み、(4) テンプレート公開 -- HTML draft を remote MCP の validate/publish ループに限定する仕組みです。local MCP の追加はユーザーのMCP設定を変更するため、エージェントは必ず事前に確認します。',
1594
+ 'Skillsは、AIコーディングエージェントにドメイン固有の知識とワークフローを追加するパッケージ機能です。html2pptx.app のスキルをインストールすると、エージェントがスライド用HTMLの作成方法を理解し、PPTX変換契約に対する検証を行い、必要に応じてローカル Visual Editor を開き、remote MCP または local MCP のワークフローでエクスポートし、内蔵のremote MCP公開ワークフローでHTMLテンプレートdraftを作成できます。自然言語の指示だけで、本番品質のPowerPointファイルや作成者所有のHTML draftを生成できます。',
1595
+ skillsHowItWorks: 'スキルには4つのコア機能がバンドルされています: (1) HTMLオーサリング知識 -- 高品質なPowerPointに変換されるHTML/CSSの書き方ルール、(2) MCPベースのエクスポート自動化 -- remote MCP または local stdio MCP に接続してジョブ作成・ステータスポーリング・結果取得を行う仕組み、(3) ローカル Visual Editor -- ユーザーがPPTX出力前にHTMLを目視確認・微調整したい場合に edit-slide を localhost bridge 経由で開く仕組み、(4) テンプレート公開 -- HTML draft を remote MCP の validate/publish ループに限定する仕組みです。local MCP の追加はユーザーのMCP設定を変更するため、エージェントは必ず事前に確認します。',
1588
1596
  skillsCompatibility: 'Claude Code、Cursor、GitHub Copilot、Windsurf、Cline、Codex 等 18以上のAIエージェントに対応。使うエージェントに対応する skills CLI コマンドを選ぶか、その他の対応エージェントは対話形式で選択します。',
1589
1597
  skillsWorkflowTitle: 'ワークフロー',
1590
1598
  skillsWorkflow: [
@@ -1592,24 +1600,31 @@ echo 'HTML2PPTX_API_KEY=sk_live_xxxx' >> .env`,
1592
1600
  'エージェントがスキル定義を読み取り、html2pptx.appのHTML契約を理解',
1593
1601
  'エージェントが .slide クラス要素と明示サイズ付きのスライドセーフHTMLを生成。1600x900 はデフォルト例',
1594
1602
  'エージェントが変換契約に対してマークアップを検証',
1595
- '目視確認が必要な場合は、local stdio MCP で edit-slide を開き、編集後のHTMLをディスクから再読み込み',
1596
- 'エージェントが remote MCP に接続し html2pptx_create_export_job を呼び出し',
1603
+ '目視確認が必要な場合は、CLI または local stdio MCP で edit-slide を開き、編集後のHTMLをディスクから再読み込み',
1604
+ 'エージェントが remote MCP または local stdio MCP に接続し html2pptx_create_export_job を呼び出し',
1597
1605
  'エージェントが html2pptx_wait_for_export_job で完了をポーリング',
1598
1606
  'HTMLテンプレートdraftを作る場合は、html2pptx スキルと remote MCP でHTMLから題名・タグを推定し、AIセキュリティ事前診断、検証、エラー修正、下書き保存を行い、draftUrlを返却',
1599
1607
  'エージェントが completed 状態のジョブサマリーをユーザーに返却',
1600
1608
  ],
1601
1609
  skillsLocalEditorTitle: 'ローカル Visual Editor',
1602
1610
  skillsLocalEditorLead:
1603
- 'edit-slide スキルを使うと、エージェントが作成したローカルHTMLスライドを、http://localhost:<port> のような空き loopback origin で動くPowerPoint風のノーコード編集画面で開けます。ローカルファイル編集に hosted edit-slide は使えません。ローカル dev script が空きポートを選び、現在のプロジェクト内の .html2pptx/edit-slide/editor-server.json に登録します。スライドHTMLファイルはユーザーのPC上に残り、CLI または local stdio MCP が起動する localhost bridge 経由で読み書きされます。local MCP が未設定の場合は、local editor が動いていれば `html2pptx edit <file>` を使い、MCP設定を変更する場合はユーザー確認を取ってから追加します。',
1604
- skillsLocalEditorCode: `# local MCP tool だけを使う
1605
- html2pptx_open_local_slide_editor({ "filePath": "html2pptx/slides.html" })
1611
+ 'edit-slide スキルを使うと、エージェントが作成したローカルHTMLスライドを、http://localhost:<port> のような空き loopback origin で動くPowerPoint風のノーコード編集画面で開けます。ローカルファイル編集に hosted edit-slide は使えません。ローカル dev script が空きポートを選び、現在のプロジェクト内の .html2pptx/edit-slide/editor-server.json に登録します。スライドHTMLファイルはユーザーのPC上に残り、CLI または local stdio MCP が起動する localhost bridge 経由で読み書きされます。local MCP が未設定の場合、エージェントはMCP設定を変更することを説明し、ユーザー確認を取ってから追加します。',
1612
+ skillsLocalEditorCode: `# スライドHTMLがあるプロジェクトで実行
1613
+ node scripts/dev-studio.mjs
1614
+ npx --yes https://html2pptx.app/downloads/html2pptx-cli-0.4.0.tgz edit ./html2pptx/slides.html
1615
+
1616
+ # npm から html2pptx-cli@0.4.0+ を使える場合
1617
+ npx --yes html2pptx-cli edit ./html2pptx/slides.html
1618
+
1619
+ # CLIをグローバルインストール済みの場合
1620
+ html2pptx edit ./html2pptx/slides.html
1606
1621
 
1607
- # Claude Code local MCP setup
1608
- npx -y html2pptx-local-mcp@latest html2pptx-install-mcp claude`,
1622
+ # ブラウザを自動で開かず、URLだけ表示
1623
+ html2pptx edit ./html2pptx/slides.html --no-open`,
1609
1624
  skillsLocalEditorFlowTitle: 'ローカル編集の仕組み',
1610
1625
  skillsLocalEditorFlow: [
1611
- { label: '1. remote / local MCP を選ぶ', detail: '通常のPPTX出力、docs、template、usage 取得は remote MCP を使えます。ローカル `.html` / `.htm` を edit-slide で開いて編集する場合は local stdio MCP または `html2pptx edit <file>` を使います。local MCP が未設定なら、直接CLIを使うか、追加前にユーザーへ確認します。' },
1612
- { label: '2. bridge を起動', detail: '`html2pptx_open_local_slide_editor` が `127.0.0.1` に小さなHTTPサーバーを起動します。ポートは指定しない限りOSが選ぶ空きポートになります。アクセス範囲はコマンドを実行したカレントディレクトリ内に限定されます。' },
1626
+ { label: '1. remote / local MCP を選ぶ', detail: '通常のPPTX出力、docs、template、usage 取得は remote MCP を使えます。ローカル `.html` / `.htm` を edit-slide で開いて編集する必要がある場合だけ local stdio MCP を使います。local MCP が未設定なら、追加前にユーザーへ確認します。' },
1627
+ { label: '2. bridge を起動', detail: '`html2pptx edit <file>` または `html2pptx_open_local_slide_editor` が `127.0.0.1` に小さなHTTPサーバーを起動します。ポートは指定しない限りOSが選ぶ空きポートになります。アクセス範囲はコマンドを実行したカレントディレクトリ内に限定されます。' },
1613
1628
  { label: '3. エディタを開く', detail: 'コマンドが `http://localhost:<editor-port>/edit-slide?file=...&bridge=http://127.0.0.1:<bridge-port>#bridgeToken=...` を開きます。ワンタイム token は URL fragment に置かれ、起動後にエディタがアドレスバーから消します。' },
1614
1629
  { label: '4. ローカルHTMLを読み込む', detail: 'ブラウザの編集画面が token を提示し、localhost bridge から対象の `.html` / `.htm` ファイルを取得します。マーケットプレイス下書きや公開ページは作成されません。' },
1615
1630
  { label: '5. ノーコードで編集', detail: 'スライド上の要素をクリックし、右パネルからテキスト、タイポグラフィ、色、サイズ、余白、角丸、ボーダー、不透明度などを調整できます。' },
@@ -1629,11 +1644,11 @@ npx -y html2pptx-local-mcp@latest html2pptx-install-mcp claude`,
1629
1644
  ],
1630
1645
  skillsLocalEditorTroubleshootingTitle: 'よくあるつまずき',
1631
1646
  skillsLocalEditorTroubleshooting: [
1632
- { issue: 'local editor tool が使えない', fix: '`npx -y html2pptx-local-mcp@latest html2pptx-install-mcp claude` local MCP を追加してよいか、先にユーザーへ確認してください。' },
1633
- { issue: '`html2pptx edit` が local editor を見つけられない', fix: '`node scripts/dev-studio.mjs` を先に起動するか、`--base-url http://localhost:<port>` loopback URL を渡してください。hosted edit-slide は使いません。' },
1647
+ { issue: 'コマンドが見つからない', fix: '`node scripts/dev-studio.mjs` を起動してから `npx --yes https://html2pptx.app/downloads/html2pptx-cli-0.4.0.tgz edit ./path/to/slides.html` を使うか、`html2pptx-cli@0.4.0+` をグローバルインストールしてください。' },
1648
+ { issue: '`npx` が使えない', fix: 'すでに設定済みの local stdio MCP ツール `html2pptx_open_local_slide_editor` を使うか、local MCP を追加する前にユーザーへ確認してください。local MCP も使えない場合は、npm またはグローバル `html2pptx` CLI を先に使える状態にします。' },
1634
1649
  { issue: '画面は開くがスライドが読み込まれない', fix: 'コマンドを実行したディレクトリからの相対パスになっているか、拡張子が `.html` / `.htm` かを確認してください。' },
1635
- { issue: '編集内容が保存されない', fix: 'local MCP セッションを終了しないでください。bridge が止まると保存できません。' },
1636
- { issue: 'エージェントが local MCP を追加しようとする', fix: '先にユーザーへ確認してください。local MCP は MCP 経由でローカル edit-slide を開く場合だけ必要です。CLI `html2pptx edit <file>` でも local editor を開けます。' },
1650
+ { issue: '編集内容が保存されない', fix: 'ターミナルで起動している `html2pptx edit` を終了しないでください。bridge が止まると保存できません。' },
1651
+ { issue: 'エージェントが local MCP を追加しようとする', fix: '先にユーザーへ確認してください。local MCP は MCP 経由でローカル edit-slide を開く場合だけ必要です。remote MCP だけでもPPTX出力はできます。' },
1637
1652
  { issue: '別タブが読み取り専用になる', fix: '同じHTMLを複数タブで編集して競合しないよう、エディタはタブロックを使います。編集権限のあるタブを使うか、画面上の移譲操作を行ってください。' },
1638
1653
  ],
1639
1654
  skillsSetupTitle: 'セットアップ',
@@ -1677,9 +1692,9 @@ echo 'HTML2PPTX_API_KEY=sk_live_xxxx' >> .env`,
1677
1692
  mcpTitle: 'MCP統合ガイド',
1678
1693
  mcpSubtitle: 'MCPとは',
1679
1694
  mcpDescription:
1680
- 'MCP (Model Context Protocol) は、標準化されたツールインターフェースを通じてAIエージェントにバックエンド機能を公開するオープンプロトコルです。html2pptx.app は2種類のMCPサーフェスを提供します。remote HTTP MCP (`/mcp`) はエクスポート、usage、docs、templates、catalog、HTMLテンプレート公開に使います。local stdio MCP localhost bridge 経由でローカルHTMLを edit-slide で開くために使います。HTMLをPPTXに変換する、またはHTMLテンプレートdraftを作るなら remote MCP、ユーザーPC上の `.html` を開いてプレビュー・編集するなら local MCP を選びます。local MCP はテンプレート公開には使いません。',
1695
+ 'MCP (Model Context Protocol) は、標準化されたツールインターフェースを通じてAIエージェントにバックエンド機能を公開するオープンプロトコルです。html2pptx.app は2種類のMCPサーフェスを提供します。remote HTTP MCP (`/mcp`) はエクスポート、usage、docs、templates、catalog、HTMLテンプレート公開に使います。local stdio MCP は同じエクスポート系ツールに加えて、localhost bridge 経由でローカルHTMLを edit-slide で開くために使います。HTMLをPPTXに変換する、またはHTMLテンプレートdraftを作るなら remote MCP、ユーザーPC上の `.html` を開いてプレビュー・編集するなら local MCP を選びます。local MCP はテンプレート公開には使いません。',
1681
1696
  mcpSetupTitle: 'インストール & セットアップ',
1682
- mcpSetupLead: 'Claude Code では1コマンドインストーラで remote MCP と edit-slide local MCP をまとめて追加できます。ローカル編集が不要な場合だけ remote-only コマンドを使ってください。',
1697
+ mcpSetupLead: 'Claude Codeユーザーは1コマンドで remote exportlocal edit-slide の両方を登録できます。remote MCP は hosted service 側の更新が反映され、local MCP html2pptx-local-mcp@latest 経由で起動するため、次回起動時に新しい公開パッケージへ追従できます。',
1683
1698
  mcpEditorTabs: [
1684
1699
  {
1685
1700
  id: 'claude-code',
@@ -1687,19 +1702,20 @@ echo 'HTML2PPTX_API_KEY=sk_live_xxxx' >> .env`,
1687
1702
  icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/claude/default.svg',
1688
1703
  recommended: true,
1689
1704
  steps: [
1690
- { title: 'リモートサーバーをセットアップ(推奨)', body: 'ターミナルで以下のコマンドを実行してください。', code: `claude mcp add --transport http html2pptx https://html2pptx.app/mcp` },
1691
- { title: '手動セットアップ', body: 'コマンドが使えない場合は、以下を実行してMCPサーバーを手動で追加してください。', code: `claude mcp add html2pptx --transport http https://html2pptx.app/mcp` },
1692
- { title: 'Remote + Local を1コマンドで追加', body: 'PPTX出力・docs・templates 用の remote MCP と、edit-slide 用の local MCP をまとめて追加します。公開パッケージから導入するため、リポジトリの checkout は不要です。local MCP は Claude Code が安定して起動できるよう stable launcher として登録されます。', code: `npx -y html2pptx-local-mcp@latest html2pptx-install-mcp claude` },
1705
+ { title: 'Claude Codeユーザーはこの1行でOK', body: '`html2pptx` として remote export を登録し、`html2pptx-local` として local edit-slide を Claude Code の user config に直接書き込みます。', code: `npx -y -p html2pptx-local-mcp@latest html2pptx-install-mcp claude` },
1706
+ { title: 'remote だけ手動セットアップ', body: 'PPTX出力などのホスト側ツールだけ必要な場合はこちらを使います。', code: `claude mcp add --scope user --transport http html2pptx https://html2pptx.app/mcp` },
1707
+ { title: 'local edit-slide 互換セットアップ', body: 'Claude Code stdio 登録が不安定な場合も、このインストーラーが local server 設定を直接書き込みます。', code: `npx -y -p html2pptx-local-mcp@latest html2pptx-install-mcp claude` },
1693
1708
  ],
1694
- tip: 'すべてのプロジェクトでMCPサーバーを使用するには --scope user フラグを追加してください:\nclaude mcp add --scope user --transport http html2pptx https://html2pptx.app/mcp',
1709
+ tip: 'このインストーラーは remote MCP user scope で登録し、互換性のため local stdio MCP Claude Code の user config に直接書き込みます。',
1695
1710
  },
1696
1711
  {
1697
1712
  id: 'codex',
1698
1713
  label: 'Codex',
1699
1714
  icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/openai/light.svg',
1700
1715
  steps: [
1701
- { title: 'CLIコマンドで追加(推奨)', body: 'ターミナルで以下のコマンドを実行してください。OAuth認証が自動的に開始されます。', code: `codex mcp add html2pptx --url https://html2pptx.app/mcp` },
1702
- { title: 'edit-slide 用 local stdio MCP(任意)', body: 'Codex がローカル `.html` を edit-slide で開く必要がある場合だけ追加します。公開パッケージから起動するため、リポジトリのcheckoutは不要です。エディタ本体と localhost bridge は空きポートを使います。MCP設定を変更するため、Codex は実行前にユーザー確認を取ってください。', code: `codex mcp add html2pptx-local -- npx -y html2pptx-local-mcp@latest html2pptx-mcp` },
1716
+ { title: 'CLIコマンドで追加(推奨)', body: 'hosted remote MCP server を追加します。OAuth認証が自動的に開始されます。', code: `codex mcp add html2pptx --url https://html2pptx.app/mcp` },
1717
+ { title: 'remote だけ手動セットアップ', body: 'PPTX出力などのホスト側ツールだけ必要な場合はこちらを使います。', code: `codex mcp add html2pptx --url https://html2pptx.app/mcp` },
1718
+ { title: 'edit-slide 用 local stdio MCP(任意)', body: 'Codex がローカル `.html` を edit-slide で開く必要がある場合だけ追加します。公開パッケージから起動するため、リポジトリの checkout は不要です。', code: `codex mcp add html2pptx-local -- npx -y -p html2pptx-local-mcp@latest html2pptx-mcp` },
1703
1719
  { title: 'codex.json で手動セットアップ', body: 'コマンドが使えない場合は、プロジェクトルートの codex.json に以下を追加してください。', code: `{\n "mcpServers": {\n "html2pptx": {\n "type": "url",\n "url": "https://html2pptx.app/mcp"\n }\n }\n}` },
1704
1720
  ],
1705
1721
  },
@@ -3,6 +3,7 @@ export interface EditOptions {
3
3
  port?: string;
4
4
  baseUrl?: string;
5
5
  noOpen?: boolean;
6
+ open?: boolean;
6
7
  json?: boolean;
7
8
  }
8
9
  interface BridgeContext {
@@ -506,24 +506,26 @@ function bail(message, options) {
506
506
  process.exit(1);
507
507
  }
508
508
  export async function editCommand(input, options = {}) {
509
+ const noOpen = options.noOpen === true || options.open === false;
510
+ const normalizedOptions = { ...options, noOpen };
509
511
  if (!input) {
510
- bail("HTML file path is required. Example: html2pptx edit ./html2pptx/slides.html", options);
512
+ bail("HTML file path is required. Example: html2pptx edit ./html2pptx/slides.html", normalizedOptions);
511
513
  }
512
514
  const root = await realpath(process.cwd());
513
515
  const abs = resolve(root, input);
514
516
  const rel = relativeToRoot(root, abs);
515
517
  const ext = extname(abs).toLowerCase();
516
518
  if (!ALLOWED_EXT.has(ext)) {
517
- bail("Only .html/.htm files can be opened in the editor.", options);
519
+ bail("Only .html/.htm files can be opened in the editor.", normalizedOptions);
518
520
  }
519
521
  if (abs !== root && !abs.startsWith(root + sep)) {
520
- bail("The file must be inside the current working directory.", options);
522
+ bail("The file must be inside the current working directory.", normalizedOptions);
521
523
  }
522
524
  try {
523
525
  await stat(abs);
524
526
  }
525
527
  catch {
526
- bail(`File not found: ${rel}`, options);
528
+ bail(`File not found: ${rel}`, normalizedOptions);
527
529
  }
528
530
  let baseUrl;
529
531
  let requestedPort;
@@ -532,7 +534,7 @@ export async function editCommand(input, options = {}) {
532
534
  requestedPort = parsePort(options.port);
533
535
  }
534
536
  catch (error) {
535
- bail(error.message, options);
537
+ bail(error.message, normalizedOptions);
536
538
  }
537
539
  const ctx = {
538
540
  root,
@@ -550,7 +552,7 @@ export async function editCommand(input, options = {}) {
550
552
  }
551
553
  const bridgeUrl = `http://127.0.0.1:${bridgePort}`;
552
554
  const editorUrl = buildEditorUrl(baseUrl, rel, bridgeUrl, ctx.sessionToken);
553
- if (options.json) {
555
+ if (normalizedOptions.json) {
554
556
  console.log(JSON.stringify({
555
557
  success: true,
556
558
  editorUrl: editorUrl.toString(),
@@ -562,7 +564,7 @@ export async function editCommand(input, options = {}) {
562
564
  }
563
565
  else {
564
566
  p.log.success(`Local edit bridge listening on ${pc.cyan(bridgeUrl)} ${pc.dim("(session token required)")}`);
565
- if (options.noOpen) {
567
+ if (normalizedOptions.noOpen) {
566
568
  p.log.info(`Open in editor: ${pc.cyan(editorUrl.toString())}`);
567
569
  }
568
570
  else {
@@ -570,7 +572,7 @@ export async function editCommand(input, options = {}) {
570
572
  }
571
573
  p.log.info(pc.dim("Press Ctrl+C to stop the bridge."));
572
574
  }
573
- if (!options.noOpen) {
575
+ if (!normalizedOptions.noOpen) {
574
576
  openUrl(editorUrl.toString());
575
577
  }
576
578
  stopOnSignal(server);
package/cli/dist/index.js CHANGED
@@ -41,7 +41,7 @@ program
41
41
  .action(convertCommand);
42
42
  program
43
43
  .command("edit")
44
- .description("Open a local HTML slide file in the localhost visual editor")
44
+ .description("Open a local slide HTML file in the html2pptx visual editor")
45
45
  .argument("<input>", "Path to HTML file")
46
46
  .option("--port <port>", "Local bridge port. Defaults to auto.")
47
47
  .option("--no-open", "Print the editor URL without opening a browser")
package/cli/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "html2pptx-cli",
3
- "version": "0.4.2",
3
+ "version": "0.4.0",
4
4
  "description": "CLI tool to convert HTML/CSS to editable PowerPoint files via html2pptx.app",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,11 +21,22 @@ export function createLocalSlideEditorManager(options = {}) {
21
21
 
22
22
  async function open(input = {}) {
23
23
  const file = await resolveEditableFile(input.filePath, rootCwd);
24
+ const reuseExisting = input.reuseExisting !== false;
25
+ if (reuseExisting) {
26
+ const existing = findReusableSession(file);
27
+ if (existing) {
28
+ return {
29
+ ...redactSessionForResponse(existing),
30
+ reused: true,
31
+ };
32
+ }
33
+ }
34
+
24
35
  const baseUrl = normalizeEditorBaseUrl(
25
36
  input.baseUrl || process.env.HTML2PPTX_EDITOR_BASE_URL || await readRegisteredEditorBaseUrl(file.root),
26
37
  );
27
38
  const port = normalizePort(input.port, 0);
28
- const openBrowser = input.openBrowser !== false;
39
+ const openBrowser = input.openBrowser === true;
29
40
  const invocation = resolveCliInvocation(options);
30
41
  const childArgs = [
31
42
  ...invocation.baseArgs,
@@ -78,7 +89,25 @@ export function createLocalSlideEditorManager(options = {}) {
78
89
 
79
90
  const details = await waitForBridgeDetails(session, launchTimeoutMs);
80
91
  session.details = details;
81
- return redactSessionForResponse(session);
92
+ return {
93
+ ...redactSessionForResponse(session),
94
+ reused: false,
95
+ };
96
+ }
97
+
98
+ function findReusableSession(file) {
99
+ for (const session of sessions.values()) {
100
+ if (
101
+ session.filePath === file.absolutePath &&
102
+ session.root === file.root &&
103
+ session.details &&
104
+ !session.child.killed &&
105
+ session.child.exitCode == null
106
+ ) {
107
+ return session;
108
+ }
109
+ }
110
+ return null;
82
111
  }
83
112
 
84
113
  async function stop(sessionId) {
@@ -416,7 +416,7 @@ export const LOCAL_TOOL_DEFINITIONS = [
416
416
  name: 'html2pptx_open_local_slide_editor',
417
417
  title: 'Open Local Slide Editor',
418
418
  description:
419
- 'Local stdio MCP only. Start the same localhost edit bridge used by `html2pptx edit` for a .html/.htm slide file inside the MCP server working directory, then return the tokenized editor URL. This does not publish or upload the HTML file.',
419
+ 'Local stdio MCP only. Start the html2pptx CLI local edit bridge for a .html/.htm slide file inside the MCP server working directory, then return the tokenized editor URL. This does not publish or upload the HTML file.',
420
420
  inputSchema: {
421
421
  type: 'object',
422
422
  properties: {
@@ -437,7 +437,11 @@ export const LOCAL_TOOL_DEFINITIONS = [
437
437
  },
438
438
  openBrowser: {
439
439
  type: 'boolean',
440
- description: 'When true (default), the CLI opens the editor in the browser. Set false to only return the URL.',
440
+ description: 'When true, open the editor in the browser. Defaults to false for MCP calls so agents can return one URL without spawning extra tabs.',
441
+ },
442
+ reuseExisting: {
443
+ type: 'boolean',
444
+ description: 'When true (default), reuse an active editor bridge for the same file instead of starting another bridge and browser tab.',
441
445
  },
442
446
  },
443
447
  required: ['filePath'],
@@ -595,7 +599,7 @@ function buildPromptMessages(name, args) {
595
599
  `3. Save the complete HTML document to a project-local .html file, preferably "${fileName}" or another clear path under html2pptx/.`,
596
600
  '4. Open only a loopback editor UI. First ensure the local editor app is running with node scripts/dev-studio.mjs; it will choose an available port and register the editor URL under .html2pptx/edit-slide/editor-server.json in the current project.',
597
601
  '5. Call html2pptx_open_local_slide_editor with { filePath: <path> }. Pass baseUrl only when the user supplied a specific loopback editor URL. Do not use https://html2pptx.app/edit-slide for local file editing.',
598
- '6. If that local editor tool is not available, use direct `html2pptx edit <path>` when the local editor app is running, or ask the user to install the local MCP with: npx -y -p https://html2pptx.app/downloads/html2pptx-local-mcp-1.1.16.tgz html2pptx-install-mcp claude.',
602
+ '6. If that local editor tool is not available, do not pretend the editor was opened. Return the saved HTML path and the local-UI CLI fallback command: npx --yes https://html2pptx.app/downloads/html2pptx-cli-0.4.0.tgz edit <path>. If the CLI reports that the editor is not registered, start node scripts/dev-studio.mjs first.',
599
603
  '7. Do not export PPTX from this prompt. In the editor, the export button should only show a prompt telling the user to ask Claude Code or another agent to use the html2pptx skills for PowerPoint export.',
600
604
  ].join('\n'),
601
605
  },
@@ -647,7 +651,18 @@ export function negotiateProtocol(requestedProtocol) {
647
651
  }
648
652
 
649
653
  export function getToolDefinitions(client = {}, { localOnly = false } = {}) {
650
- const tools = localOnly ? [] : [...TOOL_DEFINITIONS];
654
+ if (localOnly) {
655
+ const localTools = [];
656
+ if (typeof client.openLocalSlideEditor === 'function') {
657
+ localTools.push(LOCAL_TOOL_DEFINITIONS[0]);
658
+ }
659
+ if (typeof client.stopLocalSlideEditor === 'function') {
660
+ localTools.push(LOCAL_TOOL_DEFINITIONS[1]);
661
+ }
662
+ return localTools;
663
+ }
664
+
665
+ const tools = [...TOOL_DEFINITIONS];
651
666
  if (typeof client.openLocalSlideEditor === 'function') {
652
667
  tools.push(LOCAL_TOOL_DEFINITIONS[0]);
653
668
  }
@@ -660,12 +675,11 @@ export function getToolDefinitions(client = {}, { localOnly = false } = {}) {
660
675
  function buildServerInstructions(client = {}, { localOnly = false } = {}) {
661
676
  if (localOnly) {
662
677
  return [
663
- 'html2pptx local MCP opens local slide HTML in the loopback edit-slide visual editor.',
664
- '',
665
- 'This server intentionally exposes only local editor tools. Use the remote html2pptx MCP server at https://html2pptx.app/mcp for PPTX exports, docs, usage, plans, templates, and publishing.',
666
- '',
667
- 'When the user asks to open or visually edit local slides, save the deck as a project-local .html/.htm file and call html2pptx_open_local_slide_editor with { filePath: <path> }.',
668
- 'The tool starts a localhost bridge and does not publish or upload the HTML file.',
678
+ 'html2pptx local MCP opens local slide HTML files in the no-code edit-slide editor.',
679
+ 'Use this stdio server only for local visual editing. Use the remote html2pptx MCP server for PPTX export, docs, usage, templates, and publishing workflows.',
680
+ 'When the user asks to open, preview, no-code edit, visually edit, or launch an editing screen for generated slides, save the deck as a local .html/.htm file first, usually under html2pptx/<name>.html.',
681
+ 'Then call html2pptx_open_local_slide_editor with the project-relative file path.',
682
+ 'The tool starts the html2pptx CLI localhost bridge and does not publish or upload the HTML file.',
669
683
  ].join('\n');
670
684
  }
671
685
 
@@ -715,11 +729,7 @@ function buildServerInstructions(client = {}, { localOnly = false } = {}) {
715
729
  return lines.join('\n');
716
730
  }
717
731
 
718
- export async function executeTool(name, args, client, { sendNotification, progressToken, localOnly = false } = {}) {
719
- if (localOnly && !LOCAL_TOOL_DEFINITIONS.some((tool) => tool.name === name)) {
720
- throw new Error(`${name} is available from the remote html2pptx MCP server, not the local stdio MCP server.`);
721
- }
722
-
732
+ export async function executeTool(name, args, client, { sendNotification, progressToken } = {}) {
723
733
  switch (name) {
724
734
  case 'html2pptx_list_export_plans': {
725
735
  const data = await client.listExportPlans();
@@ -902,7 +912,8 @@ export async function executeTool(name, args, client, { sendNotification, progre
902
912
  filePath: args.filePath,
903
913
  baseUrl: typeof args.baseUrl === 'string' ? args.baseUrl : undefined,
904
914
  port: Number.isFinite(args.port) ? args.port : undefined,
905
- openBrowser: args.openBrowser !== false,
915
+ openBrowser: args.openBrowser === true,
916
+ reuseExisting: args.reuseExisting !== false,
906
917
  });
907
918
  return buildToolResponse(renderLocalSlideEditorText(data), data);
908
919
  }
@@ -919,7 +930,7 @@ export async function executeTool(name, args, client, { sendNotification, progre
919
930
  }
920
931
  }
921
932
 
922
- export async function handleMcpMessage(message, { protocolVersion = DEFAULT_PROTOCOL, client, sendNotification, localOnly = false, serverInfo = SERVER_INFO }) {
933
+ export async function handleMcpMessage(message, { protocolVersion = DEFAULT_PROTOCOL, client, sendNotification, serverInfo = SERVER_INFO, localOnly = false }) {
923
934
  if (!message || typeof message !== 'object') {
924
935
  return { protocolVersion, response: null };
925
936
  }
@@ -1151,7 +1162,7 @@ export async function handleMcpMessage(message, { protocolVersion = DEFAULT_PROT
1151
1162
  try {
1152
1163
  const toolArgs = params?.arguments ?? {};
1153
1164
  const progressToken = toolArgs._meta?.progressToken ?? params?._meta?.progressToken;
1154
- const payload = await executeTool(params?.name, toolArgs, client, { sendNotification, progressToken, localOnly });
1165
+ const payload = await executeTool(params?.name, toolArgs, client, { sendNotification, progressToken });
1155
1166
  return {
1156
1167
  protocolVersion,
1157
1168
  response: {
@@ -1438,7 +1449,7 @@ function renderAnimationCatalogText(catalog) {
1438
1449
 
1439
1450
  function renderLocalSlideEditorText(data) {
1440
1451
  const lines = [
1441
- '# Local slide editor started',
1452
+ data.reused ? '# Local slide editor reused' : '# Local slide editor started',
1442
1453
  '',
1443
1454
  `File: ${data.file || 'unknown'}`,
1444
1455
  `Bridge: ${data.bridgeUrl || 'unknown'}`,
@@ -13,7 +13,7 @@ import {
13
13
 
14
14
  let inputBuffer = Buffer.alloc(0);
15
15
  let negotiatedProtocol = DEFAULT_PROTOCOL;
16
- let outputFraming = 'lines';
16
+ let wireMode = 'content-length';
17
17
 
18
18
  process.stdin.on('data', (chunk) => {
19
19
  inputBuffer = Buffer.concat([inputBuffer, chunk]);
@@ -36,16 +36,22 @@ process.stdout.on('error', (error) => {
36
36
  function drainMessages() {
37
37
  while (true) {
38
38
  const headerEnd = inputBuffer.indexOf('\r\n\r\n');
39
- if (headerEnd === -1) {
40
- const newlineEnd = inputBuffer.indexOf('\n');
41
- if (newlineEnd === -1) return;
42
- const rawLine = inputBuffer.slice(0, newlineEnd).toString('utf8').trim();
43
- inputBuffer = inputBuffer.slice(newlineEnd + 1);
39
+ const lineEnd = inputBuffer.indexOf('\n');
40
+
41
+ if (headerEnd === -1 || (lineEnd !== -1 && lineEnd < headerEnd)) {
42
+ if (lineEnd === -1) return;
43
+
44
+ const rawLine = inputBuffer.slice(0, lineEnd).toString('utf8').trim();
45
+ inputBuffer = inputBuffer.slice(lineEnd + 1);
44
46
  if (!rawLine) continue;
47
+
48
+ wireMode = 'line';
45
49
  handleRawMessage(rawLine);
46
50
  continue;
47
51
  }
48
52
 
53
+ wireMode = 'content-length';
54
+
49
55
  const headerText = inputBuffer.slice(0, headerEnd).toString('utf8');
50
56
  const contentLengthMatch = headerText.match(/Content-Length:\s*(\d+)/i);
51
57
  if (!contentLengthMatch) {
@@ -60,7 +66,6 @@ function drainMessages() {
60
66
 
61
67
  const rawBody = inputBuffer.slice(headerEnd + 4, messageEnd).toString('utf8');
62
68
  inputBuffer = inputBuffer.slice(messageEnd);
63
- outputFraming = 'headers';
64
69
 
65
70
  handleRawMessage(rawBody);
66
71
  }
@@ -85,6 +90,35 @@ function handleRawMessage(rawBody) {
85
90
 
86
91
  async function handleMessage(message) {
87
92
  const client = {
93
+ listExportPlans: async () => requestJson('/api/export/plans'),
94
+ createExportJob: async (body) =>
95
+ requestJson('/api/export/jobs', {
96
+ method: 'POST',
97
+ requireApiKey: true,
98
+ body,
99
+ }),
100
+ getExportJob: async (jobId) =>
101
+ requestJson(`/api/export/jobs/${encodeURIComponent(jobId)}`, {
102
+ requireApiKey: true,
103
+ }),
104
+ getUsage: async () =>
105
+ requestJson('/api/export/usage', {
106
+ requireApiKey: true,
107
+ }),
108
+ listTemplates: async (category) => {
109
+ const url = category
110
+ ? `/api/templates?category=${encodeURIComponent(category)}`
111
+ : '/api/templates';
112
+ // requireApiKey so the API is treated as authenticated and returns
113
+ // template prompts (anonymous requests get hasPrompt boolean only).
114
+ return requestJson(url, { requireApiKey: true });
115
+ },
116
+ getTemplateHtml: async (templateId) =>
117
+ requestJson(`/api/templates/html?id=${encodeURIComponent(templateId)}`, {
118
+ // /api/templates/html now requires auth (browser session, API key,
119
+ // or WorkOS JWT) so anonymous curl/browser can't scrape prompts.
120
+ requireApiKey: true,
121
+ }),
88
122
  openLocalSlideEditor: async (args) => localSlideEditorManager.open(args),
89
123
  stopLocalSlideEditor: async (sessionId) => localSlideEditorManager.stop(sessionId),
90
124
  };
@@ -92,11 +126,11 @@ async function handleMessage(message) {
92
126
  const handled = await handleMcpMessage(message, {
93
127
  protocolVersion: negotiatedProtocol,
94
128
  client,
95
- localOnly: true,
96
129
  serverInfo: {
97
130
  ...SERVER_INFO,
98
131
  name: 'html2pptx-local',
99
132
  },
133
+ localOnly: true,
100
134
  sendNotification: (notification) => {
101
135
  sendMessage(notification);
102
136
  },
@@ -182,13 +216,14 @@ function sendError(id, code, message) {
182
216
 
183
217
  function sendMessage(message) {
184
218
  const json = JSON.stringify(message);
185
- if (outputFraming === 'headers') {
186
- const headers = `Content-Length: ${Buffer.byteLength(json, 'utf8')}\r\nContent-Type: application/json\r\n\r\n`;
187
- process.stdout.write(headers);
188
- process.stdout.write(json);
219
+ if (wireMode === 'line') {
220
+ process.stdout.write(`${json}\n`);
189
221
  return;
190
222
  }
191
- process.stdout.write(`${json}\n`);
223
+
224
+ const headers = `Content-Length: ${Buffer.byteLength(json, 'utf8')}\r\n\r\n`;
225
+ process.stdout.write(headers);
226
+ process.stdout.write(json);
192
227
  }
193
228
 
194
229
  function logError(context, error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "html2pptx-local-mcp",
3
- "version": "1.1.17",
3
+ "version": "1.1.19",
4
4
  "type": "module",
5
5
  "description": "Local stdio MCP server for opening html2pptx slide HTML in the local edit-slide editor.",
6
6
  "bin": {
@@ -1,316 +1,188 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { spawnSync } from 'node:child_process';
4
- import { chmodSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
5
- import { mkdirSync } from 'node:fs';
6
- import { homedir } from 'node:os';
7
- import { dirname, join } from 'node:path';
8
- import process from 'node:process';
9
- import packageJson from '../package.json' with { type: 'json' };
10
-
11
- const VERSION = packageJson.version;
12
- const PACKAGE_SPEC = 'html2pptx-local-mcp@latest';
13
- const VERSIONED_PACKAGE_SPEC = `html2pptx-local-mcp@${VERSION}`;
14
- const PACKAGE_URL = 'https://html2pptx.app/downloads/html2pptx-local-mcp-latest.tgz';
15
- const VERSIONED_PACKAGE_URL = `https://html2pptx.app/downloads/html2pptx-local-mcp-${VERSION}.tgz`;
16
- const MANIFEST_URL = 'https://html2pptx.app/downloads/latest.json';
17
- const REMOTE_MCP_URL = 'https://html2pptx.app/mcp';
18
- const LOCAL_SERVER_NAME = 'html2pptx-local';
19
- const REMOTE_SERVER_NAME = 'html2pptx';
20
- const LAUNCHER_PATH = join(homedir(), '.html2pptx', 'bin', 'html2pptx-local-mcp-launcher.mjs');
21
- const INSTALL_ROOT = join(homedir(), '.html2pptx', 'local-mcp', VERSION);
22
- const LOCAL_PACKAGE_ROOT = join(INSTALL_ROOT, 'node_modules', 'html2pptx-local-mcp');
23
- const LOCAL_SERVER_ENTRY = join(LOCAL_PACKAGE_ROOT, 'mcp', 'pptx-studio-mcp-server.mjs');
24
-
25
- if (isMain()) {
26
- main();
27
- }
28
-
29
- function main() {
30
- const target = process.argv[2] || 'claude';
31
-
32
- if (!['claude'].includes(target)) {
33
- fail(`Unsupported target "${target}". Usage: html2pptx-install-mcp claude`);
34
- }
35
-
36
- console.log(`Installing html2pptx MCP for ${target}.`);
37
- console.log(`Remote server: ${REMOTE_SERVER_NAME} -> ${REMOTE_MCP_URL}`);
38
- console.log(`Local server: ${LOCAL_SERVER_NAME} -> ${PACKAGE_SPEC}`);
39
- console.log(`Local launcher: ${LAUNCHER_PATH}`);
40
- console.log('');
4
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
5
+ import { join } from 'node:path';
41
6
 
42
- installClaude();
7
+ const REMOTE_SERVER_NAME = 'html2pptx';
8
+ const LOCAL_SERVER_NAME = 'html2pptx-local';
9
+ const REMOTE_MCP_URL = 'https://html2pptx.app/mcp';
10
+ const LOCAL_PACKAGE_SPEC = process.env.HTML2PPTX_LOCAL_MCP_PACKAGE_SPEC || 'html2pptx-local-mcp@latest';
43
11
 
44
- console.log('');
45
- console.log('html2pptx MCP setup finished.');
46
- console.log('Restart Claude Code if it was already running.');
47
- }
12
+ const args = process.argv.slice(2);
13
+ const dryRun = args.includes('--dry-run');
14
+ const help = args.includes('--help') || args.includes('-h');
15
+ const client = resolveClient(args);
48
16
 
49
- function isMain() {
50
- return import.meta.url === new URL(process.argv[1], 'file:').href;
17
+ if (help) {
18
+ printHelp();
19
+ process.exit(0);
51
20
  }
52
21
 
53
- function installClaude() {
54
- runClaudeRemoteAdd();
55
- installLocalPackage();
56
- writeLocalLauncher();
57
- writeClaudeLocalConfig();
22
+ if (!client) {
23
+ console.error('Unknown MCP client. Use one of: claude, codex.');
24
+ printHelp();
25
+ process.exit(1);
58
26
  }
59
27
 
60
- function runClaudeRemoteAdd() {
61
- const args = [
62
- 'mcp',
63
- 'add',
64
- '--scope',
65
- 'user',
66
- '--transport',
67
- 'http',
68
- REMOTE_SERVER_NAME,
69
- REMOTE_MCP_URL,
70
- ];
71
- console.log(`> claude ${args.join(' ')}`);
72
- const result = spawnSync(commandForPlatform('claude'), args, { encoding: 'utf8' });
73
-
74
- if (result.status === 0) {
75
- printOutput(result);
76
- return;
77
- }
78
-
79
- const combined = `${result.stdout || ''}\n${result.stderr || ''}`;
80
- if (/already exists|already registered|duplicate/i.test(combined)) {
81
- console.log('claude reported that this MCP server is already registered; continuing.');
82
- printOutput(result);
83
- return;
84
- }
28
+ const steps = buildSteps(client);
85
29
 
86
- printOutput(result);
87
- fail('Failed to register the remote html2pptx MCP server with Claude Code.');
88
- }
30
+ console.log(`Installing html2pptx MCP for ${client}.`);
31
+ console.log(`Remote server: ${REMOTE_SERVER_NAME} -> ${REMOTE_MCP_URL}`);
32
+ console.log(`Local server: ${LOCAL_SERVER_NAME} -> npx -y -p ${LOCAL_PACKAGE_SPEC} html2pptx-mcp`);
33
+ console.log('');
89
34
 
90
- function installLocalPackage() {
91
- mkdirSync(INSTALL_ROOT, { recursive: true });
92
- const args = ['install', '--silent', '--prefix', INSTALL_ROOT, VERSIONED_PACKAGE_SPEC];
93
- console.log(`> npm ${args.join(' ')}`);
94
- const result = spawnSync(commandForPlatform('npm'), args, { stdio: 'inherit' });
95
- if (result.status !== 0) {
96
- fail(`Failed to install ${PACKAGE_URL}.`);
97
- }
98
- if (!existsSync(LOCAL_SERVER_ENTRY)) {
99
- fail(`Installed package is missing ${LOCAL_SERVER_ENTRY}.`);
35
+ for (const step of steps) {
36
+ if (step.kind === 'claude-local-config') {
37
+ writeClaudeLocalMcpConfig();
38
+ } else {
39
+ runStep(step);
100
40
  }
101
41
  }
102
42
 
103
- function writeClaudeLocalConfig() {
104
- const configPath = join(homedir(), '.claude.json');
105
- const config = readJsonObject(configPath);
106
- config.mcpServers = isPlainObject(config.mcpServers) ? config.mcpServers : {};
107
- config.mcpServers[LOCAL_SERVER_NAME] = {
108
- type: 'stdio',
109
- command: process.execPath,
110
- args: [LAUNCHER_PATH],
111
- env: {},
112
- };
113
-
114
- mkdirSync(dirname(configPath), { recursive: true });
115
- writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
116
- console.log(`Updated ${configPath}`);
117
- }
43
+ console.log('');
44
+ console.log('html2pptx MCP setup finished.');
118
45
 
119
- function writeLocalLauncher() {
120
- mkdirSync(dirname(LAUNCHER_PATH), { recursive: true });
121
- writeFileSync(LAUNCHER_PATH, launcherSource(), 'utf8');
122
- chmodSync(LAUNCHER_PATH, 0o755);
123
- console.log(`Updated ${LAUNCHER_PATH}`);
124
- }
46
+ function resolveClient(argv) {
47
+ const clientFlagIndex = argv.findIndex((value) => value === '--client');
48
+ if (clientFlagIndex !== -1) {
49
+ return normalizeClient(argv[clientFlagIndex + 1]);
50
+ }
125
51
 
126
- function readJsonObject(path) {
127
- if (!existsSync(path)) return {};
128
- const raw = readFileSync(path, 'utf8').trim();
129
- if (!raw) return {};
130
- try {
131
- const parsed = JSON.parse(raw);
132
- return isPlainObject(parsed) ? parsed : {};
133
- } catch (error) {
134
- fail(`Could not parse ${path}: ${error.message}`);
52
+ const clientEquals = argv.find((value) => value.startsWith('--client='));
53
+ if (clientEquals) {
54
+ return normalizeClient(clientEquals.slice('--client='.length));
135
55
  }
136
- }
137
56
 
138
- function isPlainObject(value) {
139
- return value !== null && typeof value === 'object' && !Array.isArray(value);
57
+ const positional = argv.find((value) => !value.startsWith('-'));
58
+ return normalizeClient(positional || 'claude');
140
59
  }
141
60
 
142
- function printOutput(result) {
143
- if (result.stdout) process.stdout.write(result.stdout);
144
- if (result.stderr) process.stderr.write(result.stderr);
61
+ function normalizeClient(value) {
62
+ if (value === 'claude' || value === 'claude-code') return 'claude';
63
+ if (value === 'codex') return 'codex';
64
+ return null;
145
65
  }
146
66
 
147
- function fail(message) {
148
- console.error(`error: ${message}`);
149
- process.exit(1);
150
- }
67
+ function buildSteps(targetClient) {
68
+ if (targetClient === 'claude') {
69
+ return [
70
+ {
71
+ command: 'claude',
72
+ args: ['mcp', 'add', '--scope', 'user', '--transport', 'http', REMOTE_SERVER_NAME, REMOTE_MCP_URL],
73
+ },
74
+ {
75
+ kind: 'claude-local-config',
76
+ },
77
+ ];
78
+ }
151
79
 
152
- function commandForPlatform(command) {
153
- return process.platform === 'win32' ? `${command}.cmd` : command;
80
+ return [
81
+ {
82
+ command: 'codex',
83
+ args: ['mcp', 'add', REMOTE_SERVER_NAME, '--url', REMOTE_MCP_URL],
84
+ },
85
+ {
86
+ command: 'codex',
87
+ args: [
88
+ 'mcp',
89
+ 'add',
90
+ LOCAL_SERVER_NAME,
91
+ '--',
92
+ 'npx',
93
+ '-y',
94
+ '-p',
95
+ LOCAL_PACKAGE_SPEC,
96
+ 'html2pptx-mcp',
97
+ ],
98
+ },
99
+ ];
154
100
  }
155
101
 
156
- function launcherSource() {
157
- return `#!/usr/bin/env node
102
+ function writeClaudeLocalMcpConfig() {
103
+ const configPath = join(process.env.HOME || process.cwd(), '.claude.json');
104
+ const serverConfig = {
105
+ type: 'stdio',
106
+ command: 'npx',
107
+ args: ['-y', '-p', LOCAL_PACKAGE_SPEC, 'html2pptx-mcp'],
108
+ env: {},
109
+ };
158
110
 
159
- import { spawn, spawnSync } from 'node:child_process';
160
- import { existsSync, readdirSync, readFileSync } from 'node:fs';
161
- import { mkdirSync } from 'node:fs';
162
- import { homedir } from 'node:os';
163
- import { join } from 'node:path';
164
- import process from 'node:process';
165
-
166
- const FALLBACK_VERSION = ${JSON.stringify(VERSION)};
167
- const FALLBACK_PACKAGE_SPEC = ${JSON.stringify(VERSIONED_PACKAGE_SPEC)};
168
- const FALLBACK_PACKAGE_URL = ${JSON.stringify(VERSIONED_PACKAGE_URL)};
169
- const FALLBACK_LATEST_PACKAGE_URL = ${JSON.stringify(PACKAGE_URL)};
170
- const MANIFEST_URL = process.env.HTML2PPTX_LOCAL_MCP_MANIFEST_URL || ${JSON.stringify(MANIFEST_URL)};
171
- const INSTALL_BASE = join(homedir(), '.html2pptx', 'local-mcp');
172
- const PACKAGE_NAME = 'html2pptx-local-mcp';
173
-
174
- main().catch((error) => {
175
- log('launcher failed: ' + (error?.message || String(error)));
176
- process.exit(1);
177
- });
111
+ console.log(`> update ${configPath} mcpServers.${LOCAL_SERVER_NAME}`);
178
112
 
179
- async function main() {
180
- const manifest = await fetchManifest();
181
- const desired = normalizeManifest(manifest) || {
182
- version: FALLBACK_VERSION,
183
- packageSpec: FALLBACK_PACKAGE_SPEC || FALLBACK_PACKAGE_URL || FALLBACK_LATEST_PACKAGE_URL,
184
- };
113
+ if (dryRun) return;
185
114
 
186
- let entry = serverEntryForVersion(desired.version);
187
- if (!existsSync(entry)) {
188
- const installed = installVersion(desired.version, desired.packageSpec);
189
- if (installed) {
190
- entry = serverEntryForVersion(desired.version);
115
+ let config = {};
116
+ if (existsSync(configPath)) {
117
+ try {
118
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
119
+ } catch (error) {
120
+ console.error(`Failed to parse ${configPath}: ${error.message}`);
121
+ process.exit(1);
191
122
  }
192
123
  }
193
124
 
194
- if (!existsSync(entry)) {
195
- entry = findNewestInstalledServer();
125
+ if (!config || typeof config !== 'object' || Array.isArray(config)) {
126
+ config = {};
196
127
  }
197
128
 
198
- if (!entry) {
199
- throw new Error('No installed html2pptx local MCP server found.');
129
+ if (!config.mcpServers || typeof config.mcpServers !== 'object' || Array.isArray(config.mcpServers)) {
130
+ config.mcpServers = {};
200
131
  }
201
132
 
202
- const child = spawn(process.execPath, [entry, ...process.argv.slice(2)], {
203
- stdio: 'inherit',
204
- env: process.env,
205
- });
206
-
207
- child.on('exit', (code, signal) => {
208
- if (signal) {
209
- process.kill(process.pid, signal);
210
- return;
211
- }
212
- process.exit(code ?? 0);
213
- });
133
+ config.mcpServers[LOCAL_SERVER_NAME] = serverConfig;
134
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
135
+ console.log(`Registered local MCP server ${LOCAL_SERVER_NAME} in Claude user config.`);
214
136
  }
215
137
 
216
- async function fetchManifest() {
217
- try {
218
- const response = await fetch('https://registry.npmjs.org/' + PACKAGE_NAME + '/latest', {
219
- headers: { accept: 'application/json' },
220
- signal: AbortSignal.timeout(2500),
221
- });
222
- if (response.ok) {
223
- const payload = await response.json();
224
- if (payload && typeof payload.version === 'string' && payload.version.trim()) {
225
- return {
226
- version: payload.version.trim(),
227
- packageSpec: PACKAGE_NAME + '@' + payload.version.trim(),
228
- };
229
- }
230
- }
231
- } catch {
232
- // Fall back to html2pptx.app manifest.
233
- }
138
+ function runStep(step) {
139
+ const rendered = [step.command, ...step.args].join(' ');
140
+ console.log(`> ${rendered}`);
234
141
 
235
- try {
236
- const response = await fetch(MANIFEST_URL, {
237
- headers: { accept: 'application/json' },
238
- signal: AbortSignal.timeout(2500),
239
- });
240
- if (!response.ok) return null;
241
- return await response.json();
242
- } catch {
243
- return null;
244
- }
245
- }
246
-
247
- function normalizeManifest(value) {
248
- if (!value || typeof value !== 'object') return null;
249
- const version = typeof value.version === 'string' ? value.version.trim() : '';
250
- const packageSpec =
251
- typeof value.packageSpec === 'string' ? value.packageSpec.trim()
252
- : typeof value.packageUrl === 'string' ? value.packageUrl.trim()
253
- : typeof value.latestPackageUrl === 'string' ? value.latestPackageUrl.trim()
254
- : '';
255
- if (!version || !packageSpec) return null;
256
- if (
257
- !packageSpec.startsWith(PACKAGE_NAME + '@') &&
258
- !/^https:\\/\\/html2pptx\\.app\\/downloads\\//.test(packageSpec)
259
- ) return null;
260
- return { version, packageSpec };
261
- }
142
+ if (dryRun) return;
262
143
 
263
- function installVersion(version, packageSpec) {
264
- mkdirSync(join(INSTALL_BASE, version), { recursive: true });
265
- log('installing html2pptx local MCP ' + version);
266
- const result = spawnSync(commandForPlatform('npm'), [
267
- 'install',
268
- '--silent',
269
- '--prefix',
270
- join(INSTALL_BASE, version),
271
- packageSpec,
272
- ], {
144
+ const result = spawnSync(step.command, step.args, {
273
145
  encoding: 'utf8',
146
+ stdio: ['ignore', 'pipe', 'pipe'],
274
147
  });
275
- if (result.stdout) process.stderr.write(result.stdout);
276
- if (result.stderr) process.stderr.write(result.stderr);
277
- return result.status === 0 && existsSync(serverEntryForVersion(version));
278
- }
279
148
 
280
- function serverEntryForVersion(version) {
281
- return join(INSTALL_BASE, version, 'node_modules', PACKAGE_NAME, 'mcp', 'pptx-studio-mcp-server.mjs');
282
- }
149
+ const output = [result.stdout, result.stderr].filter(Boolean).join('\n').trim();
283
150
 
284
- function findNewestInstalledServer() {
285
- let versions = [];
286
- try {
287
- versions = readdirSync(INSTALL_BASE, { withFileTypes: true })
288
- .filter((entry) => entry.isDirectory())
289
- .map((entry) => entry.name)
290
- .filter((version) => existsSync(serverEntryForVersion(version)));
291
- } catch {
292
- return '';
151
+ if (result.error?.code === 'ENOENT') {
152
+ console.error(`Command not found: ${step.command}`);
153
+ process.exit(1);
293
154
  }
294
- versions.sort(compareVersions).reverse();
295
- return versions.length ? serverEntryForVersion(versions[0]) : '';
296
- }
297
155
 
298
- function compareVersions(a, b) {
299
- const aa = String(a).split('.').map((part) => Number.parseInt(part, 10) || 0);
300
- const bb = String(b).split('.').map((part) => Number.parseInt(part, 10) || 0);
301
- for (let i = 0; i < Math.max(aa.length, bb.length); i += 1) {
302
- const diff = (aa[i] || 0) - (bb[i] || 0);
303
- if (diff) return diff;
156
+ if (result.status !== 0) {
157
+ if (isAlreadyRegistered(output)) {
158
+ console.log(`${step.command} reported that this MCP server is already registered; continuing.`);
159
+ if (output) console.log(output);
160
+ return;
161
+ }
162
+
163
+ if (output) console.error(output);
164
+ process.exit(result.status || 1);
304
165
  }
305
- return String(a).localeCompare(String(b));
306
- }
307
166
 
308
- function commandForPlatform(command) {
309
- return process.platform === 'win32' ? command + '.cmd' : command;
167
+ if (output) console.log(output);
310
168
  }
311
169
 
312
- function log(message) {
313
- process.stderr.write('[html2pptx-local-mcp] ' + message + '\\n');
170
+ function isAlreadyRegistered(output) {
171
+ return /already\s+(exists|registered|configured)|duplicate|server.*exists|同名|既に|すでに/i.test(output || '');
314
172
  }
315
- `;
173
+
174
+ function printHelp() {
175
+ console.log(`
176
+ Usage:
177
+ html2pptx-install-mcp [claude|codex] [--dry-run]
178
+ html2pptx-install-mcp --client claude
179
+ html2pptx-install-mcp --client codex
180
+
181
+ Examples:
182
+ npx -y -p html2pptx-local-mcp@latest html2pptx-install-mcp claude
183
+ npx -y -p html2pptx-local-mcp@latest html2pptx-install-mcp codex
184
+
185
+ Optional:
186
+ HTML2PPTX_LOCAL_MCP_PACKAGE_SPEC=<package-or-tarball> html2pptx-install-mcp claude
187
+ `.trim());
316
188
  }