html2pptx-local-mcp 1.1.22 → 1.1.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/app/docs/content.js +116 -2
  2. package/cli/dist/commands/config-show.d.ts +1 -0
  3. package/cli/dist/commands/config-show.js +16 -0
  4. package/cli/dist/commands/convert.d.ts +10 -0
  5. package/cli/dist/commands/convert.js +311 -0
  6. package/cli/dist/commands/edit.d.ts +33 -0
  7. package/cli/dist/commands/edit.js +818 -0
  8. package/cli/dist/commands/init.d.ts +1 -0
  9. package/cli/dist/commands/init.js +35 -0
  10. package/cli/dist/commands/logout.d.ts +1 -0
  11. package/cli/dist/commands/logout.js +19 -0
  12. package/cli/dist/commands/publish.d.ts +10 -0
  13. package/cli/dist/commands/publish.js +17 -0
  14. package/cli/dist/commands/status.d.ts +5 -0
  15. package/cli/dist/commands/status.js +71 -0
  16. package/cli/dist/commands/templates.d.ts +13 -0
  17. package/cli/dist/commands/templates.js +85 -0
  18. package/cli/dist/commands/whoami.d.ts +5 -0
  19. package/cli/dist/commands/whoami.js +51 -0
  20. package/cli/dist/config.d.ts +7 -0
  21. package/cli/dist/config.js +24 -0
  22. package/cli/dist/dist/commands/config-show.d.ts +1 -0
  23. package/cli/dist/dist/commands/config-show.js +16 -0
  24. package/cli/dist/dist/commands/convert.d.ts +10 -0
  25. package/cli/dist/dist/commands/convert.js +311 -0
  26. package/cli/dist/dist/commands/edit.d.ts +34 -0
  27. package/cli/dist/dist/commands/edit.js +801 -0
  28. package/cli/dist/dist/commands/init.d.ts +1 -0
  29. package/cli/dist/dist/commands/init.js +35 -0
  30. package/cli/dist/dist/commands/logout.d.ts +1 -0
  31. package/cli/dist/dist/commands/logout.js +19 -0
  32. package/cli/dist/dist/commands/publish.d.ts +10 -0
  33. package/cli/dist/dist/commands/publish.js +17 -0
  34. package/cli/dist/dist/commands/status.d.ts +5 -0
  35. package/cli/dist/dist/commands/status.js +71 -0
  36. package/cli/dist/dist/commands/templates.d.ts +13 -0
  37. package/cli/dist/dist/commands/templates.js +85 -0
  38. package/cli/dist/dist/commands/whoami.d.ts +5 -0
  39. package/cli/dist/dist/commands/whoami.js +51 -0
  40. package/cli/dist/dist/config.d.ts +7 -0
  41. package/cli/dist/dist/config.js +24 -0
  42. package/cli/dist/dist/index.d.ts +2 -0
  43. package/cli/dist/dist/index.js +93 -0
  44. package/cli/dist/dist/update-check.d.ts +1 -0
  45. package/cli/dist/dist/update-check.js +30 -0
  46. package/cli/dist/index.d.ts +2 -0
  47. package/cli/dist/index.js +93 -0
  48. package/cli/dist/update-check.d.ts +1 -0
  49. package/cli/dist/update-check.js +30 -0
  50. package/package.json +1 -1
  51. package/scripts/install-mcp.mjs +5 -1
@@ -822,6 +822,61 @@ html2pptx edit ./html2pptx/slides.html --no-open`,
822
822
  { 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.' },
823
823
  ],
824
824
  skillsSetupTitle: 'Setup',
825
+ skillsSetupLead: 'Pick the agent you actually use and run the matching command. The skills CLI handles Claude Code, Codex, Cursor, and Windsurf; Grok Build uses its own installer.',
826
+ skillsEditorTabs: [
827
+ {
828
+ id: 'claude-code',
829
+ label: 'Claude Code',
830
+ icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/claude/default.svg',
831
+ recommended: true,
832
+ steps: [
833
+ { title: 'Install the html2pptx skill', body: 'Register the published html2pptx skills into Claude Code via the skills CLI.', code: `npx skills add https://html2pptx.app -a claude-code` },
834
+ { title: 'Preview without installing', body: 'List the skills the CLI is about to add before committing to them.', code: `npx skills add https://html2pptx.app --list` },
835
+ ],
836
+ tip: 'Prefer the per-agent flag over `--yes`. `--yes` installs into every detected agent directory at once.',
837
+ },
838
+ {
839
+ id: 'grok-build',
840
+ label: 'Grok Build',
841
+ icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/grok/light.svg',
842
+ steps: [
843
+ { title: 'Install Skills for Grok Build', body: 'Grok Build uses its own installer because skills land in Grok’s native skills directory rather than via the generic skills CLI.', code: `npx --yes --package html2pptx-local-mcp@latest html2pptx-install-skills grok` },
844
+ ],
845
+ tip: 'Run `grok inspect` to confirm which skills Grok Build discovered.',
846
+ },
847
+ {
848
+ id: 'codex',
849
+ label: 'Codex',
850
+ icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/openai/light.svg',
851
+ steps: [
852
+ { title: 'Install the html2pptx skill', body: 'Register the published html2pptx skills into Codex via the skills CLI.', code: `npx skills add https://html2pptx.app -a codex` },
853
+ ],
854
+ },
855
+ {
856
+ id: 'cursor',
857
+ label: 'Cursor',
858
+ icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/cursor/light.svg',
859
+ steps: [
860
+ { title: 'Install the html2pptx skill', body: 'Register the published html2pptx skills into Cursor via the skills CLI.', code: `npx skills add https://html2pptx.app -a cursor` },
861
+ ],
862
+ },
863
+ {
864
+ id: 'windsurf',
865
+ label: 'Windsurf',
866
+ icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/windsurf/default.svg',
867
+ steps: [
868
+ { title: 'Install the html2pptx skill', body: 'Register the published html2pptx skills into Windsurf via the skills CLI.', code: `npx skills add https://html2pptx.app -a windsurf` },
869
+ ],
870
+ },
871
+ {
872
+ id: 'others',
873
+ label: 'Other agents',
874
+ steps: [
875
+ { title: 'Use the interactive selector', body: 'For any other supported agent, or to install into multiple targets at once, run the CLI without `-a` and choose interactively.', code: `npx skills add https://html2pptx.app` },
876
+ ],
877
+ tip: 'Avoid `--yes` unless you intentionally want to install into every detected agent directory.',
878
+ },
879
+ ],
825
880
  skillsSetupSteps: [
826
881
  {
827
882
  step: '1',
@@ -885,8 +940,9 @@ echo 'HTML2PPTX_API_KEY=sk_live_xxxx' >> .env`,
885
940
  steps: [
886
941
  { 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` },
887
942
  { 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` },
943
+ { title: 'Manual remote setup', body: 'If you prefer to register the remote MCP yourself (or the installer is unavailable), run this directly. `--type http` forces the streamable-http transport so Grok does not auto-pick SSE.', code: `grok mcp add html2pptx --url https://html2pptx.app/mcp --type http` },
888
944
  ],
889
- tip: 'Use `grok inspect` if you want to confirm which project skills and MCP servers Grok discovered.',
945
+ tip: 'Use `grok inspect` to confirm which project skills and MCP servers Grok discovered. OAuth via WorkOS AuthKit completes in the browser on first use. If your account has not previously been seen on html2pptx.app, sign in to https://html2pptx.app once with the same Google / WorkOS identity so a user record is provisioned before launching Grok.',
890
946
  },
891
947
  {
892
948
  id: 'codex',
@@ -928,6 +984,7 @@ echo 'HTML2PPTX_API_KEY=sk_live_xxxx' >> .env`,
928
984
  {
929
985
  id: 'cline',
930
986
  label: 'Cline',
987
+ icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/cline/default.svg',
931
988
  steps: [
932
989
  { title: 'Add via Cline sidebar', body: 'Open the Cline sidebar > Configure tab > "Configure MCP Servers" and add the following.', code: `{\n "mcpServers": {\n "html2pptx": {\n "url": "https://html2pptx.app/mcp",\n "type": "streamableHttp"\n }\n }\n}` },
933
990
  ],
@@ -1677,6 +1734,61 @@ html2pptx edit ./html2pptx/slides.html --no-open`,
1677
1734
  { issue: '別タブが読み取り専用になる', fix: '同じHTMLを複数タブで編集して競合しないよう、エディタはタブロックを使います。編集権限のあるタブを使うか、画面上の移譲操作を行ってください。' },
1678
1735
  ],
1679
1736
  skillsSetupTitle: 'セットアップ',
1737
+ skillsSetupLead: '実際に使うエージェントのタブを開き、そのコマンドだけ実行してください。skills CLI は Claude Code / Codex / Cursor / Windsurf に対応し、Grok Build のみ Grok 専用 installer を使います。',
1738
+ skillsEditorTabs: [
1739
+ {
1740
+ id: 'claude-code',
1741
+ label: 'Claude Code',
1742
+ icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/claude/default.svg',
1743
+ recommended: true,
1744
+ steps: [
1745
+ { title: 'html2pptx Skill をインストール', body: '公開済みの html2pptx skills を skills CLI から Claude Code に登録します。', code: `npx skills add https://html2pptx.app -a claude-code` },
1746
+ { title: 'インストールせずに一覧確認', body: '実際に追加される skills を事前に確認したい場合はこのコマンドを使います。', code: `npx skills add https://html2pptx.app --list` },
1747
+ ],
1748
+ tip: '`--yes` は検出された全エージェントのディレクトリへ入るため、エージェントを明示する `-a` フラグを優先してください。',
1749
+ },
1750
+ {
1751
+ id: 'grok-build',
1752
+ label: 'Grok Build',
1753
+ icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/grok/light.svg',
1754
+ steps: [
1755
+ { title: 'Grok Build に Skills を追加', body: 'Grok Build の skills は Grok のネイティブ skills ディレクトリに置く必要があるため、汎用 skills CLI ではなく Grok 専用 installer を使います。', code: `npx --yes --package html2pptx-local-mcp@latest html2pptx-install-skills grok` },
1756
+ ],
1757
+ tip: 'Grok Build が認識した skills を確認したい場合は `grok inspect` を実行してください。',
1758
+ },
1759
+ {
1760
+ id: 'codex',
1761
+ label: 'Codex',
1762
+ icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/openai/light.svg',
1763
+ steps: [
1764
+ { title: 'html2pptx Skill をインストール', body: '公開済みの html2pptx skills を skills CLI から Codex に登録します。', code: `npx skills add https://html2pptx.app -a codex` },
1765
+ ],
1766
+ },
1767
+ {
1768
+ id: 'cursor',
1769
+ label: 'Cursor',
1770
+ icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/cursor/light.svg',
1771
+ steps: [
1772
+ { title: 'html2pptx Skill をインストール', body: '公開済みの html2pptx skills を skills CLI から Cursor に登録します。', code: `npx skills add https://html2pptx.app -a cursor` },
1773
+ ],
1774
+ },
1775
+ {
1776
+ id: 'windsurf',
1777
+ label: 'Windsurf',
1778
+ icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/windsurf/default.svg',
1779
+ steps: [
1780
+ { title: 'html2pptx Skill をインストール', body: '公開済みの html2pptx skills を skills CLI から Windsurf に登録します。', code: `npx skills add https://html2pptx.app -a windsurf` },
1781
+ ],
1782
+ },
1783
+ {
1784
+ id: 'others',
1785
+ label: 'その他のエージェント',
1786
+ steps: [
1787
+ { title: '対話形式で選択', body: 'その他の対応エージェントや、複数のエージェントへ一度にインストールする場合は `-a` を付けずに対話形式で選択します。', code: `npx skills add https://html2pptx.app` },
1788
+ ],
1789
+ tip: '`--yes` は検出された全エージェントのディレクトリへ入るため、全対応が必要な場合以外は使わないでください。',
1790
+ },
1791
+ ],
1680
1792
  skillsSetupSteps: [
1681
1793
  {
1682
1794
  step: '1',
@@ -1740,8 +1852,9 @@ echo 'HTML2PPTX_API_KEY=sk_live_xxxx' >> .env`,
1740
1852
  steps: [
1741
1853
  { 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` },
1742
1854
  { title: 'Grok Build 向けに Skills を追加', body: '公開済みの html2pptx skills を Grok Build の native skills ディレクトリへ入れます。MCP設定は変更しません。', code: `npx --yes --package html2pptx-local-mcp@latest html2pptx-install-skills grok` },
1855
+ { title: 'remote だけ手動セットアップ', body: 'インストーラを使わず remote MCP を自分で登録する場合はこちら。`--type http` を明示することで Grok が SSE を自動選択するのを防ぎます。', code: `grok mcp add html2pptx --url https://html2pptx.app/mcp --type http` },
1743
1856
  ],
1744
- tip: 'Grok が検出した project skills と MCP server を確認したい場合は `grok inspect` を使ってください。',
1857
+ tip: 'Grok が検出した project skills と MCP server を確認したい場合は `grok inspect` を使ってください。OAuth は初回ブラウザで WorkOS AuthKit にログインします。同じ Google / WorkOS アカウントで https://html2pptx.app に一度サインインしておくと、Grok 起動時にユーザレコードが解決できる状態になります。',
1745
1858
  },
1746
1859
  {
1747
1860
  id: 'codex',
@@ -1783,6 +1896,7 @@ echo 'HTML2PPTX_API_KEY=sk_live_xxxx' >> .env`,
1783
1896
  {
1784
1897
  id: 'cline',
1785
1898
  label: 'Cline',
1899
+ icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/cline/default.svg',
1786
1900
  steps: [
1787
1901
  { title: 'Cline サイドバーから追加', body: 'Cline サイドバー > Configure タブ > 「Configure MCP Servers」を開き、以下を追加してください。', code: `{\n "mcpServers": {\n "html2pptx": {\n "url": "https://html2pptx.app/mcp",\n "type": "streamableHttp"\n }\n }\n}` },
1788
1902
  ],
@@ -0,0 +1 @@
1
+ export declare function configShowCommand(): Promise<void>;
@@ -0,0 +1,16 @@
1
+ import * as p from "@clack/prompts";
2
+ import pc from "picocolors";
3
+ import { loadConfig, getConfigPath } from "../config.js";
4
+ export async function configShowCommand() {
5
+ const config = await loadConfig();
6
+ const configPath = getConfigPath();
7
+ p.log.info(pc.bold("Config file: ") + pc.dim(configPath));
8
+ if (!config.apiKey) {
9
+ p.log.warn("No configuration found. Run " + pc.cyan("html2pptx login") + " to set up.");
10
+ return;
11
+ }
12
+ p.log.info(pc.bold("API Key: ") +
13
+ pc.dim(config.apiKey.slice(0, 12) + "..." + config.apiKey.slice(-4)));
14
+ p.log.info(pc.bold("API Base URL: ") +
15
+ (config.baseUrl ?? "https://html2pptx.app") + pc.dim(" (default)"));
16
+ }
@@ -0,0 +1,10 @@
1
+ interface ConvertOptions {
2
+ output?: string;
3
+ size?: string;
4
+ css?: string;
5
+ json?: boolean;
6
+ open?: boolean;
7
+ baseUrl?: string;
8
+ }
9
+ export declare function convertCommand(input?: string, options?: ConvertOptions): Promise<void>;
10
+ export {};
@@ -0,0 +1,311 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import { exec } from "node:child_process";
3
+ import { platform } from "node:os";
4
+ import { basename, resolve } from "node:path";
5
+ import * as p from "@clack/prompts";
6
+ import pc from "picocolors";
7
+ import { loadConfig } from "../config.js";
8
+ function parseSize(size) {
9
+ if (size === "16:9")
10
+ return { layout: "LAYOUT_16x9" };
11
+ if (size === "4:3")
12
+ return { layout: "LAYOUT_4x3" };
13
+ const match = size.match(/^(\d+)x(\d+)$/);
14
+ if (match)
15
+ return { width: parseInt(match[1]), height: parseInt(match[2]) };
16
+ return { layout: "LAYOUT_16x9" };
17
+ }
18
+ function formatDuration(ms) {
19
+ if (ms < 1000)
20
+ return `${ms}ms`;
21
+ return `${(ms / 1000).toFixed(1)}s`;
22
+ }
23
+ function openFile(filePath) {
24
+ const os = platform();
25
+ const cmd = os === "darwin" ? "open" : os === "win32" ? "start" : "xdg-open";
26
+ exec(`${cmd} "${filePath}"`);
27
+ }
28
+ async function interactivePrompt() {
29
+ p.intro(pc.bgCyan(pc.black(" html2pptx convert ")));
30
+ const result = await p.group({
31
+ input: () => p.text({
32
+ message: "HTML file to convert",
33
+ placeholder: "./slides.html",
34
+ validate(value) {
35
+ if (!value)
36
+ return "HTML file path is required";
37
+ if (!value.endsWith(".html") && !value.endsWith(".htm"))
38
+ return "File must be .html or .htm";
39
+ },
40
+ }),
41
+ size: () => p.select({
42
+ message: "Slide size",
43
+ options: [
44
+ { value: "16:9", label: "16:9 (1600x900)", hint: "default" },
45
+ { value: "4:3", label: "4:3 (1024x768)" },
46
+ { value: "custom", label: "Custom size" },
47
+ ],
48
+ }),
49
+ customSize: ({ results }) => {
50
+ if (results.size !== "custom")
51
+ return;
52
+ return p.text({
53
+ message: "Enter custom size (WxH)",
54
+ placeholder: "1920x1080",
55
+ validate(value) {
56
+ if (!value?.match(/^\d+x\d+$/))
57
+ return 'Format: WIDTHxHEIGHT (e.g. 1920x1080)';
58
+ },
59
+ });
60
+ },
61
+ css: () => p.text({
62
+ message: "External CSS file (optional)",
63
+ placeholder: "Press Enter to skip",
64
+ }),
65
+ output: ({ results }) => {
66
+ const defaultName = basename(results.input).replace(/\.html?$/, "") + ".pptx";
67
+ return p.text({
68
+ message: "Output filename",
69
+ placeholder: defaultName,
70
+ initialValue: defaultName,
71
+ });
72
+ },
73
+ }, {
74
+ onCancel() {
75
+ p.cancel("Conversion cancelled.");
76
+ process.exit(0);
77
+ },
78
+ });
79
+ return {
80
+ input: result.input,
81
+ output: result.output,
82
+ size: result.customSize ?? result.size,
83
+ css: result.css || undefined,
84
+ };
85
+ }
86
+ async function runExport(html, css, size, fileName, baseUrl, apiKey) {
87
+ const sizeParams = parseSize(size);
88
+ const body = {
89
+ html,
90
+ fileName,
91
+ responseFormat: "url",
92
+ ...sizeParams,
93
+ };
94
+ if (css)
95
+ body.css = css;
96
+ const res = await fetch(`${baseUrl}/api/v1/export/jobs`, {
97
+ method: "POST",
98
+ headers: {
99
+ "Content-Type": "application/json",
100
+ Authorization: `Bearer ${apiKey}`,
101
+ },
102
+ body: JSON.stringify(body),
103
+ });
104
+ if (res.status === 401) {
105
+ throw new Error("Invalid API key. Run `html2pptx login` to update your key.");
106
+ }
107
+ if (res.status === 403) {
108
+ throw new Error("Access denied. Your API key may be expired or your plan may have changed. Visit https://html2pptx.app/dashboard to check.");
109
+ }
110
+ if (res.status === 413) {
111
+ throw new Error("HTML payload too large for your plan. Try reducing the HTML size or upgrading your plan.");
112
+ }
113
+ if (res.status === 429) {
114
+ throw new Error("Rate limit exceeded. Please wait a moment and try again, or upgrade your plan for higher limits.");
115
+ }
116
+ if (!res.ok) {
117
+ const text = await res.text();
118
+ throw new Error(`API error ${res.status}: ${text}`);
119
+ }
120
+ return (await res.json());
121
+ }
122
+ async function pollJob(statusUrl, apiKey, baseUrl) {
123
+ if (!statusUrl.startsWith(baseUrl)) {
124
+ throw new Error("Unexpected status URL from server");
125
+ }
126
+ const maxAttempts = 60;
127
+ for (let i = 0; i < maxAttempts; i++) {
128
+ await new Promise((r) => setTimeout(r, 2000));
129
+ const res = await fetch(statusUrl, {
130
+ headers: { Authorization: `Bearer ${apiKey}` },
131
+ });
132
+ if (!res.ok) {
133
+ const text = await res.text();
134
+ throw new Error(`Poll error ${res.status}: ${text}`);
135
+ }
136
+ const data = (await res.json());
137
+ if (data.status === "completed")
138
+ return data;
139
+ if (data.status === "failed")
140
+ throw new Error(data.error ?? "Export failed");
141
+ }
142
+ throw new Error("Export timed out after 2 minutes");
143
+ }
144
+ async function downloadFile(url, outputPath) {
145
+ const res = await fetch(url);
146
+ if (!res.ok)
147
+ throw new Error(`Download failed: ${res.status}`);
148
+ const buffer = Buffer.from(await res.arrayBuffer());
149
+ await writeFile(outputPath, buffer);
150
+ return buffer.byteLength;
151
+ }
152
+ function formatBytes(bytes) {
153
+ if (bytes < 1024)
154
+ return `${bytes} B`;
155
+ if (bytes < 1024 * 1024)
156
+ return `${(bytes / 1024).toFixed(1)} KB`;
157
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
158
+ }
159
+ export async function convertCommand(input, options = {}) {
160
+ const config = await loadConfig();
161
+ const apiKey = config.apiKey;
162
+ const baseUrl = options.baseUrl ?? config.baseUrl ?? "https://html2pptx.app";
163
+ const jsonMode = options.json ?? false;
164
+ if (!apiKey) {
165
+ if (jsonMode) {
166
+ console.log(JSON.stringify({ success: false, error: "No API key found. Run html2pptx login first." }));
167
+ process.exit(1);
168
+ }
169
+ p.log.error("No API key found. Run " + pc.cyan("html2pptx login") + " first.");
170
+ process.exit(1);
171
+ }
172
+ let htmlPath;
173
+ let outputFile;
174
+ let size;
175
+ let cssPath;
176
+ // If args provided -> direct mode, else -> interactive mode
177
+ if (input) {
178
+ htmlPath = resolve(input);
179
+ outputFile =
180
+ options.output ?? basename(htmlPath).replace(/\.html?$/, "") + ".pptx";
181
+ size = options.size ?? "16:9";
182
+ cssPath = options.css;
183
+ }
184
+ else {
185
+ const answers = await interactivePrompt();
186
+ htmlPath = resolve(answers.input);
187
+ outputFile = answers.output;
188
+ size = answers.size;
189
+ cssPath = answers.css;
190
+ }
191
+ const startTime = Date.now();
192
+ const spinner = jsonMode ? null : p.spinner();
193
+ spinner?.start("Reading HTML file...");
194
+ let html;
195
+ try {
196
+ html = await readFile(htmlPath, "utf-8");
197
+ }
198
+ catch {
199
+ if (jsonMode) {
200
+ console.log(JSON.stringify({ success: false, error: `Could not read file: ${htmlPath}` }));
201
+ }
202
+ else {
203
+ spinner?.stop("Failed");
204
+ p.log.error(`Could not read file: ${pc.dim(htmlPath)}`);
205
+ }
206
+ process.exit(1);
207
+ }
208
+ let css;
209
+ if (cssPath) {
210
+ try {
211
+ css = await readFile(resolve(cssPath), "utf-8");
212
+ }
213
+ catch {
214
+ if (jsonMode) {
215
+ console.log(JSON.stringify({ success: false, error: `Could not read CSS file: ${cssPath}` }));
216
+ }
217
+ else {
218
+ spinner?.stop("Failed");
219
+ p.log.error(`Could not read CSS file: ${pc.dim(cssPath)}`);
220
+ }
221
+ process.exit(1);
222
+ }
223
+ }
224
+ spinner?.message("Sending to html2pptx API...");
225
+ let job;
226
+ try {
227
+ job = await runExport(html, css, size, outputFile, baseUrl, apiKey);
228
+ }
229
+ catch (e) {
230
+ const errMsg = e.message;
231
+ if (jsonMode) {
232
+ console.log(JSON.stringify({ success: false, error: errMsg }));
233
+ }
234
+ else {
235
+ spinner?.stop("Failed");
236
+ p.log.error(errMsg);
237
+ }
238
+ process.exit(1);
239
+ }
240
+ const finishConversion = async (downloadUrl) => {
241
+ spinner?.message("Downloading PPTX...");
242
+ const fileSize = await downloadFile(downloadUrl, resolve(outputFile));
243
+ const duration = Date.now() - startTime;
244
+ if (jsonMode) {
245
+ const result = {
246
+ success: true,
247
+ file: outputFile,
248
+ size: formatBytes(fileSize),
249
+ duration: formatDuration(duration),
250
+ };
251
+ console.log(JSON.stringify(result));
252
+ }
253
+ else {
254
+ spinner?.stop(pc.green("Done!"));
255
+ p.log.success(`Saved to ${pc.cyan(outputFile)} ${pc.dim(`(${formatBytes(fileSize)}, ${formatDuration(duration)})`)}`);
256
+ // Next steps guidance
257
+ p.log.info(pc.dim("Next: ") +
258
+ `Open the file or run ${pc.cyan("html2pptx status")} to check your usage.`);
259
+ }
260
+ if (options.open) {
261
+ openFile(resolve(outputFile));
262
+ }
263
+ };
264
+ // If job completed immediately
265
+ if (job.status === "completed" && job.downloadUrl) {
266
+ try {
267
+ await finishConversion(job.downloadUrl);
268
+ return;
269
+ }
270
+ catch (e) {
271
+ const errMsg = e.message;
272
+ if (jsonMode) {
273
+ console.log(JSON.stringify({ success: false, error: errMsg }));
274
+ }
275
+ else {
276
+ spinner?.stop("Failed");
277
+ p.log.error(errMsg);
278
+ }
279
+ process.exit(1);
280
+ }
281
+ }
282
+ // Poll for completion
283
+ spinner?.message("Converting... this may take a moment");
284
+ try {
285
+ const result = await pollJob(job.statusUrl, apiKey, baseUrl);
286
+ if (result.downloadUrl) {
287
+ await finishConversion(result.downloadUrl);
288
+ }
289
+ else {
290
+ if (jsonMode) {
291
+ console.log(JSON.stringify({ success: false, error: "No download URL returned" }));
292
+ }
293
+ else {
294
+ spinner?.stop("Failed");
295
+ p.log.error("No download URL returned");
296
+ }
297
+ process.exit(1);
298
+ }
299
+ }
300
+ catch (e) {
301
+ const errMsg = e.message;
302
+ if (jsonMode) {
303
+ console.log(JSON.stringify({ success: false, error: errMsg }));
304
+ }
305
+ else {
306
+ spinner?.stop("Failed");
307
+ p.log.error(errMsg);
308
+ }
309
+ process.exit(1);
310
+ }
311
+ }
@@ -0,0 +1,33 @@
1
+ export interface EditOptions {
2
+ port?: string;
3
+ baseUrl?: string;
4
+ noOpen?: boolean;
5
+ open?: boolean;
6
+ json?: boolean;
7
+ }
8
+ interface BridgeContext {
9
+ root: string;
10
+ editorOrigin: string;
11
+ localStateDir: string;
12
+ sessionToken: string;
13
+ }
14
+ declare function generateSessionToken(): string;
15
+ declare function parsePort(value: string | undefined): number;
16
+ declare function normalizeBaseUrl(raw: string): URL;
17
+ declare function readRegisteredEditorBaseUrl(root: string): Promise<string | null>;
18
+ declare function resolveEditorBaseUrl(root: string, explicitBaseUrl: string | undefined): Promise<URL>;
19
+ declare function buildEditorUrl(baseUrl: URL, rel: string, bridgeUrl: string, sessionToken: string): URL;
20
+ declare function createBridgeServer(ctx: BridgeContext): any;
21
+ declare function listen(server: ReturnType<typeof createBridgeServer>, requestedPort: number): Promise<number>;
22
+ export declare function editCommand(input: string | undefined, options?: EditOptions): Promise<void>;
23
+ export declare const editCommandInternalsForTest: {
24
+ buildEditorUrl: typeof buildEditorUrl;
25
+ createBridgeServer: typeof createBridgeServer;
26
+ generateSessionToken: typeof generateSessionToken;
27
+ listen: typeof listen;
28
+ normalizeBaseUrl: typeof normalizeBaseUrl;
29
+ parsePort: typeof parsePort;
30
+ readRegisteredEditorBaseUrl: typeof readRegisteredEditorBaseUrl;
31
+ resolveEditorBaseUrl: typeof resolveEditorBaseUrl;
32
+ };
33
+ export {};