html2pptx-local-mcp 1.1.19 → 1.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/docs/content.js +57 -23
- package/cli/dist/commands/edit.d.ts +1 -1
- package/cli/dist/commands/edit.js +231 -3
- package/cli/dist/index.js +0 -0
- package/lib/local-editor-server.js +316 -0
- package/lib/local-editor-state.js +45 -0
- package/lib/local-slide-editor-launcher.js +19 -18
- package/lib/pptx-studio-mcp-core.js +15 -9
- package/local-editor-app/app/api/edit-slide/local-health/route.js +16 -0
- package/local-editor-app/app/edit-slide/edit-slide-client.jsx +13153 -0
- package/local-editor-app/app/edit-slide/page.jsx +13 -0
- package/local-editor-app/app/globals.css +4 -0
- package/local-editor-app/app/layout.jsx +14 -0
- package/local-editor-app/components/studio/edit-property-panel.jsx +1061 -0
- package/local-editor-app/lib/edit-panel-value-normalizer.js +97 -0
- package/local-editor-app/lib/edit-slide-editor-helpers.js +120 -0
- package/local-editor-app/lib/edit-slide-url-security.js +247 -0
- package/local-editor-app/next.config.mjs +31 -0
- package/local-editor-app/package.json +7 -0
- package/mcp/pptx-studio-mcp-server.mjs +1 -1
- package/package.json +16 -3
- package/public/skills/html2pptx/SKILL.md +635 -0
- package/public/skills/html2pptx/references/automation-contract.md +68 -0
- package/public/skills/html2pptx/references/input-contract.md +107 -0
- package/public/skills/html2pptx/references/japanese-slide-design.md +273 -0
- package/public/skills/html2pptx/references/rewrite-patterns.md +218 -0
- package/public/skills/icon-generator/SKILL.md +133 -0
- package/public/skills/open-slide/SKILL.md +160 -0
- package/public/skills/publish-template/SKILL.md +215 -0
- package/public/skills/register-template/SKILL.md +142 -0
- package/scripts/extract-html2pptx-comments.mjs +172 -0
- package/scripts/install-mcp.mjs +58 -13
- package/scripts/install-skills.mjs +82 -0
package/app/docs/content.js
CHANGED
|
@@ -179,14 +179,19 @@ npx skills add https://html2pptx.app -a cursor
|
|
|
179
179
|
# Windsurf
|
|
180
180
|
npx skills add https://html2pptx.app -a windsurf
|
|
181
181
|
|
|
182
|
+
# Grok Build
|
|
183
|
+
npx --yes --package html2pptx-local-mcp@latest html2pptx-install-skills grok
|
|
184
|
+
|
|
182
185
|
# Preview published skills without installing
|
|
183
186
|
npx skills add https://html2pptx.app --list
|
|
184
187
|
|
|
185
188
|
# More agents / multiple targets: use the interactive selector
|
|
186
189
|
npx skills add https://html2pptx.app
|
|
187
190
|
|
|
188
|
-
# Claude Code users: run
|
|
189
|
-
npx
|
|
191
|
+
# Claude Code / Codex / Grok Build users: run the matching one line for MCP.
|
|
192
|
+
npx --yes --package html2pptx-local-mcp@latest html2pptx-install-mcp claude
|
|
193
|
+
npx --yes --package html2pptx-local-mcp@latest html2pptx-install-mcp codex
|
|
194
|
+
npx --yes --package html2pptx-local-mcp@latest html2pptx-install-mcp grok`;
|
|
190
195
|
|
|
191
196
|
const SKILL_INSTALL_COMMAND_JA = `# 使うエージェントのコマンドを選択
|
|
192
197
|
# Claude Code
|
|
@@ -201,14 +206,19 @@ npx skills add https://html2pptx.app -a cursor
|
|
|
201
206
|
# Windsurf
|
|
202
207
|
npx skills add https://html2pptx.app -a windsurf
|
|
203
208
|
|
|
209
|
+
# Grok Build
|
|
210
|
+
npx --yes --package html2pptx-local-mcp@latest html2pptx-install-skills grok
|
|
211
|
+
|
|
204
212
|
# インストールせずに公開Skillを確認
|
|
205
213
|
npx skills add https://html2pptx.app --list
|
|
206
214
|
|
|
207
215
|
# その他のエージェント / 複数指定は対話形式で選択
|
|
208
216
|
npx skills add https://html2pptx.app
|
|
209
217
|
|
|
210
|
-
# Claude Code
|
|
211
|
-
npx
|
|
218
|
+
# Claude Code / Codex / Grok Build ユーザーは MCP 用の該当行を実行。
|
|
219
|
+
npx --yes --package html2pptx-local-mcp@latest html2pptx-install-mcp claude
|
|
220
|
+
npx --yes --package html2pptx-local-mcp@latest html2pptx-install-mcp codex
|
|
221
|
+
npx --yes --package html2pptx-local-mcp@latest html2pptx-install-mcp grok`;
|
|
212
222
|
|
|
213
223
|
const SKILL_INVOCATION_EXAMPLE = `# 例: Claude Code でスライド作成 → PPTX出力
|
|
214
224
|
|
|
@@ -243,8 +253,12 @@ const SKILL_AVAILABLE_LIST = [
|
|
|
243
253
|
},
|
|
244
254
|
];
|
|
245
255
|
|
|
246
|
-
const MCP_INSTALL_EXAMPLE = `# Claude Code
|
|
247
|
-
npx
|
|
256
|
+
const MCP_INSTALL_EXAMPLE = `# Claude Code / Codex / Grok Build ユーザーは該当する1行でOK
|
|
257
|
+
npx --yes --package html2pptx-local-mcp@latest html2pptx-install-mcp claude
|
|
258
|
+
npx --yes --package html2pptx-local-mcp@latest html2pptx-install-mcp codex
|
|
259
|
+
|
|
260
|
+
# Grok Build
|
|
261
|
+
npx --yes --package html2pptx-local-mcp@latest html2pptx-install-mcp grok`;
|
|
248
262
|
|
|
249
263
|
const MCP_CONFIG_EXAMPLE = `// 方法2: 設定ファイルに手動追加
|
|
250
264
|
|
|
@@ -256,7 +270,7 @@ const MCP_CONFIG_EXAMPLE = `// 方法2: 設定ファイルに手動追加
|
|
|
256
270
|
"mcpServers": {
|
|
257
271
|
"html2pptx": {
|
|
258
272
|
"command": "npx",
|
|
259
|
-
"args": ["
|
|
273
|
+
"args": ["--yes", "--package", "html2pptx-local-mcp@latest", "html2pptx-mcp"],
|
|
260
274
|
"env": {
|
|
261
275
|
"PPTX_STUDIO_API_KEY": "sk_live_xxxx",
|
|
262
276
|
"PPTX_STUDIO_BASE_URL": "http://127.0.0.1:<app-port>"
|
|
@@ -749,7 +763,7 @@ export const DOCS_COPY = {
|
|
|
749
763
|
skillsDescription:
|
|
750
764
|
'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
765
|
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
|
-
skillsCompatibility: 'Works with 18+ AI agents including Claude Code, Cursor, GitHub Copilot, Windsurf, Cline, Codex, and more. Use the skills CLI command for
|
|
766
|
+
skillsCompatibility: 'Works with 18+ AI agents including Claude Code, Grok Build, Cursor, GitHub Copilot, Windsurf, Cline, Codex, and more. Use the skills CLI command for Claude Code, Codex, Cursor, and Windsurf. For Grok Build, use the Grok-specific skills installer shown below.',
|
|
753
767
|
skillsWorkflowTitle: 'Workflow',
|
|
754
768
|
skillsWorkflow: [
|
|
755
769
|
'Agent receives a user request (e.g., "Create a deck from these meeting notes")',
|
|
@@ -813,7 +827,7 @@ html2pptx edit ./html2pptx/slides.html --no-open`,
|
|
|
813
827
|
step: '1',
|
|
814
828
|
title: 'Install MCP + Skill',
|
|
815
829
|
icons: ['claude-code', 'codex', 'cursor', 'windsurf'],
|
|
816
|
-
body: 'Choose the command for the agent you actually use: Claude Code, Codex, Cursor, or Windsurf. For other supported agents or multiple targets, use the interactive selector. Avoid --yes unless you intentionally want to install into every detected agent directory.',
|
|
830
|
+
body: 'Choose the command for the agent you actually use: Claude Code, Codex, Cursor, or Windsurf. For Grok Build, use the Grok-specific installer. For other supported agents or multiple targets, use the interactive selector. Avoid --yes unless you intentionally want to install into every detected agent directory.',
|
|
817
831
|
code: SKILL_INSTALL_COMMAND_EN,
|
|
818
832
|
},
|
|
819
833
|
{
|
|
@@ -850,7 +864,7 @@ echo 'HTML2PPTX_API_KEY=sk_live_xxxx' >> .env`,
|
|
|
850
864
|
mcpDescription:
|
|
851
865
|
'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.',
|
|
852
866
|
mcpSetupTitle: 'Installation & Setup',
|
|
853
|
-
mcpSetupLead: '
|
|
867
|
+
mcpSetupLead: 'Claude Code, Codex, and Grok Build 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.',
|
|
854
868
|
mcpEditorTabs: [
|
|
855
869
|
{
|
|
856
870
|
id: 'claude-code',
|
|
@@ -858,22 +872,32 @@ echo 'HTML2PPTX_API_KEY=sk_live_xxxx' >> .env`,
|
|
|
858
872
|
icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/claude/default.svg',
|
|
859
873
|
recommended: true,
|
|
860
874
|
steps: [
|
|
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
|
|
875
|
+
{ 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 --yes --package html2pptx-local-mcp@latest html2pptx-install-mcp claude` },
|
|
862
876
|
{ 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
|
|
877
|
+
{ 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 --yes --package html2pptx-local-mcp@latest html2pptx-install-mcp claude` },
|
|
864
878
|
],
|
|
865
879
|
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.',
|
|
866
880
|
},
|
|
881
|
+
{
|
|
882
|
+
id: 'grok-build',
|
|
883
|
+
label: 'Grok Build',
|
|
884
|
+
steps: [
|
|
885
|
+
{ title: 'Install MCP for Grok Build', body: 'Run the Grok installer. It registers remote export as `html2pptx` and local edit-slide as `html2pptx-local` through `grok mcp add`.', code: `npx --yes --package html2pptx-local-mcp@latest html2pptx-install-mcp grok` },
|
|
886
|
+
{ title: 'Install Skills for Grok Build', body: 'Install the published html2pptx skills into Grok Build’s native skills directory. This does not change MCP configuration.', code: `npx --yes --package html2pptx-local-mcp@latest html2pptx-install-skills grok` },
|
|
887
|
+
],
|
|
888
|
+
tip: 'Use `grok inspect` if you want to confirm which project skills and MCP servers Grok discovered.',
|
|
889
|
+
},
|
|
867
890
|
{
|
|
868
891
|
id: 'codex',
|
|
869
892
|
label: 'Codex',
|
|
870
893
|
icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/openai/light.svg',
|
|
871
894
|
steps: [
|
|
872
|
-
{ title: 'Run
|
|
895
|
+
{ title: 'Run one command (recommended)', body: 'This registers remote export as `html2pptx`, then adds local edit-slide as `html2pptx-local` using the published package.', code: `npx --yes --package html2pptx-local-mcp@latest html2pptx-install-mcp codex` },
|
|
873
896
|
{ 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
|
|
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}` },
|
|
897
|
+
{ 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 --yes --package html2pptx-local-mcp@latest html2pptx-mcp` },
|
|
898
|
+
{ title: 'Manual remote setup via codex.json', body: 'Alternatively, create or edit codex.json in your project root for the hosted remote server.', code: `{\n "mcpServers": {\n "html2pptx": {\n "type": "url",\n "url": "https://html2pptx.app/mcp"\n }\n }\n}` },
|
|
876
899
|
],
|
|
900
|
+
tip: 'The installer runs the two Codex MCP add commands in order and continues if either server is already registered.',
|
|
877
901
|
},
|
|
878
902
|
{
|
|
879
903
|
id: 'cursor',
|
|
@@ -1593,7 +1617,7 @@ echo 'HTML2PPTX_API_KEY=sk_live_xxxx' >> .env`,
|
|
|
1593
1617
|
skillsDescription:
|
|
1594
1618
|
'Skillsは、AIコーディングエージェントにドメイン固有の知識とワークフローを追加するパッケージ機能です。html2pptx.app のスキルをインストールすると、エージェントがスライド用HTMLの作成方法を理解し、PPTX変換契約に対する検証を行い、必要に応じてローカル Visual Editor を開き、remote MCP または local MCP のワークフローでエクスポートし、内蔵のremote MCP公開ワークフローでHTMLテンプレートdraftを作成できます。自然言語の指示だけで、本番品質のPowerPointファイルや作成者所有のHTML draftを生成できます。',
|
|
1595
1619
|
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設定を変更するため、エージェントは必ず事前に確認します。',
|
|
1596
|
-
skillsCompatibility: 'Claude Code、Cursor、GitHub Copilot、Windsurf、Cline、Codex 等 18以上のAI
|
|
1620
|
+
skillsCompatibility: 'Claude Code、Grok Build、Cursor、GitHub Copilot、Windsurf、Cline、Codex 等 18以上のAIエージェントに対応。Claude Code、Codex、Cursor、Windsurf は skills CLI コマンドを使います。Grok Build は下に表示している Grok 専用の skills installer を使います。',
|
|
1597
1621
|
skillsWorkflowTitle: 'ワークフロー',
|
|
1598
1622
|
skillsWorkflow: [
|
|
1599
1623
|
'エージェントがユーザーリクエストを受信(例: 「この会議メモからデッキを作成して」)',
|
|
@@ -1657,7 +1681,7 @@ html2pptx edit ./html2pptx/slides.html --no-open`,
|
|
|
1657
1681
|
step: '1',
|
|
1658
1682
|
title: 'MCP + スキルをインストール',
|
|
1659
1683
|
icons: ['claude-code', 'codex', 'cursor', 'windsurf'],
|
|
1660
|
-
body: 'Claude Code、Codex、Cursor、Windsurf
|
|
1684
|
+
body: 'Claude Code、Codex、Cursor、Windsurf のうち、実際に使うエージェントのコマンドを選んで実行してください。Grok Build は Grok 専用 installer を使います。その他の対応エージェントや複数指定の場合は対話形式で選択します。--yes は検出された全エージェントのディレクトリへ入るため、全対応が必要な場合以外は使わないでください。',
|
|
1661
1685
|
code: SKILL_INSTALL_COMMAND_JA,
|
|
1662
1686
|
},
|
|
1663
1687
|
{
|
|
@@ -1694,7 +1718,7 @@ echo 'HTML2PPTX_API_KEY=sk_live_xxxx' >> .env`,
|
|
|
1694
1718
|
mcpDescription:
|
|
1695
1719
|
'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 はテンプレート公開には使いません。',
|
|
1696
1720
|
mcpSetupTitle: 'インストール & セットアップ',
|
|
1697
|
-
mcpSetupLead: 'Claude Code
|
|
1721
|
+
mcpSetupLead: 'Claude Code、Codex、Grok Build は1コマンドで remote export と local edit-slide の両方を登録できます。remote MCP は hosted service 側の更新が反映され、local MCP は html2pptx-local-mcp@latest 経由で起動するため、次回起動時に新しい公開パッケージへ追従できます。',
|
|
1698
1722
|
mcpEditorTabs: [
|
|
1699
1723
|
{
|
|
1700
1724
|
id: 'claude-code',
|
|
@@ -1702,22 +1726,32 @@ echo 'HTML2PPTX_API_KEY=sk_live_xxxx' >> .env`,
|
|
|
1702
1726
|
icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/claude/default.svg',
|
|
1703
1727
|
recommended: true,
|
|
1704
1728
|
steps: [
|
|
1705
|
-
{ title: 'Claude Codeユーザーはこの1行でOK', body: '`html2pptx` として remote export を登録し、`html2pptx-local` として local edit-slide を Claude Code の user config に直接書き込みます。', code: `npx
|
|
1729
|
+
{ title: 'Claude Codeユーザーはこの1行でOK', body: '`html2pptx` として remote export を登録し、`html2pptx-local` として local edit-slide を Claude Code の user config に直接書き込みます。', code: `npx --yes --package html2pptx-local-mcp@latest html2pptx-install-mcp claude` },
|
|
1706
1730
|
{ 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
|
|
1731
|
+
{ title: 'local edit-slide 互換セットアップ', body: 'Claude Code の stdio 登録が不安定な場合も、このインストーラーが local server 設定を直接書き込みます。', code: `npx --yes --package html2pptx-local-mcp@latest html2pptx-install-mcp claude` },
|
|
1708
1732
|
],
|
|
1709
1733
|
tip: 'このインストーラーは remote MCP を user scope で登録し、互換性のため local stdio MCP は Claude Code の user config に直接書き込みます。',
|
|
1710
1734
|
},
|
|
1735
|
+
{
|
|
1736
|
+
id: 'grok-build',
|
|
1737
|
+
label: 'Grok Build',
|
|
1738
|
+
steps: [
|
|
1739
|
+
{ title: 'Grok Build 向けに MCP を追加', body: 'Grok installer を実行します。remote export は `html2pptx`、local edit-slide は `html2pptx-local` として `grok mcp add` で登録されます。', code: `npx --yes --package html2pptx-local-mcp@latest html2pptx-install-mcp grok` },
|
|
1740
|
+
{ title: 'Grok Build 向けに Skills を追加', body: '公開済みの html2pptx skills を Grok Build の native skills ディレクトリへ入れます。MCP設定は変更しません。', code: `npx --yes --package html2pptx-local-mcp@latest html2pptx-install-skills grok` },
|
|
1741
|
+
],
|
|
1742
|
+
tip: 'Grok が検出した project skills と MCP server を確認したい場合は `grok inspect` を使ってください。',
|
|
1743
|
+
},
|
|
1711
1744
|
{
|
|
1712
1745
|
id: 'codex',
|
|
1713
1746
|
label: 'Codex',
|
|
1714
1747
|
icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/openai/light.svg',
|
|
1715
1748
|
steps: [
|
|
1716
|
-
{ title: '
|
|
1749
|
+
{ title: '1コマンドで追加(推奨)', body: '`html2pptx` として remote export を登録し、`html2pptx-local` として local edit-slide を公開パッケージ経由で追加します。', code: `npx --yes --package html2pptx-local-mcp@latest html2pptx-install-mcp codex` },
|
|
1717
1750
|
{ 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
|
|
1719
|
-
{ title: 'codex.json
|
|
1751
|
+
{ title: 'edit-slide 用 local stdio MCP(任意)', body: 'Codex がローカル `.html` を edit-slide で開く必要がある場合だけ追加します。公開パッケージから起動するため、リポジトリの checkout は不要です。', code: `codex mcp add html2pptx-local -- npx --yes --package html2pptx-local-mcp@latest html2pptx-mcp` },
|
|
1752
|
+
{ title: 'codex.json で remote を手動セットアップ', body: 'コマンドが使えない場合は、hosted remote server 用にプロジェクトルートの codex.json に以下を追加してください。', code: `{\n "mcpServers": {\n "html2pptx": {\n "type": "url",\n "url": "https://html2pptx.app/mcp"\n }\n }\n}` },
|
|
1720
1753
|
],
|
|
1754
|
+
tip: 'インストーラーは Codex の2つの MCP add コマンドを順に実行し、すでに登録済みの場合は継続します。',
|
|
1721
1755
|
},
|
|
1722
1756
|
{
|
|
1723
1757
|
id: 'cursor',
|
|
@@ -18,7 +18,7 @@ declare function normalizeBaseUrl(raw: string): URL;
|
|
|
18
18
|
declare function readRegisteredEditorBaseUrl(root: string): Promise<string | null>;
|
|
19
19
|
declare function resolveEditorBaseUrl(root: string, explicitBaseUrl: string | undefined): Promise<URL>;
|
|
20
20
|
declare function buildEditorUrl(baseUrl: URL, rel: string, bridgeUrl: string, sessionToken: string): URL;
|
|
21
|
-
declare function createBridgeServer(ctx: BridgeContext): import("http").Server<typeof IncomingMessage, typeof ServerResponse>;
|
|
21
|
+
declare function createBridgeServer(ctx: BridgeContext): import("node:http").Server<typeof IncomingMessage, typeof ServerResponse>;
|
|
22
22
|
declare function listen(server: ReturnType<typeof createBridgeServer>, requestedPort: number): Promise<number>;
|
|
23
23
|
export declare function editCommand(input: string | undefined, options?: EditOptions): Promise<void>;
|
|
24
24
|
export declare const editCommandInternalsForTest: {
|
|
@@ -2,14 +2,46 @@ import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { createServer } from "node:http";
|
|
4
4
|
import { platform } from "node:os";
|
|
5
|
-
import { mkdir, readFile, realpath, stat, writeFile, } from "node:fs/promises";
|
|
5
|
+
import { mkdir, readdir, readFile, realpath, stat, writeFile, } from "node:fs/promises";
|
|
6
6
|
import { dirname, extname, join, relative, resolve, sep, } from "node:path";
|
|
7
7
|
import * as p from "@clack/prompts";
|
|
8
8
|
import pc from "picocolors";
|
|
9
9
|
const AUTO_PORT = 0;
|
|
10
10
|
const MAX_WRITE_BYTES = 5 * 1024 * 1024;
|
|
11
|
+
const MAX_ASSET_BYTES = 64 * 1024 * 1024;
|
|
11
12
|
const ALLOWED_EXTENSIONS = [".html", ".htm"];
|
|
12
13
|
const ALLOWED_EXT = new Set(ALLOWED_EXTENSIONS);
|
|
14
|
+
const ASSET_IMAGE_EXTENSIONS = [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".avif"];
|
|
15
|
+
const ASSET_VIDEO_EXTENSIONS = [".mp4", ".webm", ".mov", ".m4v", ".ogv"];
|
|
16
|
+
const ASSET_VIDEO_EXT = new Set(ASSET_VIDEO_EXTENSIONS);
|
|
17
|
+
const ASSET_MEDIA_EXT = new Set([...ASSET_IMAGE_EXTENSIONS, ...ASSET_VIDEO_EXTENSIONS]);
|
|
18
|
+
const ASSET_CONTENT_TYPES = {
|
|
19
|
+
".png": "image/png",
|
|
20
|
+
".jpg": "image/jpeg",
|
|
21
|
+
".jpeg": "image/jpeg",
|
|
22
|
+
".gif": "image/gif",
|
|
23
|
+
".webp": "image/webp",
|
|
24
|
+
".svg": "image/svg+xml",
|
|
25
|
+
".avif": "image/avif",
|
|
26
|
+
".mp4": "video/mp4",
|
|
27
|
+
".webm": "video/webm",
|
|
28
|
+
".mov": "video/quicktime",
|
|
29
|
+
".m4v": "video/x-m4v",
|
|
30
|
+
".ogv": "video/ogg",
|
|
31
|
+
};
|
|
32
|
+
const ASSET_CONTENT_TYPE_EXT = {
|
|
33
|
+
"image/png": ".png",
|
|
34
|
+
"image/jpeg": ".jpg",
|
|
35
|
+
"image/gif": ".gif",
|
|
36
|
+
"image/webp": ".webp",
|
|
37
|
+
"image/svg+xml": ".svg",
|
|
38
|
+
"image/avif": ".avif",
|
|
39
|
+
"video/mp4": ".mp4",
|
|
40
|
+
"video/webm": ".webm",
|
|
41
|
+
"video/quicktime": ".mov",
|
|
42
|
+
"video/x-m4v": ".m4v",
|
|
43
|
+
"video/ogg": ".ogv",
|
|
44
|
+
};
|
|
13
45
|
const DISALLOWED_TOP_DIRECTORIES = [
|
|
14
46
|
"public",
|
|
15
47
|
".next",
|
|
@@ -32,6 +64,9 @@ const EDITOR_BASE_URL_EXAMPLE = "http://localhost:<port>";
|
|
|
32
64
|
function sha256(content) {
|
|
33
65
|
return createHash("sha256").update(content).digest("hex");
|
|
34
66
|
}
|
|
67
|
+
function sha256Buffer(buf) {
|
|
68
|
+
return createHash("sha256").update(buf).digest("hex");
|
|
69
|
+
}
|
|
35
70
|
function generateSessionToken() {
|
|
36
71
|
return randomBytes(32).toString("base64url");
|
|
37
72
|
}
|
|
@@ -278,13 +313,13 @@ function validateWriteRequest(req, ctx, reqUrl) {
|
|
|
278
313
|
return (req.headers["x-edit-slide-local"] === "1" ||
|
|
279
314
|
req.headers["x-open-slide-local"] === "1");
|
|
280
315
|
}
|
|
281
|
-
async function readBody(req) {
|
|
316
|
+
async function readBody(req, maxBytes = MAX_WRITE_BYTES) {
|
|
282
317
|
const chunks = [];
|
|
283
318
|
let total = 0;
|
|
284
319
|
for await (const chunk of req) {
|
|
285
320
|
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
286
321
|
total += buf.length;
|
|
287
|
-
if (total >
|
|
322
|
+
if (total > maxBytes + 1024 * 1024) {
|
|
288
323
|
throw new Error("request body too large");
|
|
289
324
|
}
|
|
290
325
|
chunks.push(buf);
|
|
@@ -427,6 +462,187 @@ async function handlePost(ctx, req, res) {
|
|
|
427
462
|
sendJson(res, 400, { error: error?.message || "write failed" });
|
|
428
463
|
}
|
|
429
464
|
}
|
|
465
|
+
async function safeAssetPath(ctx, rel) {
|
|
466
|
+
if (typeof rel !== "string" || !rel) {
|
|
467
|
+
throw new Error("missing path");
|
|
468
|
+
}
|
|
469
|
+
const normalized = rel.replace(/^\/+/, "");
|
|
470
|
+
const abs = resolve(ctx.root, normalized);
|
|
471
|
+
if (abs !== ctx.root && !abs.startsWith(ctx.root + sep)) {
|
|
472
|
+
throw new Error("path escape");
|
|
473
|
+
}
|
|
474
|
+
const ext = extname(abs).toLowerCase();
|
|
475
|
+
if (!ASSET_MEDIA_EXT.has(ext)) {
|
|
476
|
+
throw new Error("only image, GIF, and video files are allowed");
|
|
477
|
+
}
|
|
478
|
+
const real = await resolveReal(abs);
|
|
479
|
+
if (real !== ctx.root && !real.startsWith(ctx.root + sep)) {
|
|
480
|
+
throw new Error("path escape via symlink");
|
|
481
|
+
}
|
|
482
|
+
for (const candidate of [relative(ctx.root, abs), relative(ctx.root, real)]) {
|
|
483
|
+
const first = candidate.split(sep)[0];
|
|
484
|
+
if (DISALLOWED_TOP_DIRS.has(first)) {
|
|
485
|
+
throw new Error(`assets under ${first}/ are not allowed`);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return real;
|
|
489
|
+
}
|
|
490
|
+
function assetExt(name, contentType) {
|
|
491
|
+
const fromName = extname(String(name || "")).toLowerCase();
|
|
492
|
+
if (ASSET_MEDIA_EXT.has(fromName))
|
|
493
|
+
return fromName;
|
|
494
|
+
return ASSET_CONTENT_TYPE_EXT[String(contentType || "").toLowerCase()] || "";
|
|
495
|
+
}
|
|
496
|
+
function assetSlug(name) {
|
|
497
|
+
const base = String(name || "asset").replace(/\.[^.]+$/, "");
|
|
498
|
+
const slug = base
|
|
499
|
+
.normalize("NFKD")
|
|
500
|
+
.replace(/[^\w.-]+/g, "-")
|
|
501
|
+
.replace(/^[-.]+|[-.]+$/g, "")
|
|
502
|
+
.toLowerCase();
|
|
503
|
+
return slug || "asset";
|
|
504
|
+
}
|
|
505
|
+
function assetKind(ext) {
|
|
506
|
+
return ASSET_VIDEO_EXT.has(ext) ? "video" : "image";
|
|
507
|
+
}
|
|
508
|
+
function assetDirRel(scope, htmlDirRel) {
|
|
509
|
+
if (scope === "global")
|
|
510
|
+
return "assets";
|
|
511
|
+
return htmlDirRel ? join(htmlDirRel, "assets") : "assets";
|
|
512
|
+
}
|
|
513
|
+
async function readBytesOrNull(abs) {
|
|
514
|
+
try {
|
|
515
|
+
return await readFile(abs);
|
|
516
|
+
}
|
|
517
|
+
catch (error) {
|
|
518
|
+
if (error?.code === "ENOENT")
|
|
519
|
+
return null;
|
|
520
|
+
throw error;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
function sendBinary(res, status, contentType, buf) {
|
|
524
|
+
res.statusCode = status;
|
|
525
|
+
res.setHeader("content-type", contentType);
|
|
526
|
+
res.setHeader("cache-control", "no-store");
|
|
527
|
+
res.setHeader("x-content-type-options", "nosniff");
|
|
528
|
+
res.setHeader("content-security-policy", "sandbox; default-src 'none'; style-src 'unsafe-inline'");
|
|
529
|
+
res.end(buf);
|
|
530
|
+
}
|
|
531
|
+
async function handleAssetGet(ctx, req, reqUrl, res) {
|
|
532
|
+
if (!validateBridgeRequest(req, ctx, reqUrl)) {
|
|
533
|
+
sendJson(res, 403, { error: "forbidden" });
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
const file = reqUrl.searchParams.get("file") || "";
|
|
537
|
+
const htmlDirRel = file ? dirname(file) : "";
|
|
538
|
+
if (reqUrl.searchParams.get("list") === "1") {
|
|
539
|
+
const scope = reqUrl.searchParams.get("scope") === "global" ? "global" : "project";
|
|
540
|
+
const dirRel = assetDirRel(scope, htmlDirRel);
|
|
541
|
+
const absDir = resolve(ctx.root, dirRel);
|
|
542
|
+
const htmlDirAbs = resolve(ctx.root, htmlDirRel || ".");
|
|
543
|
+
let assets = [];
|
|
544
|
+
try {
|
|
545
|
+
if (absDir === ctx.root || absDir.startsWith(ctx.root + sep)) {
|
|
546
|
+
const names = await readdir(absDir);
|
|
547
|
+
assets = names
|
|
548
|
+
.filter((name) => ASSET_MEDIA_EXT.has(extname(name).toLowerCase()))
|
|
549
|
+
.map((name) => ({
|
|
550
|
+
name,
|
|
551
|
+
src: toPosixPath(relative(htmlDirAbs, join(absDir, name))),
|
|
552
|
+
kind: assetKind(extname(name).toLowerCase()),
|
|
553
|
+
}));
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
catch {
|
|
557
|
+
assets = [];
|
|
558
|
+
}
|
|
559
|
+
sendJson(res, 200, { assets, policy: buildPolicy(ctx) });
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
const src = reqUrl.searchParams.get("src") || "";
|
|
563
|
+
if (!src) {
|
|
564
|
+
sendJson(res, 400, { error: "missing src" });
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
try {
|
|
568
|
+
const abs = await safeAssetPath(ctx, join(htmlDirRel, src));
|
|
569
|
+
const buf = await readFile(abs);
|
|
570
|
+
const ext = extname(abs).toLowerCase();
|
|
571
|
+
sendBinary(res, 200, ASSET_CONTENT_TYPES[ext] || "application/octet-stream", buf);
|
|
572
|
+
}
|
|
573
|
+
catch (error) {
|
|
574
|
+
const message = error?.message || "read failed";
|
|
575
|
+
const status = message.includes("ENOENT") ? 404 : 400;
|
|
576
|
+
sendJson(res, status, { error: message });
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
async function handleAssetPost(ctx, req, res) {
|
|
580
|
+
const reqUrl = new URL(req.url || "/", `http://${req.headers.host || "127.0.0.1"}`);
|
|
581
|
+
if (!validateWriteRequest(req, ctx, reqUrl)) {
|
|
582
|
+
sendJson(res, 403, { error: "forbidden" });
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
let body;
|
|
586
|
+
try {
|
|
587
|
+
body = JSON.parse(await readBody(req, MAX_ASSET_BYTES * 2));
|
|
588
|
+
}
|
|
589
|
+
catch (error) {
|
|
590
|
+
sendJson(res, 400, { error: error?.message || "invalid json" });
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
const { file, scope: rawScope, name, contentType, dataBase64 } = body || {};
|
|
594
|
+
if (typeof dataBase64 !== "string" || !dataBase64) {
|
|
595
|
+
sendJson(res, 400, { error: "missing asset data" });
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
const buf = Buffer.from(dataBase64, "base64");
|
|
599
|
+
if (!buf.length) {
|
|
600
|
+
sendJson(res, 400, { error: "empty asset data" });
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
if (buf.length > MAX_ASSET_BYTES) {
|
|
604
|
+
sendJson(res, 413, { error: "asset too large (>64MB)" });
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
const ext = assetExt(name, contentType);
|
|
608
|
+
if (!ASSET_MEDIA_EXT.has(ext)) {
|
|
609
|
+
sendJson(res, 400, { error: "unsupported asset type" });
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
const scope = rawScope === "global" ? "global" : "project";
|
|
613
|
+
const htmlDirRel = typeof file === "string" && file ? dirname(file) : "";
|
|
614
|
+
const htmlDirAbs = resolve(ctx.root, htmlDirRel || ".");
|
|
615
|
+
const dirRel = assetDirRel(scope, htmlDirRel);
|
|
616
|
+
const slug = assetSlug(name);
|
|
617
|
+
try {
|
|
618
|
+
let fileName = `${slug}${ext}`;
|
|
619
|
+
let absTarget = await safeAssetPath(ctx, join(dirRel, fileName));
|
|
620
|
+
const existing = await readBytesOrNull(absTarget);
|
|
621
|
+
if (existing && !existing.equals(buf)) {
|
|
622
|
+
fileName = `${slug}-${sha256Buffer(buf).slice(0, 8)}${ext}`;
|
|
623
|
+
absTarget = await safeAssetPath(ctx, join(dirRel, fileName));
|
|
624
|
+
}
|
|
625
|
+
const alreadyIdentical = Boolean(existing && existing.equals(buf));
|
|
626
|
+
if (!alreadyIdentical) {
|
|
627
|
+
await mkdir(dirname(absTarget), { recursive: true });
|
|
628
|
+
await writeFile(absTarget, buf);
|
|
629
|
+
}
|
|
630
|
+
sendJson(res, 200, {
|
|
631
|
+
ok: true,
|
|
632
|
+
scope,
|
|
633
|
+
name: fileName,
|
|
634
|
+
path: toPosixPath(relative(ctx.root, absTarget)),
|
|
635
|
+
src: toPosixPath(relative(htmlDirAbs, absTarget)),
|
|
636
|
+
kind: assetKind(ext),
|
|
637
|
+
bytes: buf.length,
|
|
638
|
+
reused: alreadyIdentical,
|
|
639
|
+
policy: buildPolicy(ctx),
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
catch (error) {
|
|
643
|
+
sendJson(res, 400, { error: error?.message || "asset write failed" });
|
|
644
|
+
}
|
|
645
|
+
}
|
|
430
646
|
function createBridgeServer(ctx) {
|
|
431
647
|
return createServer(async (req, res) => {
|
|
432
648
|
if (!applyCors(req, res, ctx.editorOrigin))
|
|
@@ -445,6 +661,18 @@ function createBridgeServer(ctx) {
|
|
|
445
661
|
sendJson(res, 200, { ok: true, service: "html2pptx edit bridge", root: ctx.root });
|
|
446
662
|
return;
|
|
447
663
|
}
|
|
664
|
+
if (reqUrl.pathname === "/api/edit-slide/asset") {
|
|
665
|
+
if (req.method === "GET") {
|
|
666
|
+
await handleAssetGet(ctx, req, reqUrl, res);
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
if (req.method === "POST") {
|
|
670
|
+
await handleAssetPost(ctx, req, res);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
sendJson(res, 405, { error: "method not allowed" });
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
448
676
|
if (reqUrl.pathname !== "/api/edit-slide/file") {
|
|
449
677
|
sendJson(res, 404, { error: "not found" });
|
|
450
678
|
return;
|
package/cli/dist/index.js
CHANGED
|
File without changes
|