html2pptx-local-mcp 1.1.28 → 1.1.31

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.
@@ -763,7 +763,7 @@ export const DOCS_COPY = {
763
763
  skillsDescription:
764
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.',
765
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.',
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.',
766
+ skillsCompatibility: 'Works with 18+ AI agents including Claude Code, Codex, Cursor, VS Code (GitHub Copilot), Grok Build, Antigravity, and more. Use the skills CLI command for Claude Code, Codex, and Cursor. For Grok Build, use the Grok-specific skills installer shown below.',
767
767
  skillsWorkflowTitle: 'Workflow',
768
768
  skillsWorkflow: [
769
769
  'Agent receives a user request (e.g., "Create a deck from these meeting notes")',
@@ -881,8 +881,8 @@ html2pptx edit ./html2pptx/slides.html --no-open`,
881
881
  {
882
882
  step: '1',
883
883
  title: 'Install MCP + Skill',
884
- icons: ['claude-code', 'codex', 'cursor', 'windsurf'],
885
- 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.',
884
+ icons: ['claude-code', 'codex', 'cursor'],
885
+ body: 'Choose the command for the agent you actually use: Claude Code, Codex, or Cursor. 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.',
886
886
  code: SKILL_INSTALL_COMMAND_EN,
887
887
  },
888
888
  {
@@ -974,20 +974,13 @@ echo 'HTML2PPTX_API_KEY=sk_live_xxxx' >> .env`,
974
974
  ],
975
975
  },
976
976
  {
977
- id: 'windsurf',
978
- label: 'Windsurf',
979
- icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/windsurf/default.svg',
980
- steps: [
981
- { title: 'Add to mcp_config.json', body: 'Edit ~/.codeium/windsurf/mcp_config.json and add the following. Restart Windsurf after saving.', code: `{\n "mcpServers": {\n "html2pptx": {\n "serverUrl": "https://html2pptx.app/mcp"\n }\n }\n}` },
982
- ],
983
- },
984
- {
985
- id: 'cline',
986
- label: 'Cline',
987
- icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/cline/default.svg',
977
+ id: 'antigravity',
978
+ label: 'Antigravity',
979
+ icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/antigravity-google/default.svg',
988
980
  steps: [
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}` },
981
+ { title: 'Add to ~/.antigravity/mcp.json', body: 'Create or edit ~/.antigravity/mcp.json and add the following. Restart Antigravity after saving so the IDE picks up the new MCP server.', code: `{\n "mcpServers": {\n "html2pptx": {\n "url": "https://html2pptx.app/mcp"\n }\n }\n}` },
990
982
  ],
983
+ tip: 'Antigravity (Google) uses the standard MCP mcpServers shape — same JSON as Cursor.',
991
984
  },
992
985
  ],
993
986
  mcpToolsTitle: 'Available MCP Tools',
@@ -1675,7 +1668,7 @@ echo 'HTML2PPTX_API_KEY=sk_live_xxxx' >> .env`,
1675
1668
  skillsDescription:
1676
1669
  'Skillsは、AIコーディングエージェントにドメイン固有の知識とワークフローを追加するパッケージ機能です。html2pptx.app のスキルをインストールすると、エージェントがスライド用HTMLの作成方法を理解し、PPTX変換契約に対する検証を行い、必要に応じてローカル Visual Editor を開き、remote MCP または local MCP のワークフローでエクスポートし、内蔵のremote MCP公開ワークフローでHTMLテンプレートdraftを作成できます。自然言語の指示だけで、本番品質のPowerPointファイルや作成者所有のHTML draftを生成できます。',
1677
1670
  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設定を変更するため、エージェントは必ず事前に確認します。',
1678
- skillsCompatibility: 'Claude Code、Grok Build、Cursor、GitHub Copilot、WindsurfCline、Codex 等 18以上のAIエージェントに対応。Claude Code、Codex、Cursor、Windsurf は skills CLI コマンドを使います。Grok Build は下に表示している Grok 専用の skills installer を使います。',
1671
+ skillsCompatibility: 'Claude Code、Codex、Cursor、VS Code (GitHub Copilot)Grok BuildAntigravity 等 18以上のAIエージェントに対応。Claude Code、Codex、Cursor は skills CLI コマンドを使います。Grok Build は下に表示している Grok 専用の skills installer を使います。',
1679
1672
  skillsWorkflowTitle: 'ワークフロー',
1680
1673
  skillsWorkflow: [
1681
1674
  'エージェントがユーザーリクエストを受信(例: 「この会議メモからデッキを作成して」)',
@@ -1793,8 +1786,8 @@ html2pptx edit ./html2pptx/slides.html --no-open`,
1793
1786
  {
1794
1787
  step: '1',
1795
1788
  title: 'MCP + スキルをインストール',
1796
- icons: ['claude-code', 'codex', 'cursor', 'windsurf'],
1797
- body: 'Claude Code、Codex、Cursor、Windsurf のうち、実際に使うエージェントのコマンドを選んで実行してください。Grok Build は Grok 専用 installer を使います。その他の対応エージェントや複数指定の場合は対話形式で選択します。--yes は検出された全エージェントのディレクトリへ入るため、全対応が必要な場合以外は使わないでください。',
1789
+ icons: ['claude-code', 'codex', 'cursor'],
1790
+ body: 'Claude Code、Codex、Cursor のうち、実際に使うエージェントのコマンドを選んで実行してください。Grok Build は Grok 専用 installer を使います。その他の対応エージェントや複数指定の場合は対話形式で選択します。--yes は検出された全エージェントのディレクトリへ入るため、全対応が必要な場合以外は使わないでください。',
1798
1791
  code: SKILL_INSTALL_COMMAND_JA,
1799
1792
  },
1800
1793
  {
@@ -1886,20 +1879,13 @@ echo 'HTML2PPTX_API_KEY=sk_live_xxxx' >> .env`,
1886
1879
  ],
1887
1880
  },
1888
1881
  {
1889
- id: 'windsurf',
1890
- label: 'Windsurf',
1891
- icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/windsurf/default.svg',
1892
- steps: [
1893
- { title: 'mcp_config.json に追加', body: '~/.codeium/windsurf/mcp_config.json を編集し、以下を追加してください。保存後 Windsurf を再起動してください。', code: `{\n "mcpServers": {\n "html2pptx": {\n "serverUrl": "https://html2pptx.app/mcp"\n }\n }\n}` },
1894
- ],
1895
- },
1896
- {
1897
- id: 'cline',
1898
- label: 'Cline',
1899
- icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/cline/default.svg',
1882
+ id: 'antigravity',
1883
+ label: 'Antigravity',
1884
+ icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/antigravity-google/default.svg',
1900
1885
  steps: [
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}` },
1886
+ { title: '~/.antigravity/mcp.json に追加', body: '~/.antigravity/mcp.json を作成または編集し、以下を追加してください。保存後 Antigravity を再起動するとMCPサーバが読み込まれます。', code: `{\n "mcpServers": {\n "html2pptx": {\n "url": "https://html2pptx.app/mcp"\n }\n }\n}` },
1902
1887
  ],
1888
+ tip: 'Antigravity (Google) は標準の MCP mcpServers 形式をそのまま使えます。Cursor とまったく同じ JSON です。',
1903
1889
  },
1904
1890
  ],
1905
1891
  mcpToolsTitle: '利用可能なMCPツール',
@@ -1,4 +1,3 @@
1
- import { type IncomingMessage, type ServerResponse } from "node:http";
2
1
  export interface EditOptions {
3
2
  port?: string;
4
3
  baseUrl?: string;
@@ -18,7 +17,7 @@ declare function normalizeBaseUrl(raw: string): URL;
18
17
  declare function readRegisteredEditorBaseUrl(root: string): Promise<string | null>;
19
18
  declare function resolveEditorBaseUrl(root: string, explicitBaseUrl: string | undefined): Promise<URL>;
20
19
  declare function buildEditorUrl(baseUrl: URL, rel: string, bridgeUrl: string, sessionToken: string): URL;
21
- declare function createBridgeServer(ctx: BridgeContext): import("node:http").Server<typeof IncomingMessage, typeof ServerResponse>;
20
+ declare function createBridgeServer(ctx: BridgeContext): any;
22
21
  declare function listen(server: ReturnType<typeof createBridgeServer>, requestedPort: number): Promise<number>;
23
22
  export declare function editCommand(input: string | undefined, options?: EditOptions): Promise<void>;
24
23
  export declare const editCommandInternalsForTest: {
@@ -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,34 @@
1
+ import { type IncomingMessage, type ServerResponse } from "node:http";
2
+ export interface EditOptions {
3
+ port?: string;
4
+ baseUrl?: string;
5
+ noOpen?: boolean;
6
+ open?: boolean;
7
+ json?: boolean;
8
+ }
9
+ interface BridgeContext {
10
+ root: string;
11
+ editorOrigin: string;
12
+ localStateDir: string;
13
+ sessionToken: string;
14
+ }
15
+ declare function generateSessionToken(): string;
16
+ declare function parsePort(value: string | undefined): number;
17
+ declare function normalizeBaseUrl(raw: string): URL;
18
+ declare function readRegisteredEditorBaseUrl(root: string): Promise<string | null>;
19
+ declare function resolveEditorBaseUrl(root: string, explicitBaseUrl: string | undefined): Promise<URL>;
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>;
22
+ declare function listen(server: ReturnType<typeof createBridgeServer>, requestedPort: number): Promise<number>;
23
+ export declare function editCommand(input: string | undefined, options?: EditOptions): Promise<void>;
24
+ export declare const editCommandInternalsForTest: {
25
+ buildEditorUrl: typeof buildEditorUrl;
26
+ createBridgeServer: typeof createBridgeServer;
27
+ generateSessionToken: typeof generateSessionToken;
28
+ listen: typeof listen;
29
+ normalizeBaseUrl: typeof normalizeBaseUrl;
30
+ parsePort: typeof parsePort;
31
+ readRegisteredEditorBaseUrl: typeof readRegisteredEditorBaseUrl;
32
+ resolveEditorBaseUrl: typeof resolveEditorBaseUrl;
33
+ };
34
+ export {};