htmlcraft 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +77 -2
- package/SECURITY.md +32 -0
- package/THIRD-PARTY-LICENSES.md +1401 -0
- package/dist/cli.js +108625 -0
- package/dist/editor/assets/index-BklY9zS4.css +1 -0
- package/dist/editor/assets/index-CcXgWYEN.js +737 -0
- package/dist/editor/index.html +57 -0
- package/dist/mcp.js +99001 -0
- package/dist/skill/SKILL.md +254 -0
- package/dist/skill/examples/basic.html +742 -0
- package/dist/skill/examples/notes-transitions.html +736 -0
- package/dist/skill/examples/table-chart.html +754 -0
- package/dist/skill/references/authoring.md +160 -0
- package/dist/skill/references/contract.md +177 -0
- package/dist/skill/references/mcp-tools.md +397 -0
- package/dist/skill/references/troubleshooting.md +188 -0
- package/package.json +51 -4
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
# htmlcraft MCP tools and resources
|
|
2
|
+
|
|
3
|
+
The htmlcraft MCP server exposes 25 tools and 3 resources over stdio. Start it with
|
|
4
|
+
`htmlcraft-mcp` or `htmlcraft mcp`. Tool names and arguments below are the complete
|
|
5
|
+
supported surface; there are no other deck tools in this milestone.
|
|
6
|
+
|
|
7
|
+
## Shared conventions
|
|
8
|
+
|
|
9
|
+
- `path` is a deck HTML path. A relative path resolves against the MCP server's
|
|
10
|
+
working directory. Responses report the resolved absolute path.
|
|
11
|
+
- `slideId` is a slide's `data-slide-id`, for example `s1`.
|
|
12
|
+
- `target` is exactly one of:
|
|
13
|
+
|
|
14
|
+
```json
|
|
15
|
+
{ "elementId": "e3" }
|
|
16
|
+
{ "selector": ".card > h2" }
|
|
17
|
+
{ "path": [1, 0] }
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`elementId` matches `data-ppt-el` in the slide, `selector` must match exactly
|
|
21
|
+
one element in the slide, and `path` is child indices starting at the slide
|
|
22
|
+
element.
|
|
23
|
+
- Every tool returns one text content item containing JSON. Read tools return
|
|
24
|
+
the data directly. A handler or domain failure sets `isError: true` and
|
|
25
|
+
returns `{ code, message, ...context }`.
|
|
26
|
+
- Schema-validation failures are different: the MCP SDK rejects them with the
|
|
27
|
+
`InvalidParams` code (JSON-RPC `-32602`) before the handler runs, and
|
|
28
|
+
delivers that rejection as an `isError: true` text result such as
|
|
29
|
+
`MCP error -32602: Input validation error: Invalid arguments for tool
|
|
30
|
+
set_text: Required at text`. There is no structured `{ code, message }`
|
|
31
|
+
payload to parse. Missing required arguments and wrong argument types take
|
|
32
|
+
this path; check the call against the tool schema and retry. Domain failures
|
|
33
|
+
such as `file-not-found`, `conflict`, or `slide-not-found` use the structured
|
|
34
|
+
payload described above.
|
|
35
|
+
- Sending a non-object `arguments` value is a malformed JSON-RPC request; the
|
|
36
|
+
transport reports it as an internal error rather than a tool result.
|
|
37
|
+
- Reads return `revision`. Mutations return the new `revision`. The server
|
|
38
|
+
remembers the last revision it read or wrote per file and compares it before
|
|
39
|
+
writing; an external edit produces `code: "conflict"`.
|
|
40
|
+
- Each mutation validates the whole document before writing. An invalid result
|
|
41
|
+
is rejected and the file is left unchanged.
|
|
42
|
+
- Writes are atomic. In host-attached mode they route through the editor host
|
|
43
|
+
and broadcast a `revision` event with `source: "mcp"`; without a live host
|
|
44
|
+
they write the file directly.
|
|
45
|
+
|
|
46
|
+
## Read-only tools
|
|
47
|
+
|
|
48
|
+
### `get_deck_contract`
|
|
49
|
+
|
|
50
|
+
No arguments. Returns `{ contract, authoring }`: the contract markdown and the
|
|
51
|
+
authoring guidance markdown.
|
|
52
|
+
|
|
53
|
+
### `validate_deck`
|
|
54
|
+
|
|
55
|
+
Arguments: `{ path }`.
|
|
56
|
+
|
|
57
|
+
Returns `{ path, revision, errors, warnings, info, valid }`. Each diagnostic has
|
|
58
|
+
`severity`, `code`, `message`, and optional `slideId`/`location`. Validation
|
|
59
|
+
runs on the authored bytes, not on in-memory normalization.
|
|
60
|
+
|
|
61
|
+
### `get_outline`
|
|
62
|
+
|
|
63
|
+
Arguments: `{ path }`.
|
|
64
|
+
|
|
65
|
+
Returns `{ path, revision, outline }`. The outline contains `title`, `width`,
|
|
66
|
+
`height`, `slideCount`, `runtime`, and `slides`. Each slide has `id`, `index`,
|
|
67
|
+
`width`, `height`, `transition`, `notes`, and `elements`. Each element has
|
|
68
|
+
`id`, `tag`, `path`, `text` (trimmed to 120 characters), `classes`, and
|
|
69
|
+
`group`.
|
|
70
|
+
|
|
71
|
+
Use this before editing to learn slide ids and element ids instead of guessing.
|
|
72
|
+
|
|
73
|
+
### `read_slide`
|
|
74
|
+
|
|
75
|
+
Arguments: `{ path, slideId }`.
|
|
76
|
+
|
|
77
|
+
Returns `{ path, revision, slide }`. The slide contains `id`, `index`, `html`
|
|
78
|
+
(the slide's `outerHTML`), `notes`, `transition`, `width`, and `height`.
|
|
79
|
+
|
|
80
|
+
### `get_element`
|
|
81
|
+
|
|
82
|
+
Arguments: `{ path, slideId, target }`.
|
|
83
|
+
|
|
84
|
+
Returns `{ path, revision, html, style }`. `html` is the element's
|
|
85
|
+
`outerHTML`; `style` is its raw inline `style` attribute or `null`. No layout
|
|
86
|
+
is computed.
|
|
87
|
+
|
|
88
|
+
## Mutation tools
|
|
89
|
+
|
|
90
|
+
### `create_deck`
|
|
91
|
+
|
|
92
|
+
Arguments:
|
|
93
|
+
|
|
94
|
+
| Name | Type | Notes |
|
|
95
|
+
| --- | --- | --- |
|
|
96
|
+
| `path` | string | Required. Refuses to overwrite an existing file. |
|
|
97
|
+
| `title` | string | Optional deck title. |
|
|
98
|
+
| `width` | positive integer | Optional canvas width. |
|
|
99
|
+
| `height` | positive integer | Optional canvas height. |
|
|
100
|
+
| `slides` | positive integer, max 1000 | Optional slide count. |
|
|
101
|
+
| `html` | string | Optional full HTML. Normalized and validated before writing. |
|
|
102
|
+
| `runtime` | boolean | Include the runtime. Defaults to true. |
|
|
103
|
+
|
|
104
|
+
Returns `{ path, revision, created: true }`. An existing path fails with
|
|
105
|
+
`file-exists`. An invalid supplied document fails with `invalid-deck` and
|
|
106
|
+
includes `errors`.
|
|
107
|
+
|
|
108
|
+
Prefer `create_deck` over hand-writing a document; it writes the runtime,
|
|
109
|
+
canvas size, and stable slide ids.
|
|
110
|
+
|
|
111
|
+
### `add_slide`
|
|
112
|
+
|
|
113
|
+
Arguments: `{ path, html?, afterSlideId?, notes?, transition? }`.
|
|
114
|
+
|
|
115
|
+
Adds one slide after the last slide, or after `afterSlideId`. Returns
|
|
116
|
+
`{ path, revision, slideId }` with the new slide id. `html` becomes the slide's
|
|
117
|
+
inner HTML. `transition` is `none`, `fade`, or `slide`.
|
|
118
|
+
|
|
119
|
+
### `update_slide_html`
|
|
120
|
+
|
|
121
|
+
Arguments: `{ path, slideId, html }`.
|
|
122
|
+
|
|
123
|
+
Replaces the slide's inner HTML. Returns `{ path, revision }`. Notes and
|
|
124
|
+
transition stay on the slide element. Read the slide first.
|
|
125
|
+
|
|
126
|
+
### `duplicate_slide`
|
|
127
|
+
|
|
128
|
+
Arguments: `{ path, slideId }`.
|
|
129
|
+
|
|
130
|
+
Clones one slide and returns `{ path, revision, slideId }` with the clone's id.
|
|
131
|
+
The clone inherits notes, transition, and content.
|
|
132
|
+
|
|
133
|
+
### `delete_slide`
|
|
134
|
+
|
|
135
|
+
Arguments: `{ path, slideId }`.
|
|
136
|
+
|
|
137
|
+
Deletes one slide and returns `{ path, revision }`. The last remaining slide
|
|
138
|
+
cannot be deleted (`last-slide` error).
|
|
139
|
+
|
|
140
|
+
### `move_slide`
|
|
141
|
+
|
|
142
|
+
Arguments: `{ path, slideId, toIndex }`.
|
|
143
|
+
|
|
144
|
+
Moves one slide to a zero-based index. Returns `{ path, revision }`. The index
|
|
145
|
+
must be in `0..slideCount-1` (`index-out-of-range`).
|
|
146
|
+
|
|
147
|
+
### `set_notes`
|
|
148
|
+
|
|
149
|
+
Arguments: `{ path, slideId, notes }`.
|
|
150
|
+
|
|
151
|
+
Sets or clears speaker notes. An empty string removes the notes aside. Returns
|
|
152
|
+
`{ path, revision }`.
|
|
153
|
+
|
|
154
|
+
### `set_transition`
|
|
155
|
+
|
|
156
|
+
Arguments: `{ path, slideId, transition }`.
|
|
157
|
+
|
|
158
|
+
Sets a slide transition. `transition` is `none`, `fade`, or `slide`. Returns
|
|
159
|
+
`{ path, revision }`.
|
|
160
|
+
|
|
161
|
+
### `set_slide_background`
|
|
162
|
+
|
|
163
|
+
Arguments: `{ path, slideId, background }`.
|
|
164
|
+
|
|
165
|
+
`background` is a strict object with exactly one of `color`, `gradient`, or
|
|
166
|
+
`image` set to a non-empty string. A color uses the `background` shorthand; a
|
|
167
|
+
gradient or image uses `background-image`, and the other form is cleared.
|
|
168
|
+
Returns `{ path, revision }`. Invalid input fails with `invalid-background`.
|
|
169
|
+
|
|
170
|
+
### `add_element`
|
|
171
|
+
|
|
172
|
+
Arguments: `{ path, slideId, html, target? }`.
|
|
173
|
+
|
|
174
|
+
Inserts one raw HTML element as a top-level object. `html` must contain exactly
|
|
175
|
+
one root element and no non-whitespace text (`invalid-fragment`). Without
|
|
176
|
+
`target` the element is appended; with `target` the anchor must be a direct
|
|
177
|
+
child of the slide. Returns `{ path, revision, elementId }`; `elementId` is the
|
|
178
|
+
`data-ppt-el` value (added when missing) or `null` for non-object tags such as
|
|
179
|
+
`script` or `style`.
|
|
180
|
+
|
|
181
|
+
### `add_image`
|
|
182
|
+
|
|
183
|
+
Arguments: `{ path, slideId, source, width?, target? }`.
|
|
184
|
+
|
|
185
|
+
Inserts one top-level `<img>` and returns `{ path, revision, elementId }`.
|
|
186
|
+
`source` is required, and a missing or empty-string value is rejected by the
|
|
187
|
+
SDK as `InvalidParams` before the handler runs. A supplied value is one of:
|
|
188
|
+
|
|
189
|
+
- a supported image data URL (`data:image/...`);
|
|
190
|
+
- an absolute `http:`/`https:` URL, validated but never fetched;
|
|
191
|
+
- a local image path read from the MCP server process's own filesystem exactly
|
|
192
|
+
as given: an absolute path is used as-is, and a relative path resolves
|
|
193
|
+
against the server working directory. The file is embedded into the deck, so
|
|
194
|
+
pass only assets you intend to embed. Protocol-relative and UNC values
|
|
195
|
+
(`//server/share/x.png` or `\\server\share\x.png`) are not local: they fail
|
|
196
|
+
URL validation as `invalid-image` without being resolved, so a remote share
|
|
197
|
+
is never contacted. Single-letter drive paths such as `C:foo.png` are local.
|
|
198
|
+
|
|
199
|
+
A whitespace-only `source` reaches the handler and fails with `invalid-image`.
|
|
200
|
+
Local files must be PNG, JPEG, GIF, WebP, AVIF, or SVG and at most 8 MB. The
|
|
201
|
+
file is opened once, measured through that same descriptor, required to be a
|
|
202
|
+
regular non-empty file, and read only up to its measured size. If the path is
|
|
203
|
+
not a regular file or the file cannot be opened, measured, or read (including
|
|
204
|
+
a file that shrinks during the read), the call fails with `invalid-image`
|
|
205
|
+
naming the image path; it never reports the deck as `file-not-found` or
|
|
206
|
+
`file-unreadable`. Local bytes are embedded as a data URL, so the deck stays
|
|
207
|
+
self-contained. Remote images keep their URL and are not downloaded.
|
|
208
|
+
|
|
209
|
+
The default box is `x: 100`, `y: 100`, `width: 480`, height `270` (16:9). A
|
|
210
|
+
supplied `width` must be a finite number of at least 1 pixel; the height is
|
|
211
|
+
`max(1, Math.round(width * 9 / 16))`. `target` is an optional insertion
|
|
212
|
+
anchor: it must resolve inside the slide and be a direct child, and the image
|
|
213
|
+
is inserted after it; without `target` the image is appended. Failures use
|
|
214
|
+
`invalid-image`, `target-not-found`, `target-ambiguous`, `target-invalid`, or
|
|
215
|
+
`invalid-insert` from the core insertion checks.
|
|
216
|
+
|
|
217
|
+
### `add_table`
|
|
218
|
+
|
|
219
|
+
Arguments: `{ path, slideId, rows, columns, target? }`.
|
|
220
|
+
|
|
221
|
+
Inserts a real `<table>` with a header row and returns
|
|
222
|
+
`{ path, revision, elementId }`. `rows` (including the header row, 1-50) and
|
|
223
|
+
`columns` (1-20) must be integers. Missing arguments or non-integer values are
|
|
224
|
+
`InvalidParams`; values outside the core bounds fail with `invalid-table`.
|
|
225
|
+
The default box is `x: 100`, `y: 100`, width `960`. `target` follows the same
|
|
226
|
+
direct-child insertion anchor rules as `add_image` and can fail with
|
|
227
|
+
`target-not-found`, `target-ambiguous`, or `target-invalid`.
|
|
228
|
+
|
|
229
|
+
### `update_chart`
|
|
230
|
+
|
|
231
|
+
Arguments: `{ path, slideId, target, chart }`.
|
|
232
|
+
|
|
233
|
+
`target` must resolve to an existing chart: a `<figure>` carrying
|
|
234
|
+
`data-ppt-chart`. A target that matches nothing or several elements fails with
|
|
235
|
+
`target-not-found` or `target-ambiguous`; anything that resolves but is not a
|
|
236
|
+
chart fails with `not-a-chart`. `chart` is required (a missing or wrongly
|
|
237
|
+
typed value is `InvalidParams`) and is a JSON string or object:
|
|
238
|
+
|
|
239
|
+
```json
|
|
240
|
+
{
|
|
241
|
+
"type": "bar",
|
|
242
|
+
"title": "Revenue",
|
|
243
|
+
"labels": ["Q1", "Q2"],
|
|
244
|
+
"series": [{ "name": "USD", "values": [12, 18] }]
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
`type` is `bar`, `line`, or `pie`; optional fields are `colors`, `legend`, and
|
|
249
|
+
`valueLabels`. Labels and every series must be non-empty, values must be finite
|
|
250
|
+
numbers with one value per label, and pie charts need exactly one non-negative
|
|
251
|
+
series with a positive total. Malformed JSON or invalid data fails with
|
|
252
|
+
`invalid-chart` and does not write the deck. The tool normalizes the JSON,
|
|
253
|
+
stores it in `data-ppt-chart`, and regenerates the SVG at the figure's
|
|
254
|
+
persisted size: inline width at least 160 px and inline height at least 120 px,
|
|
255
|
+
then the SVG `viewBox`, then 640x360. A valid short chart (for example 640x140)
|
|
256
|
+
keeps its height instead of being resized to 360. Returns
|
|
257
|
+
`{ path, revision, width, height }`.
|
|
258
|
+
|
|
259
|
+
### `set_element_html`
|
|
260
|
+
|
|
261
|
+
Arguments: `{ path, slideId, target, html }`.
|
|
262
|
+
|
|
263
|
+
Replaces the element's inner HTML. Returns `{ path, revision }`. Validate after
|
|
264
|
+
using intentional markup.
|
|
265
|
+
|
|
266
|
+
### `set_text`
|
|
267
|
+
|
|
268
|
+
Arguments: `{ path, slideId, target, text }`.
|
|
269
|
+
|
|
270
|
+
Replaces the element's text content. The text is escaped, not interpreted as
|
|
271
|
+
HTML. Returns `{ path, revision }`.
|
|
272
|
+
|
|
273
|
+
### `set_style`
|
|
274
|
+
|
|
275
|
+
Arguments: `{ path, slideId, target, styles }`.
|
|
276
|
+
|
|
277
|
+
`styles` maps CSS property names to string values, or to `null` to remove a
|
|
278
|
+
property. Returns `{ path, revision }`. Invalid property names fail with
|
|
279
|
+
`invalid-style-property`.
|
|
280
|
+
|
|
281
|
+
### `remove_element`
|
|
282
|
+
|
|
283
|
+
Arguments: `{ path, slideId, target }`.
|
|
284
|
+
|
|
285
|
+
Removes one element from a slide. Returns `{ path, revision }`. The slide itself
|
|
286
|
+
cannot be removed this way (`cannot-remove-slide`).
|
|
287
|
+
|
|
288
|
+
### `set_slide_order`
|
|
289
|
+
|
|
290
|
+
Arguments: `{ path, slideIds }`.
|
|
291
|
+
|
|
292
|
+
Reorders all slides. `slideIds` must contain every slide id exactly once
|
|
293
|
+
(`invalid-order`). Returns `{ path, revision }`.
|
|
294
|
+
|
|
295
|
+
## Workflow tools
|
|
296
|
+
|
|
297
|
+
### `open_editor`
|
|
298
|
+
|
|
299
|
+
Arguments: `{ path, port?, open? }`.
|
|
300
|
+
|
|
301
|
+
Starts a detached local editor host for an existing deck and returns
|
|
302
|
+
`{ path, url }`. `url` is a tokenized loopback URL such as
|
|
303
|
+
`http://127.0.0.1:53211/?token=...`. `port` defaults to the port recorded for
|
|
304
|
+
the deck, or an operating-system-assigned port when none is recorded; an
|
|
305
|
+
explicit `port` wins. The token is recorded per deck and reused by later opens
|
|
306
|
+
until `htmlcraft stop` rotates it; replacing a stale-build host rotates it too,
|
|
307
|
+
because `open` stops the broken host first, so a URL from before the replacement
|
|
308
|
+
needs the newly printed one. Apart from that replacement, the tokenized URL
|
|
309
|
+
keeps working across a host restart. `open` (default false) controls whether a
|
|
310
|
+
browser is launched.
|
|
311
|
+
The host binds to 127.0.0.1 and permits only the opened deck, its export, and
|
|
312
|
+
editor assets.
|
|
313
|
+
|
|
314
|
+
Reuses an existing session only when two probes pass: the authenticated
|
|
315
|
+
`/api/health` endpoint accepts the deck and the editor shell at
|
|
316
|
+
`/d/<deck-name>` is the built editor. Health alone is not success; a
|
|
317
|
+
`stale-build` host is stopped and replaced on the recorded port by the next
|
|
318
|
+
open. Fails with `editor-unavailable` when the CLI build is missing,
|
|
319
|
+
`editor-failed` when the spawned CLI exits with a non-zero code or is terminated
|
|
320
|
+
by a signal before publishing a healthy session (the message names the exit code
|
|
321
|
+
or signal), and `editor-timeout` when no healthy session appears within 5
|
|
322
|
+
seconds. A spawned CLI that exits with code 0 before publishing a session is not
|
|
323
|
+
`editor-failed`; it surfaces as `editor-timeout`. On timeout the spawned host
|
|
324
|
+
process is terminated instead of being left running.
|
|
325
|
+
|
|
326
|
+
### `export_deck`
|
|
327
|
+
|
|
328
|
+
Arguments: `{ path, format, outDir? }`.
|
|
329
|
+
|
|
330
|
+
`format` is one of `html`, `pdf`, or `png` and defaults to `html`.
|
|
331
|
+
|
|
332
|
+
- `html` returns `{ path, format: "html", outputPath }` and writes
|
|
333
|
+
`<deck-name>.export.html` beside the deck, or in `outDir` when supplied.
|
|
334
|
+
- `pdf` returns `{ path, format: "pdf", files }` with one PDF named
|
|
335
|
+
`<deck-name>.pdf`.
|
|
336
|
+
- `png` returns `{ path, format: "png", files }` with one
|
|
337
|
+
`<deck-name>-sN.png` per slide.
|
|
338
|
+
|
|
339
|
+
PDF and PNG require a system Chrome, Edge, or Chromium (or `HTMLCRAFT_CHROMIUM`)
|
|
340
|
+
and fail with `renderer-unavailable` when none is found; `export-failed`
|
|
341
|
+
reports a rendering error. `outDir` resolves against the deck directory and
|
|
342
|
+
must stay inside it for every format, in direct and host-attached mode; an
|
|
343
|
+
escape fails with `invalid-out-dir`. An invalid deck fails with `invalid-deck`
|
|
344
|
+
and includes `errors`.
|
|
345
|
+
|
|
346
|
+
## Resources
|
|
347
|
+
|
|
348
|
+
| URI | MIME type | Content |
|
|
349
|
+
| --- | --- | --- |
|
|
350
|
+
| `htmlcraft://contract` | `text/markdown` | The htmldeck/v1 contract. |
|
|
351
|
+
| `htmlcraft://authoring` | `text/markdown` | Agent authoring guidance. |
|
|
352
|
+
| `htmlcraft://example` | `text/html` | A complete valid example deck with text, image, shape, table, chart, notes, transitions, and the real runtime. |
|
|
353
|
+
|
|
354
|
+
Read `htmlcraft://contract` and `htmlcraft://authoring` before the first edit in a
|
|
355
|
+
session. Read `htmlcraft://example` when you need a working reference file.
|
|
356
|
+
|
|
357
|
+
## Error codes
|
|
358
|
+
|
|
359
|
+
The error payload always includes `code` and `message`, plus `path` and, when
|
|
360
|
+
relevant, `slideId`, `target`, `outputPath`, `currentRevision`, or `errors`.
|
|
361
|
+
|
|
362
|
+
| Code | Meaning and fix |
|
|
363
|
+
| --- | --- |
|
|
364
|
+
| `conflict` | The file changed after your last read/write. Re-read with `get_outline` or `read_slide`, reconcile, retry. |
|
|
365
|
+
| `file-exists` | `create_deck` refuses to overwrite. Choose a new path or ask the user. |
|
|
366
|
+
| `file-not-found` | The deck path does not exist. Check the path or create the deck. |
|
|
367
|
+
| `file-unreadable` | Permission or path type prevents reading. |
|
|
368
|
+
| `file-unwritable` | Permission prevents writing. |
|
|
369
|
+
| `directory-not-found` | The parent directory of a new file does not exist. |
|
|
370
|
+
| `invalid-deck` | The supplied or exported deck failed validation. Inspect `errors`. |
|
|
371
|
+
| `invalid-result` | A mutation would make the deck invalid and was rejected. |
|
|
372
|
+
| `renderer-unavailable` | PDF/PNG export found no Chrome, Edge, or Chromium. Install one or set `HTMLCRAFT_CHROMIUM`. |
|
|
373
|
+
| `export-failed` | Rendering or the host export path failed. Read `message` for the actionable detail. |
|
|
374
|
+
| `slide-not-found` | The slide id is wrong. Use `get_outline`. |
|
|
375
|
+
| `target-not-found` | No element matches the target. Re-read the slide. |
|
|
376
|
+
| `target-ambiguous` | The target matches more than one element. Use an id or a narrower selector. |
|
|
377
|
+
| `target-invalid` | The selector or path is invalid, or an insertion anchor is not a direct child of the slide. |
|
|
378
|
+
| `invalid-fragment` | `add_element` HTML is not exactly one root element. |
|
|
379
|
+
| `invalid-image` | `add_image` source is not a supported data/http(s) URL, or a local image cannot be opened, measured, or read, is not a regular file, is empty, has an unsupported type, or is larger than 8 MB. A missing or empty-string `source` is `InvalidParams` before the handler runs. |
|
|
380
|
+
| `invalid-out-dir` | `export_deck` `outDir` escapes the deck directory. Use a relative subdirectory such as `exports`. |
|
|
381
|
+
| `source-overwrite` | The export destination would replace the source deck (for example a deck named `deck.pdf` exported as PDF). Rename the deck or choose a different `outDir`. |
|
|
382
|
+
| `invalid-table` | `add_table` rows or columns are outside the 50x20 core bounds. |
|
|
383
|
+
| `invalid-chart` | `update_chart` data is malformed or fails chart validation; the deck is unchanged. |
|
|
384
|
+
| `invalid-insert` | A core insert rejected the geometry or content, for example a computed non-positive image size. |
|
|
385
|
+
| `not-a-chart` | `update_chart` target is not a `<figure data-ppt-chart>`. |
|
|
386
|
+
| `cannot-remove-slide` | The slide/body itself cannot be removed as an element. |
|
|
387
|
+
| `last-slide` | The final slide cannot be deleted. |
|
|
388
|
+
| `invalid-order` | `set_slide_order` must list every slide id exactly once. |
|
|
389
|
+
| `index-out-of-range` | `move_slide` index is outside the slide range. |
|
|
390
|
+
| `slide-operations-unavailable` | Slide operations are unavailable on the body fallback. |
|
|
391
|
+
| `invalid-style-property` | A style key is not a valid CSS property name. |
|
|
392
|
+
| `invalid-background` | Background must set exactly one non-empty color/gradient/image. |
|
|
393
|
+
| `invalid-transition` | Transition must be none, fade, or slide. |
|
|
394
|
+
| `editor-unavailable` | The built CLI was not found. Run `npm run build`. |
|
|
395
|
+
| `editor-failed` | The spawned CLI exited with a non-zero code or was terminated by a signal before publishing a healthy session; the message names the exit code or signal. Run `htmlcraft open <file> --no-browser` and read its stderr. |
|
|
396
|
+
| `editor-timeout` | No healthy editor session appeared within 5 seconds; the spawned host was terminated. Check the editor build and try `htmlcraft open`. |
|
|
397
|
+
| `internal` | Unexpected failure; the message carries the detail. |
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Troubleshooting htmlcraft
|
|
2
|
+
|
|
3
|
+
Start with the cheapest probe:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
htmlcraft validate deck.html --json # errors, warnings, info, valid
|
|
7
|
+
htmlcraft outline deck.html --json # slide and element ids
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Through MCP, use `validate_deck` and `get_outline` for the same data. Re-read
|
|
11
|
+
before changing a deck after an external edit or a conflict.
|
|
12
|
+
|
|
13
|
+
## Validation errors
|
|
14
|
+
|
|
15
|
+
Errors make the deck invalid and block MCP mutations and export. Fix every
|
|
16
|
+
error before continuing.
|
|
17
|
+
|
|
18
|
+
| Code | Fix |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| `empty-document` | Add at least one slide with visible content. |
|
|
21
|
+
| `nested-slide` | Move the inner slide out of its parent slide so slides are siblings. |
|
|
22
|
+
| `invalid-canvas-size` | Use a positive pixel number for `data-width`/`data-height`, or omit both. |
|
|
23
|
+
| `invalid-transition` | Use exactly `none`, `fade`, or `slide`. |
|
|
24
|
+
| `duplicate-element-id` | Make `data-ppt-el` unique within the slide; ids may repeat across different slides. |
|
|
25
|
+
| `invalid-chart-json` | Fix the JSON in `data-ppt-chart`. Use single quotes around a double-quoted JSON attribute. |
|
|
26
|
+
| `duplicate-slide-id` | Give every slide a unique, non-empty `data-slide-id`. |
|
|
27
|
+
| `invalid-runtime-marker` | Put `data-ppt-runtime="1"` on a `<script>`, or remove the attribute. |
|
|
28
|
+
| `duplicate-runtime` | Keep exactly one runtime script per document. |
|
|
29
|
+
| `notes-nested-incorrectly` | Make the notes `<aside data-ppt-notes hidden>` a direct child of its slide. |
|
|
30
|
+
| `editor-artifacts` | Remove `data-ppt-editing` and `data-ppt-frame-slide`; never save editor internals into the deck. |
|
|
31
|
+
|
|
32
|
+
## Validation warnings
|
|
33
|
+
|
|
34
|
+
Warnings do not block saving or export but usually predict a broken round trip.
|
|
35
|
+
|
|
36
|
+
| Code | Fix |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `missing-title` | Add a non-empty `<title>`. |
|
|
39
|
+
| `single-body-fallback` | Add explicit slides (`data-slide-id`); the body fallback is hard to edit. |
|
|
40
|
+
| `missing-slide-id` | Add a unique `data-slide-id`. Normalization invents one, but an explicit id is stable. |
|
|
41
|
+
| `missing-runtime` | Copy the real runtime script from `examples/basic.html`, or regenerate with `create_deck`/`htmlcraft new`. |
|
|
42
|
+
| `script-present` | Remove non-runtime scripts or accept that they do not run in the editor. |
|
|
43
|
+
| `relative-asset` | Use a `data:` URI or an absolute URL for images, fonts, and `url(...)` values. |
|
|
44
|
+
| `fixed-layout` | Replace `position: fixed` and full-height `100vh` units with fixed-canvas absolute positioning. Other viewport units are not detected but still scale with the window. |
|
|
45
|
+
| `mixed-canvas-sizes` | Use one `data-width`/`data-height` for every slide. |
|
|
46
|
+
| `canvas-content` | Replace `<canvas>` drawing with editable markup or embed an image. |
|
|
47
|
+
| `unsupported-group-nesting` | Flatten nested `data-ppt-group` wrappers. |
|
|
48
|
+
|
|
49
|
+
## MCP tool failures
|
|
50
|
+
|
|
51
|
+
Every failure returns `isError: true` and a JSON payload with `code`,
|
|
52
|
+
`message`, and context. The full list is in
|
|
53
|
+
[mcp-tools.md](mcp-tools.md#error-codes).
|
|
54
|
+
|
|
55
|
+
### `conflict`
|
|
56
|
+
|
|
57
|
+
The file changed after the MCP server last read or wrote it. The file was not
|
|
58
|
+
modified by the failed call.
|
|
59
|
+
|
|
60
|
+
1. Call `get_outline` or `read_slide`.
|
|
61
|
+
2. Reconcile the external change; do not discard a person's edits.
|
|
62
|
+
3. Retry the mutation.
|
|
63
|
+
|
|
64
|
+
If the caller is a host-attached editor session, the host also emits a
|
|
65
|
+
`revision` event with `source: "mcp"` when an MCP write succeeds. The open
|
|
66
|
+
editor consumes the event and applies the new revision. Saving from a stale
|
|
67
|
+
editor revision returns 409/conflict.
|
|
68
|
+
|
|
69
|
+
### `slide-not-found`, `target-not-found`, `target-ambiguous`
|
|
70
|
+
|
|
71
|
+
Your ids or selector are stale, or the target matches more than one node.
|
|
72
|
+
Read `get_outline` and `get_element` again. Prefer `elementId` targets after a
|
|
73
|
+
read; a selector must match exactly one element inside the slide.
|
|
74
|
+
|
|
75
|
+
### `invalid-fragment`
|
|
76
|
+
|
|
77
|
+
`add_element` needs exactly one root element and no non-whitespace text. Wrap
|
|
78
|
+
multiple elements in one container, for example a `<div data-ppt-el="...">`.
|
|
79
|
+
|
|
80
|
+
### `invalid-deck` or `invalid-result`
|
|
81
|
+
|
|
82
|
+
The deck would be invalid after the operation. The payload includes `errors`
|
|
83
|
+
for `create_deck`/`export_deck`; `invalid-result` means the mutation was
|
|
84
|
+
rejected before writing. Run `validate_deck` and fix the listed codes. Small
|
|
85
|
+
edits make this easier to diagnose.
|
|
86
|
+
|
|
87
|
+
### `file-exists`
|
|
88
|
+
|
|
89
|
+
`create_deck` never overwrites. Choose a new path, or ask the user before
|
|
90
|
+
replacing the existing deck.
|
|
91
|
+
|
|
92
|
+
### `editor-unavailable`, `editor-failed`, or `editor-timeout`
|
|
93
|
+
|
|
94
|
+
`open_editor` starts the built CLI. If it reports `editor-unavailable`, run
|
|
95
|
+
`npm run build` so `packages/editor/dist` and the CLI dist exist. If it reports
|
|
96
|
+
`editor-failed`, the spawned CLI exited with a non-zero code or was terminated
|
|
97
|
+
by a signal before publishing a healthy session, and the message names that
|
|
98
|
+
code or signal: run `htmlcraft open <file> --no-browser` manually and read its
|
|
99
|
+
stderr. If it reports `editor-timeout`, the CLI did not publish a healthy
|
|
100
|
+
session within 5 seconds (an exit with code 0 before a session surfaces here,
|
|
101
|
+
not as `editor-failed`): run the same command manually.
|
|
102
|
+
|
|
103
|
+
A host that is alive but times out or answers health with a 5xx is `unknown`,
|
|
104
|
+
and a shell probe that times out leaves the same verdict; these hosts block a
|
|
105
|
+
second host for that deck, so retry, or stop one explicitly. A host whose health
|
|
106
|
+
passes but whose editor shell answers a non-200 status, returns non-HTML, or
|
|
107
|
+
omits `id="root"` (`stale-build`) is not a blocker: the next `open` stops it and
|
|
108
|
+
replaces it automatically, which rotates the token. Do not stop a broken editor
|
|
109
|
+
by hand unless the automatic replacement itself fails.
|
|
110
|
+
|
|
111
|
+
### Unexpected host behavior
|
|
112
|
+
|
|
113
|
+
`HTMLCRAFT_MCP_MODE=direct` forces direct file access even when a host session
|
|
114
|
+
exists. Use it only as a temporary diagnostic; it bypasses host synchronization
|
|
115
|
+
and can make the editor miss changes. Unset it for normal operation.
|
|
116
|
+
|
|
117
|
+
## CLI failures
|
|
118
|
+
|
|
119
|
+
CLI failures print a message to stderr and exit 1.
|
|
120
|
+
|
|
121
|
+
| Message | Cause and fix |
|
|
122
|
+
| --- | --- |
|
|
123
|
+
| `Deck file not found: <file>. Create one with "htmlcraft new <file>".` | `open` path is wrong or the deck does not exist. |
|
|
124
|
+
| `Deck file not found: <file>.` | `outline` path is wrong. |
|
|
125
|
+
| `Deck file could not be read: <file>.` | The path is a directory or permissions block reading. |
|
|
126
|
+
| `Refusing to overwrite <file>. Pass --force to replace it.` | `new` found an existing file. Choose another name or pass `--force`. |
|
|
127
|
+
| `Invalid --size. Use positive WIDTHxHEIGHT, for example 1600x900.` | Fix the `--size` value. |
|
|
128
|
+
| `Invalid --slides. Use a positive integer up to 1000.` | Fix the `--slides` value. |
|
|
129
|
+
| `Port <port> is already in use. Omit --port or choose another.` | Another process owns the port. Omit `--port` to let `open` retry the deck's recorded port and fall back to an ephemeral one, or choose another port. |
|
|
130
|
+
| `Editor build not found. Run "npm run build" first.` | `packages/editor/dist` is missing. Build the workspace. |
|
|
131
|
+
| `An editor for this deck appears to be running but is not responding. Retry in a moment or run "htmlcraft stop <file>" to stop it.` | A recorded host has a live PID but its health check timed out or returned a 5xx (`unknown`), so `open` cannot prove it owns the deck and refuses to start a second host. Retry, or stop the recorded one. An alive host whose health passes but whose editor shell is broken is `stale-build` instead: `open` replaces it automatically, and this message is not printed. |
|
|
132
|
+
| `No live editor session for <file>. Start one with "htmlcraft open <file>".` | `stop` found nothing to stop for that deck. |
|
|
133
|
+
| `A registered editor for this deck could not be verified; leaving it running. Retry "htmlcraft stop <file>" when it responds.` | The caller's spelling has no registry entry, but the same deck is registered under another spelling (alias or short path) and its health check timed out or returned a 5xx (`unknown`). Nothing was stopped; retry when it responds. `stop --all` also leaves unverified entries running. |
|
|
134
|
+
| `Specify a deck file to stop or pass --all.` | `stop` needs a target. |
|
|
135
|
+
| `Pass either a deck file or --all, not both.` | Use one form. |
|
|
136
|
+
| `No live editor sessions.` | The registry is empty (no session files at all). |
|
|
137
|
+
| `Removed <n> stale editor session(s).` | Success: `stop --all` cleaned entries whose process had exited. Exit 0. |
|
|
138
|
+
| `Removed stale editor session for <path>.` | Success: a definitively stale entry was cleaned. |
|
|
139
|
+
| `Could not verify a live editor for <path>; leaving it running. Retry "htmlcraft stop <path>" when it responds.` | The PID is alive but health is unknown. The entry was kept. Retry later. |
|
|
140
|
+
| `No live editor sessions were stopped.` | Nothing was live or cleanable. |
|
|
141
|
+
| `The editor session for <path> changed while stopping it. Retry "htmlcraft stop <path>".` | The registry changed after verification. Re-run stop. |
|
|
142
|
+
| `The editor session for <path> disappeared before it could be stopped.` | The host removed its entry first. No action needed. |
|
|
143
|
+
| `The editor process <pid> for <path> did not stop within 5 seconds. Stop it manually, then run "htmlcraft stop <path>" again.` | The process ignored or outlived SIGTERM. Inspect the process before killing it manually. The timeout path is OS-limited and not covered by the automated tests. |
|
|
144
|
+
| `Could not read the editor session registry at <dir>: ...` | The registry directory exists but is unreadable. Fix permissions, then retry the command. |
|
|
145
|
+
| `Could not verify <n> editor session entries that are still in the registry at <dir>.` | Some entries survived pruning but could not be read or removed. Fix permissions, then retry `stop --all`. |
|
|
146
|
+
| `Could not read the editor session entry for <path>.` | One entry is unreadable. Fix permissions on the registry, then retry. |
|
|
147
|
+
| `Chromium is unavailable. Install Google Chrome, Microsoft Edge or Chromium, or set HTMLCRAFT_CHROMIUM to its executable path.` | PDF/PNG export needs a local browser. Install one, or set `HTMLCRAFT_CHROMIUM` to a Chrome/Edge/Chromium executable. |
|
|
148
|
+
| `Unsupported export format "<format>". Use --format html, pdf, or png.` | Fix `--format`. |
|
|
149
|
+
| `Invalid --port. Use an integer between 0 and 65535.` | Fix the port number. |
|
|
150
|
+
|
|
151
|
+
`open` reuses an editor for the same deck and prints its `HTMLCRAFT_URL` instead
|
|
152
|
+
of starting a second host, but only when the authenticated health endpoint
|
|
153
|
+
accepts the deck and `/d/<deck-name>` serves the built editor shell. A
|
|
154
|
+
`stale-build` host (health OK, editor broken) is stopped and replaced on the
|
|
155
|
+
recorded port; because that stop rotates the token, the printed URL carries a
|
|
156
|
+
new token. The session registry lives under `~/.htmlcraft/sessions`; set
|
|
157
|
+
`HTMLCRAFT_HOME` to isolate it for tests or automation. `HTMLCRAFT_EDITOR_DIR`
|
|
158
|
+
overrides the editor build location.
|
|
159
|
+
|
|
160
|
+
## Editor and host behavior
|
|
161
|
+
|
|
162
|
+
- Scripts other than the runtime do not run inside the editor. Layout and
|
|
163
|
+
interaction must work from HTML/CSS alone.
|
|
164
|
+
- All slides stacked on one page usually means the runtime is missing or the
|
|
165
|
+
runtime script body is empty/placeholder text. Copy the real runtime from
|
|
166
|
+
`examples/basic.html`. Note that validation only checks the
|
|
167
|
+
`<script data-ppt-runtime="1">` marker, so an empty body still validates;
|
|
168
|
+
the `missing-runtime` warning fires only when the marker is absent.
|
|
169
|
+
- The host watches the file and emits revision events over SSE, including for
|
|
170
|
+
external writes. The open editor consumes them and applies the new revision.
|
|
171
|
+
A save based on the old revision can return 409/conflict; reload and merge.
|
|
172
|
+
- A relative image or font may load on the author's machine but disappear in
|
|
173
|
+
export. Validation warns with `relative-asset`; use `data:` or an absolute
|
|
174
|
+
URL.
|
|
175
|
+
- Text overflow is usually a missing width on the object. Set an explicit
|
|
176
|
+
`width` inline; do not rely on viewport sizing.
|
|
177
|
+
- Notes are hidden in presentation. If they appear on the slide, add the
|
|
178
|
+
`hidden` attribute to the note aside; edit notes in the inspector.
|
|
179
|
+
- `data-ppt-transition` values are validated and preserved, but the v1 runtime
|
|
180
|
+
does not animate them yet; play-mode transitions arrive in a later milestone.
|
|
181
|
+
`none` is a valid choice, not an omission.
|
|
182
|
+
|
|
183
|
+
## When to regenerate rather than repair
|
|
184
|
+
|
|
185
|
+
Regenerate with `create_deck` / `htmlcraft new` when the document has no explicit
|
|
186
|
+
slides, has deeply nested slides, or has lost its runtime and ids beyond a
|
|
187
|
+
small repair. Preserve the user's content by copying it into the fresh
|
|
188
|
+
structure, then validate.
|
package/package.json
CHANGED
|
@@ -1,6 +1,53 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "htmlcraft",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Visual editor, CLI, and MCP server for self-contained HTML presentations",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "htmlcraft contributors",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/HappyLiang12/htmlppt.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/HappyLiang12/htmlppt#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/HappyLiang12/htmlppt/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"presentation",
|
|
17
|
+
"slides",
|
|
18
|
+
"html",
|
|
19
|
+
"mcp",
|
|
20
|
+
"cli",
|
|
21
|
+
"editor",
|
|
22
|
+
"deck",
|
|
23
|
+
"agent"
|
|
24
|
+
],
|
|
25
|
+
"type": "module",
|
|
26
|
+
"engines": { "node": ">=20" },
|
|
27
|
+
"bin": {
|
|
28
|
+
"htmlcraft": "dist/cli.js",
|
|
29
|
+
"htmlcraft-mcp": "dist/mcp.js"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"!dist/**/*.map",
|
|
34
|
+
"README.md",
|
|
35
|
+
"LICENSE",
|
|
36
|
+
"SECURITY.md",
|
|
37
|
+
"THIRD-PARTY-LICENSES.md"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsup && node scripts/copy-assets.mjs && node ../../scripts/generate-third-party-licenses.mjs",
|
|
41
|
+
"typecheck": "tsc --noEmit"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"playwright-core": "^1.64.1"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"tsup": "^8.3.0",
|
|
48
|
+
"typescript": "^5.7.0"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
}
|
|
53
|
+
}
|