@tycoworks/tycoslide 0.15.0 → 0.16.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.
Files changed (62) hide show
  1. package/README.md +10 -6
  2. package/dist/agents/catalog.d.ts +32 -0
  3. package/dist/agents/catalog.js +37 -0
  4. package/dist/agents/commands.d.ts +9 -0
  5. package/dist/agents/commands.js +57 -0
  6. package/dist/agents/extract.d.ts +22 -0
  7. package/dist/agents/extract.js +54 -0
  8. package/dist/{files.d.ts → agents/files.d.ts} +13 -3
  9. package/dist/{files.js → agents/files.js} +16 -3
  10. package/dist/agents/inventory.d.ts +83 -0
  11. package/dist/agents/inventory.js +568 -0
  12. package/dist/agents/manifest.d.ts +3 -0
  13. package/dist/{manifest.js → agents/manifest.js} +3 -14
  14. package/dist/agents/media.d.ts +38 -0
  15. package/dist/agents/media.js +83 -0
  16. package/dist/agents/pptx.d.ts +48 -0
  17. package/dist/agents/pptx.js +123 -0
  18. package/dist/agents/skill.d.ts +43 -0
  19. package/dist/agents/skill.js +134 -0
  20. package/dist/cli.js +8 -44
  21. package/dist/engine/dom.d.ts +3 -0
  22. package/dist/engine/dom.js +4 -0
  23. package/dist/engine/fillers/image.d.ts +6 -5
  24. package/dist/engine/fillers/image.js +24 -7
  25. package/dist/engine/generate.d.ts +2 -2
  26. package/dist/engine/generate.js +2 -2
  27. package/dist/engine/index.d.ts +3 -0
  28. package/dist/engine/index.js +2 -0
  29. package/dist/engine/notes.js +20 -62
  30. package/dist/engine/ooxml.d.ts +36 -0
  31. package/dist/engine/ooxml.js +56 -0
  32. package/dist/engine/types.d.ts +4 -0
  33. package/dist/index.d.ts +4 -11
  34. package/dist/index.js +2 -15
  35. package/dist/markdown/blocks/code.js +1 -2
  36. package/dist/markdown/blocks/image.d.ts +23 -0
  37. package/dist/markdown/blocks/image.js +65 -3
  38. package/dist/markdown/blocks/mermaid.d.ts +1 -1
  39. package/dist/markdown/blocks/mermaid.js +6 -4
  40. package/dist/markdown/blocks/table.js +7 -6
  41. package/dist/markdown/blocks/text.js +9 -8
  42. package/dist/markdown/deckCompiler.d.ts +2 -13
  43. package/dist/markdown/deckCompiler.js +7 -78
  44. package/dist/markdown/index.d.ts +4 -2
  45. package/dist/markdown/index.js +3 -1
  46. package/dist/markdown/inline.d.ts +2 -0
  47. package/dist/markdown/inline.js +4 -3
  48. package/dist/markdown/schema/themeConfigSchema.d.ts +2 -11
  49. package/dist/markdown/schema/themeConfigSchema.js +6 -16
  50. package/dist/markdown/slideParser.d.ts +2 -0
  51. package/dist/markdown/slideParser.js +6 -3
  52. package/dist/markdown/types.d.ts +4 -29
  53. package/dist/markdown/types.js +0 -11
  54. package/{theme-package/syntax.md → docs/markdown.md} +60 -28
  55. package/docs/theme.md +144 -0
  56. package/package.json +3 -2
  57. package/theme-package/SKILL.md +24 -87
  58. package/dist/engine/rels.d.ts +0 -18
  59. package/dist/engine/rels.js +0 -55
  60. package/dist/manifest.d.ts +0 -5
  61. package/dist/skillZip.d.ts +0 -49
  62. package/dist/skillZip.js +0 -177
package/docs/theme.md ADDED
@@ -0,0 +1,144 @@
1
+ # theme.json reference
2
+
3
+ `theme.json` sits at the root of a theme package next to `template/`. It maps author-facing names onto shapes in the template `.pptx`: each layout names one slide, each parameter or slot names one shape on it. The template is never edited. At build time the engine clones the named slide, rewrites the named shapes, and removes any optional shape the author left unfilled. Every object is strict: an unknown key fails the load with `theme.json: invalid theme config` and `Unknown key(s): x. Valid keys: ...`.
4
+
5
+ The one trap is `slideNumber`. It is the number in the file name `ppt/slides/slideN.xml`, not the slide's position in the deck. Reordering slides in PowerPoint changes positions but not file names. Always use the file number in `slideNumber` and `sourceSlide`.
6
+
7
+ ## `template`
8
+
9
+ | Field | Meaning | Error when wrong |
10
+ |---|---|---|
11
+ | `template` | File name of the `.pptx`, resolved inside the theme's `template/` folder. Required. | `File not found: <path>` at build; `Theme declares "template/<name>", but no such file exists` at package. |
12
+
13
+ ## `layouts[]`
14
+
15
+ One entry per slide the author may pick. Two layouts may not share a `slideNumber`; the load fails with `Layouts "A" and "B" share slideNumber N`. A slide with more than one arrangement becomes one layout with a multi-accept slot, never two layouts.
16
+
17
+ | Field | Meaning | Error when wrong |
18
+ |---|---|---|
19
+ | `name` | The value authors write as `layout:` in slide frontmatter. Required, unique. | `unknown layout "xyz"` at deck compile, listing the valid names. |
20
+ | `slideNumber` | File number of the slide to clone. Required. | Wrong slide is cloned; no error, wrong content. |
21
+ | `description` | Neutral prose about the arrangement, shown to deck authors. Optional. | None. |
22
+ | `variant` | `"light"` or `"dark"`: the surface the code panel sits on. Optional, required when `codeTheme` is a pair and the layout takes code. | `the theme's "codeTheme" is a { light, dark } pair, but this layout declares no "variant"`. |
23
+ | `parameters` | Single-value text shapes filled from frontmatter. Required, may be `[]`. | |
24
+ | `slots` | Body regions filled from `::key::` markdown regions. Required, may be `[]`. | |
25
+
26
+ ### `parameters[]`
27
+
28
+ A parameter is a styled text shape whose text is rebuilt from a template string. The `{key}` placeholders are the frontmatter keys authors fill. There is no separate key field. A key may appear in only one parameter per layout: `key "title" (parameter "...") is declared twice`. Parameter shape names and slot keys share one namespace: `name "body" (slot) collides with another parameter or slot`.
29
+
30
+ | Field | Meaning | Error when wrong |
31
+ |---|---|---|
32
+ | `shapeName` | Exact name of the text shape on the layout's slide. Required. | The build prints `Can't find element on slide N in source:` and leaves the placeholder text in place. |
33
+ | `template` | The shape's full text with `{key}` placeholders; `\n` is a line break. Required. | `template has 2 lines but the shape has only 1 visual line(s)`; or `its styled sample text "..." does not fit the template` when styles inside a line do not match the placeholders. |
34
+ | `required` | Whether the author must supply the keys. Optional, default `false`; an unfilled optional parameter has its shape removed. | `parameter "X" is marked required but its template has no keys to fill`; at compile, `requires parameter "X" (keys: ...); none provided`. |
35
+
36
+ Keys match `[A-Za-z0-9_]+`. Write `{{` or `}}` for a literal brace. A shape with one line and one style is the safe case. A line that mixes styles must place a literal or style boundary between two placeholders, or the load fails with `variables "{a}" and "{b}" are adjacent with no separator`.
37
+
38
+ ### `slots[]`
39
+
40
+ A slot is a body region. The author's markdown shape picks which accepted block fills it: prose or a code fence routes to `text`, a GFM table to `table`, an image reference or mermaid fence to `image`.
41
+
42
+ | Field | Meaning | Error when wrong |
43
+ |---|---|---|
44
+ | `key` | The `::key::` region name. Required, unique within the layout. | `unknown slot "::x::" in layout "..."` at compile. |
45
+ | `accepts` | The blocks this slot can become, at most one per `type`. Required. | `accepts two text blocks; each content type may appear once.` |
46
+ | `frame` | `{x, y, cx, cy}` in EMU: the region a transplanted shape is positioned into. Required when any block's `sourceSlide` differs from `slideNumber`, otherwise optional. | `Layout "X" slot "y": a transplant block (sourceSlide ≠ N) requires a "frame"`. |
47
+ | `required` | Whether the author must fill the region. Optional, default `false`; an unfilled optional slot has its shape removed. | `layout "X" requires slot "y"; none provided`. |
48
+
49
+ ### Blocks inside `accepts`
50
+
51
+ Each block is discriminated by `type` and carries only its own option. A `bodyRows` on a text block or a `startAt` on a table block is an unknown key and fails the load.
52
+
53
+ | Field | Meaning | Error when wrong |
54
+ |---|---|---|
55
+ | `type` | `"text"`, `"table"` or `"image"`. Required. | Schema error naming the invalid value. |
56
+ | `sourceSlide` | File number of the slide carrying the specimen shape. Required. | Wrong shape cloned, or `Can't find element on slide N`. |
57
+ | `shapeName` | Exact name of the specimen shape on `sourceSlide`. Required. | `Can't find element on slide N in source:` and the placeholder remains. |
58
+ | `startAt` | Text only. Leave the first N specimen paragraphs untouched and rebuild from paragraph N. Optional, default 0. | `startAt 3 is past the last paragraph (shape has 2)`. |
59
+ | `bodyRows` | Table only. `[start, end]`, 0-based inclusive: the rows that repeat for data. Required. Row 0 is always the header. Rows above `start` and below `end` render once, as fixed rows. | `bodyRows [s, e] is out of range; require 1 <= start <= end <= R-1 (row 0 is the header)`; `has no <a:tbl> element (is it actually a table?)`. |
60
+
61
+ For an image block the shape must be a real picture: `is not a picture (missing <a:off>, <a:ext>, <p:blipFill>, or <p:cNvPr>)`.
62
+
63
+ Transplants: a block whose `sourceSlide` differs from the layout's `slideNumber` is copied from that other slide onto the cloned slide, positioned into the slot's `frame`. The shape the slot replaces on the base slide is removed. This is how one slot accepts both text and a table when the styled table lives on a different slide, as the worked example's `Content` layout does with a table from slide 15. The frame is the observed `<a:xfrm>` of the shape being replaced, so a text block in the same slot fills in place and the table lands exactly where the text was. The transplanted shape is positioned and sized to the frame, so its own size on the source slide does not matter.
64
+
65
+ ## `fonts`
66
+
67
+ Optional array of `@font-face` entries used only when rendering mermaid diagrams. PPTX text keeps the template's own fonts. Declare weights 400 and 700 for each family to avoid synthetic bold.
68
+
69
+ | Field | Meaning | Error when wrong |
70
+ |---|---|---|
71
+ | `family` | CSS family name; must equal the mermaid variant's `fontFamily`. Required. | Warning `fontFamily "X" matches none of the declared theme fonts`, then a substitute font. |
72
+ | `path` | A package specifier such as `@fontsource/inter/files/inter-latin-400-normal.woff2`, or a `./` or `/` file path. Required. | `could not resolve "path" from <rootDir>`; `file not found at ...`; `unsupported format ".otf"`. Supported: woff2, woff, ttf. |
73
+ | `weight` | OpenType weight. Optional, default 400. | None. |
74
+
75
+ ## `codeTheme`
76
+
77
+ Optional. A Shiki theme id such as `"github-light"`, or a pair `{ "light": "...", "dark": "..." }`. Required as soon as any deck uses a code fence: `deck contains a code fence but the theme declares no "codeTheme"`. A pair requires `variant` on every layout that takes code; each layout's `variant` picks the arm.
78
+
79
+ ## `mermaid`
80
+
81
+ Optional record of named color variants. The record keys are free; every variant must carry all eleven fields. Colors are hex strings.
82
+
83
+ | Key | Meaning |
84
+ |---|---|
85
+ | `primary` | Node fill color. |
86
+ | `primaryContrast` | Text color on `primary` nodes. |
87
+ | `text` | Default text, title and node label color. |
88
+ | `line` | Edge and arrow color. |
89
+ | `surface` | Subgraph, secondary node and edge-label background. |
90
+ | `surfaceBorder` | Border color for nodes and subgraphs. |
91
+ | `fontFamily` | CSS family name; match a `fonts` entry. |
92
+ | `accents` | Array of colors assigned in order to `class` groups in a flowchart. |
93
+ | `accentOpacity` | Percent, 0 to 100, applied to accent and subgraph fills. |
94
+ | `accentTextColor` | Text color inside accent-classed nodes. |
95
+ | `groupCornerRadius` | Subgraph corner radius in pixels; 0 for square. |
96
+
97
+ A missing block fails with `theme has no "mermaid" block`.
98
+
99
+ ## `mermaidVariant`
100
+
101
+ Optional string naming a key of `mermaid`. Required as soon as any deck has a mermaid fence: `declares no "mermaidVariant"`. A name that is not a key fails with `mermaid variant "x" not found in theme. Available variants: ...`. One variant per theme; pick the one matching most diagram layouts.
102
+
103
+ ## Minimal complete example
104
+
105
+ ```json
106
+ {
107
+ "template": "composition.pptx",
108
+ "layouts": [
109
+ {
110
+ "name": "Composed",
111
+ "slideNumber": 1,
112
+ "description": "Title over one body region.",
113
+ "parameters": [
114
+ { "shapeName": "Title 1", "template": "{title}", "required": true }
115
+ ],
116
+ "slots": [
117
+ {
118
+ "key": "body",
119
+ "frame": { "x": 457200, "y": 1371600, "cx": 8229600, "cy": 2743200 },
120
+ "accepts": [
121
+ { "type": "text", "sourceSlide": 1, "shapeName": "Text 1" },
122
+ { "type": "table", "sourceSlide": 2, "shapeName": "Table 0", "bodyRows": [1, 1] },
123
+ { "type": "image", "sourceSlide": 3, "shapeName": "Image 0" }
124
+ ]
125
+ }
126
+ ]
127
+ },
128
+ {
129
+ "name": "TextOnly",
130
+ "slideNumber": 4,
131
+ "parameters": [],
132
+ "slots": [
133
+ { "key": "body", "accepts": [{ "type": "text", "sourceSlide": 4, "shapeName": "Text 1" }] }
134
+ ]
135
+ }
136
+ ]
137
+ }
138
+ ```
139
+
140
+ `TextOnly` has no `frame` because its only block lives on its own slide. `Composed` needs one because two blocks are transplants.
141
+
142
+ ## Full example
143
+
144
+ The tycoworks theme is a complete 18-layout theme with a multi-accept slot, `variant`, a `codeTheme` pair, light and dark `mermaid` variants, and `fonts`: https://github.com/tycoworks/tycoworks-theme/blob/main/theme.json
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tycoworks/tycoslide",
3
- "version": "0.15.0",
4
- "description": "Create editable, on-brand PowerPoint slides from markdown, using your existing .pptx templates.",
3
+ "version": "0.16.0",
4
+ "description": "Create editable PowerPoint slides from markdown, using your existing .pptx templates.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -11,6 +11,7 @@
11
11
  "files": [
12
12
  "dist",
13
13
  "bin",
14
+ "docs",
14
15
  "theme-package"
15
16
  ],
16
17
  "engines": {
@@ -13,21 +13,22 @@ Before first use, install dependencies from the theme root:
13
13
 
14
14
  ```bash
15
15
  npm install
16
+ unzip -nq assets.dat
16
17
  ```
17
18
 
18
- This installs the tycoslide engine and its dependencies. You only need to do this once.
19
+ This installs the tycoslide engine and its dependencies, and unpacks the theme's images so you can copy them into decks. You only need to do this once. If there is no `assets.dat`, skip the unzip.
19
20
 
20
21
 
21
22
  ## Overview
22
23
 
23
- This skill builds on-brand decks from a markdown deck file. The theme provides slide layouts that control design. Your job: pick the right layouts, fill them with content, and build. You never restyle the layout; the engine clones the real slides, so brand, layout, fonts, and chrome come for free.
24
+ This skill builds decks from a markdown deck file. The theme provides slide layouts that control design. Your job: pick the right layouts, fill them with content, and build. You never restyle the layout; the engine clones the real slides, so brand, layout, fonts, and chrome come for free.
24
25
 
25
26
  ## Quick Reference
26
27
 
27
28
  | Task | Guide |
28
29
  |------|-------|
29
30
  | Discover layouts | Read `manifest.json` |
30
- | Find a logo, illustration or icon | Search `assets.json` |
31
+ | Find a logo, illustration or icon | Search `assets.json`, then copy it next to the deck (see [Images](#images)) |
31
32
  | Write a deck (structure, slots, assets) | See [Creating Slides](#creating-slides) below |
32
33
  | Fix build errors | See [QA](#qa-required) below |
33
34
 
@@ -35,88 +36,33 @@ This skill builds on-brand decks from a markdown deck file. The theme provides s
35
36
 
36
37
  ## Layout Discovery
37
38
 
38
- Before writing anything, read `manifest.json`. It lists the theme's **layouts** -- for each: `name`, `description`, `parameters` (frontmatter inputs) and `slots` (body regions). A layout is identified by its `name`; every parameter and slot by its `key`. Parameters carry a `type`, slots carry `accepts`, and either may be `required`.
39
+ Before writing anything, read `manifest.json`. It lists the theme's **layouts** -- for each: `name`, `description`, `parameters` (frontmatter inputs) and `slots` (body regions). A layout is identified by its `name`; every parameter and slot by its `key`. Slots carry `accepts`, and either may be `required`.
39
40
 
40
- Pictures live in `assets.json`: every logo, illustration and icon the theme offers, keyed by category and name. **Search it, do not read it whole** -- an icon set alone can run to thousands of entries. Grep for it and use the `$category.name` you find -- but **search for what the icon depicts, not what you mean by it**: a catalog is indexed by picture, so "freshness" finds nothing while `grep -i "clock" assets.json` and `grep -i "bolt"` find the icon you wanted.
41
-
42
- A layout's inputs split two ways (see [syntax.md](syntax.md) for details):
43
- - **parameters** -- one value on a frontmatter line. Fill by putting a value under the parameter's key in the slide frontmatter.
44
- - **slots** -- a multi-line region in the body. Accept types: `text`, `table`, `image`. Fill as a `::key::` region, using the slot's `key` from the manifest; a fenced code block routes to a `text` slot, a fenced mermaid block to an `image` slot.
45
-
46
- A slot may accept more than one content type -- the manifest lists each slot's `accepts`, and the shape of the content you write selects which one.
47
-
48
- Parameters and slots you leave unfilled are dropped from the slide, so a layout with numbered slots (several sections, stats, columns) renders only the ones you fill -- fill as many as you have.
41
+ A layout's inputs split two ways, parameters and slots; see [markdown.md](node_modules/@tycoworks/tycoslide/docs/markdown.md#parameters-and-slots) for how to fill each. Unfilled ones are dropped, so fill as many of a layout's numbered slots (sections, stats, columns) as you have content for.
49
42
 
50
43
  Study each layout's `slots` before writing any slides.
51
44
 
52
- ---
45
+ ### Images
53
46
 
54
- ## Creating Slides
47
+ Images live in `assets.json`: every logo, illustration and icon the theme offers, keyed by category and name, each with a `path`, a `fit` and a `description`. **Search it, do not read it whole** -- an icon set alone can run to thousands of entries. **Search for what the icon depicts, not what you mean by it**: a catalog is indexed by image, so "freshness" finds nothing while `grep -i "clock" assets.json` and `grep -i "bolt"` find the icon you wanted.
55
48
 
56
- > **Every layout, parameter, and slot name in the examples below is a placeholder.** Your theme's real names live in `manifest.json` — read it first, and never assume a name shown in an example exists in your theme.
49
+ To use an image, copy it into your deck's folder at the same relative path, then write its `path` and `fit` in the image:
57
50
 
58
- Write a deck file in markdown. The file starts with a global frontmatter block declaring the theme, followed by slides separated by `---`.
51
+ ```bash
52
+ mkdir -p <deck dir>/assets && cp <theme dir>/assets/hub.png <deck dir>/assets/
53
+ ```
59
54
 
60
55
  ```markdown
61
- ---
62
- theme: ./theme.json
63
- ---
64
-
65
- ---
66
- layout: Title # ← a layout from your manifest.json
67
- title: Q2 Business Review # ← a parameter that layout declares
68
- subtitle: Engineering Division
69
- ---
70
-
71
- ---
72
- layout: Quote dark
73
- quote: Great products are built by great teams.
74
- attributionName: Jane Smith
75
- attributionTitle: CEO, Acme Corp
76
- logo: assets/clients/acme.png
77
- ---
56
+ ![Central hub connecting three services](assets/hub.png "fit: scale-down")
78
57
  ```
79
58
 
80
- A text shape that holds several lines (e.g. an attribution with a name over a title) surfaces as one key per line -- fill each as its own scalar (`attributionName`, `attributionTitle` above), never as a YAML list.
81
-
82
- ### File structure
83
-
84
- A deck file has three parts:
59
+ If your deck is in the theme folder, the file is already there, so skip the copy. An image the user gives you also goes in the deck's folder; choose its fit by looking at it (`contain` unless it is full-bleed art that may crop). Write alt text for this slide, describing what the image shows here rather than repeating the catalog description.
85
60
 
86
- 1. **Global frontmatter** (required) -- the first `---`-delimited block. Must contain `theme:` pointing to the theme config file. The output `.pptx` is written next to the deck, named after it (`deck.md` → `deck.pptx`).
87
- 2. **Slides** -- each begins with a `---` separator. A slide's frontmatter sits between `---` delimiters. Body content follows the closing `---`.
88
- 3. **Slide separators** -- a `---` on its own line separates slides.
89
-
90
- ### Slide frontmatter
91
-
92
- Every slide must have a `layout:` key. All other frontmatter keys map 1:1 to the layout's **parameters**. A value containing a colon-then-space must be quoted -- `title: "The change: compute on the difference"` -- or YAML reads it as a second key and the build fails.
93
-
94
- ```yaml
95
61
  ---
96
- layout: Body
97
- title: Key Achievements
98
- subtitle: This Quarter
99
- ---
100
- ```
101
-
102
- - `layout` is required and consumed by the compiler (not forwarded as content).
103
- - All other frontmatter keys fill parameters: `title` fills the `title` parameter, `subtitle` fills the `subtitle` parameter, etc. A multi-line text shape surfaces as several keys (e.g. `name` + `jobTitle`); fill each as its own scalar line.
104
- - Slots (accepting `text`, `table`, `image`) are filled by body regions, not frontmatter -- see below.
105
- - A slide may also carry a `notes:` block in frontmatter -- plain-text speaker notes for the slide's notes page (see [syntax.md](syntax.md#speaker-notes)). It is slide-level metadata, not a parameter or slot.
106
-
107
- ### Body content, slots, and formatting
108
-
109
- See [syntax.md](syntax.md) for the full syntax reference: body content (paragraphs, bullets, nesting), inline formatting (bold, italic, strikethrough, underline, hyperlinks), named slots (`::key::` markers), and the parameter/slot split (slots accept: text, table, image).
110
-
111
- ### Build
112
62
 
113
- Build the deck (replace `<deck.md>` with your deck file):
114
-
115
- ```bash
116
- npx tycoslide build <deck.md>
117
- ```
63
+ ## Creating Slides
118
64
 
119
- The `.pptx` is written next to your deck file (same directory as `<deck.md>`).
65
+ Write a deck file in markdown, as [markdown.md](node_modules/@tycoworks/tycoslide/docs/markdown.md) describes (see its [full example](node_modules/@tycoworks/tycoslide/docs/markdown.md#full-example)), and build it as its [Build](node_modules/@tycoworks/tycoslide/docs/markdown.md#build) section shows.
120
66
 
121
67
  ---
122
68
 
@@ -142,8 +88,10 @@ Keep each slot's content to what its region comfortably holds. When content over
142
88
  - **Don't overstuff a slot** -- keep content to what its region comfortably holds; split across slides when there's too much
143
89
  - **Don't restyle the layout** -- the theme owns all design; you only fill slots
144
90
  - **Don't use an image that's wrong for the slot** -- a small slot wants a simple icon, not a dense illustration. If you get a `shrunk to X%` warning, look at the rendered slide: if the image is now too small to make out, use a simpler one. A `leaves X% of the frame empty` warning is the opposite: the image is a different shape from the slot. Neither fails the build -- judge both from the rendered slide.
145
- - **Don't invent layout or asset names** -- only use layouts from `manifest.json` and assets from `assets.json`
146
- - **Don't leave required parameters or slots empty** -- and don't leave a placeholder logo or dummy text in an image slot you care about
91
+ - **Don't skip alt text** -- describe what a meaningful image shows and why it's there, not "image of". Leave it empty only for decoration, such as backgrounds and icons beside a heading.
92
+ - **Don't crop what can't be cropped** -- copy the `fit` from `assets.json`. For your own images, never `fit: cover` a diagram, chart, screenshot or logo.
93
+ - **Don't invent layout names or image paths** -- layouts come from `manifest.json`, and theme images from `assets.json`, copied next to the deck
94
+ - **Don't leave required parameters or slots empty** -- and don't leave a placeholder logo or dummy text in an image slot you care about. If you don't have a suitable image, ask the user for one.
147
95
 
148
96
  ---
149
97
 
@@ -153,17 +101,12 @@ Keep each slot's content to what its region comfortably holds. When content over
153
101
 
154
102
  Your first draft almost never comes out clean. Approach QA as a debugging session, not a confirmation step. If you haven't run at least one build-fix cycle, you're not done.
155
103
 
156
- Build the deck again ([Build](#build)) and read the output carefully. Common errors and fixes:
104
+ Build the deck again ([Build](node_modules/@tycoworks/tycoslide/docs/markdown.md#build)) and read the output carefully. Common errors and fixes are in [markdown.md](node_modules/@tycoworks/tycoslide/docs/markdown.md#build), and layout names are in `manifest.json`. Also:
157
105
 
158
106
  | Error | Fix |
159
107
  |-------|-----|
160
- | `unknown layout "xyz"` | Check layout names in `manifest.json` |
161
- | A parameter or slot didn't fill | Use the key names the layout declares -- parameters in frontmatter, slots as body regions |
162
- | An image didn't swap / placeholder remains | Write a `::key::` region using the image slot's key, containing `![]($category.name)` from `assets.json` |
163
- | YAML parse error | Fix the YAML syntax in the slide's frontmatter |
164
- | `Skipped setting relation target` | The asset image couldn't be placed; check the path and file |
165
- | `forbidden style directive` | Remove `style`, `classDef`, `linkStyle`, or `%%{init}` from your mermaid block -- use `class` for grouping instead |
166
- | `no "mermaid" block` | The theme has no mermaid color config -- add a `mermaid` section to theme.json |
108
+ | An image didn't swap / placeholder remains | Write a `::key::` region using the image slot's key, containing `![alt](path "fit: …")` with the path relative to the deck |
109
+ | `image "…" not found at …` | For a theme image, copy it into the deck's folder at the path you wrote (search `assets.json`; did you unzip `assets.dat`?) |
167
110
 
168
111
  ### Verification Loop
169
112
 
@@ -211,13 +154,7 @@ Check for:
211
154
  For each issue, suggest a specific fix.
212
155
 
213
156
  Read: /path/to/deck.md and the rendered PNGs in the working directory
214
- Also read: manifest.json (for layout documentation); search assets.json for pictures
157
+ Also read: manifest.json (for layout documentation); search assets.json for images
215
158
  ```
216
159
 
217
160
  If the subagent finds issues, fix them and rebuild.
218
-
219
- ---
220
-
221
- ## Full Example
222
-
223
- See [syntax.md](syntax.md#full-example) for a complete multi-slide deck example.
@@ -1,18 +0,0 @@
1
- /**
2
- * Post-write sweep of relationships whose target part no longer exists.
3
- *
4
- * pptx-automizer's `cleanup` removes media parts that no shape embeds any more,
5
- * and `removeExistingSlides` drops the root template's original slide parts.
6
- * Neither touches the `.rels` files that named them, so a filled picture leaves
7
- * two dead entries behind (the template's placeholder image and automizer's
8
- * copy of it) and every removed template slide leaves an orphan rels file.
9
- * PowerPoint treats a relationship to a missing part as a damaged package.
10
- *
11
- * Runs on the written file: cleanup is the last thing automizer does during
12
- * `write()`, and it offers no hook after it.
13
- */
14
- export type PruneResult = {
15
- removedEntries: number;
16
- removedParts: number;
17
- };
18
- export declare function pruneDanglingRels(outputPath: string): Promise<PruneResult>;
@@ -1,55 +0,0 @@
1
- /**
2
- * Post-write sweep of relationships whose target part no longer exists.
3
- *
4
- * pptx-automizer's `cleanup` removes media parts that no shape embeds any more,
5
- * and `removeExistingSlides` drops the root template's original slide parts.
6
- * Neither touches the `.rels` files that named them, so a filled picture leaves
7
- * two dead entries behind (the template's placeholder image and automizer's
8
- * copy of it) and every removed template slide leaves an orphan rels file.
9
- * PowerPoint treats a relationship to a missing part as a damaged package.
10
- *
11
- * Runs on the written file: cleanup is the last thing automizer does during
12
- * `write()`, and it offers no hook after it.
13
- */
14
- import { readFileSync, writeFileSync } from "node:fs";
15
- import { posix } from "node:path";
16
- import { DOMParser, XMLSerializer } from "@xmldom/xmldom";
17
- import JSZip from "jszip";
18
- import { Attr, Tag } from "./dom.js";
19
- const SLIDE_RELS = /^ppt\/slides\/_rels\/(slide\d+\.xml)\.rels$/;
20
- const SLIDES_DIR = "ppt/slides";
21
- export async function pruneDanglingRels(outputPath) {
22
- const zip = await JSZip.loadAsync(readFileSync(outputPath));
23
- const parts = new Set(Object.keys(zip.files).filter((name) => !zip.files[name].dir));
24
- const result = { removedEntries: 0, removedParts: 0 };
25
- for (const relsPart of [...parts]) {
26
- const match = SLIDE_RELS.exec(relsPart);
27
- if (!match)
28
- continue;
29
- if (!parts.has(`${SLIDES_DIR}/${match[1]}`)) {
30
- zip.remove(relsPart);
31
- result.removedParts++;
32
- continue;
33
- }
34
- const doc = new DOMParser().parseFromString(await zip.file(relsPart).async("string"), "text/xml");
35
- const rels = doc.getElementsByTagName(Tag.RELATIONSHIP);
36
- const dead = [];
37
- for (let i = 0; i < rels.length; i++) {
38
- const target = rels[i].getAttribute(Attr.TARGET);
39
- if (!target || rels[i].getAttribute("TargetMode") === "External")
40
- continue;
41
- if (!parts.has(posix.normalize(posix.join(SLIDES_DIR, target))))
42
- dead.push(rels[i]);
43
- }
44
- if (dead.length === 0)
45
- continue;
46
- for (const rel of dead)
47
- rel.parentNode?.removeChild(rel);
48
- result.removedEntries += dead.length;
49
- zip.file(relsPart, new XMLSerializer().serializeToString(doc));
50
- }
51
- if (result.removedEntries > 0 || result.removedParts > 0) {
52
- writeFileSync(outputPath, await zip.generateAsync({ type: "nodebuffer", compression: "DEFLATE" }));
53
- }
54
- return result;
55
- }
@@ -1,5 +0,0 @@
1
- import type { CompilerThemeConfig } from "./markdown/types.js";
2
- /** The layouts document: read whole, so it carries no open-ended list. */
3
- export declare function generateManifest(config: CompilerThemeConfig): string;
4
- /** The catalog document: searched by name, never read whole. */
5
- export declare function generateAssetCatalog(config: CompilerThemeConfig): string;
@@ -1,49 +0,0 @@
1
- import type { CompilerThemeConfig } from "./markdown/types.js";
2
- /**
3
- * Pack `paths` (theme-relative, POSIX) into one archive, reading each through
4
- * `read`. Entries keep their declared paths, so expanding reproduces the layout
5
- * `theme.json` already refers to.
6
- */
7
- export declare function packAssets(paths: string[], read: (rel: string) => Buffer): Promise<Buffer>;
8
- /**
9
- * Expand a packaged theme's archive into its directory, so the files the catalog
10
- * names are on disk before anything fills with them. Idempotent per file; loose
11
- * files win, so a stale archive never overwrites a theme's real assets; the
12
- * archive itself stays put. A theme with no archive -- every theme under
13
- * development -- returns immediately.
14
- */
15
- export declare function expandAssets(rootDir: string): Promise<void>;
16
- /**
17
- * Rewrite the `name:` value in a SKILL.md's leading YAML frontmatter so the
18
- * packaged skill is named after the consuming theme, not the source template.
19
- * Only the leading `---`…`---` block is touched; the body is left byte-for-byte.
20
- * Throws if there is no frontmatter or no `name:` line — the caller names the file.
21
- */
22
- export declare function renameSkill(md: string, name: string): string;
23
- /**
24
- * The `package.json` a packaged skill installs from — deliberately NOT the theme's
25
- * own. A theme repo's manifest is a development document: it carries the script
26
- * that regenerates the skill, and lists the engine as a devDependency because the
27
- * repo builds with it rather than shipping it.
28
- *
29
- * Copying that verbatim breaks the consumer twice. The build script runs as a
30
- * postinstall inside their container, so anything it touches that is read-only
31
- * fails their whole `npm install`. And under `--omit=dev` the engine is never
32
- * installed, so neither the postinstall nor `npx tycoslide build` can find it.
33
- *
34
- * What ships instead declares only what the skill needs to RUN: the theme's own
35
- * dependencies plus the engine, as runtime dependencies, and no scripts at all.
36
- */
37
- export declare function skillPackageJson(theme: Record<string, unknown>, engine: {
38
- name: string;
39
- version: string;
40
- }): string;
41
- /**
42
- * Zip a theme into an uploadable Agent Skill archive whose entries all live
43
- * under a single root folder (e.g. `acme-slides/theme.json`), matching Anthropic's
44
- * custom-skill format. `generated` names the files the caller just wrote (the
45
- * config, manifest, SKILL.md, syntax.md); `packageJson` is the authored manifest
46
- * from `skillPackageJson`. Optional support files are skipped when absent;
47
- * anything the config declares but that is missing is an error.
48
- */
49
- export declare function zipDir(rootDir: string, folderName: string, config: CompilerThemeConfig, generated: string[], packageJson: string): Promise<Buffer>;