@tycoworks/tycoslide 0.11.0 → 0.11.2

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/README.md CHANGED
@@ -16,6 +16,8 @@ Create editable, on-brand PowerPoint slides from markdown.
16
16
  npm install @tycoworks/tycoslide
17
17
  ```
18
18
 
19
+ Installing also downloads Chromium (~554MB), via `@playwright/browser-chromium`, which tycoslide uses to render mermaid diagrams. If you install with `--ignore-scripts`, run `npx playwright install chromium-headless-shell` before building a deck with diagrams.
20
+
19
21
  Create `deck.md`:
20
22
 
21
23
  ```markdown
package/SKILL.md CHANGED
@@ -162,7 +162,6 @@ Build the deck again ([Build](#build)) and read the output carefully. Common err
162
162
  | YAML parse error | Fix the YAML syntax in the slide's frontmatter |
163
163
  | `Skipped setting relation target` | The asset image couldn't be placed; check the path and file |
164
164
  | `forbidden style directive` | Remove `style`, `classDef`, `linkStyle`, or `%%{init}` from your mermaid block -- use `class` for grouping instead |
165
- | `Mermaid render failed` | The engine renders diagrams with Playwright's Chromium; install it once with `npx playwright install chromium` |
166
165
  | `no "mermaid" block` | The theme has no mermaid color config -- add a `mermaid` section to theme.json |
167
166
 
168
167
  ### Verification Loop
package/dist/cli.js CHANGED
@@ -79,7 +79,8 @@ program
79
79
  // Bundle the WHOLE theme so the skill is self-contained: unzip ->
80
80
  // `npm install` (pulls the engine + its deps) -> `npx tycoslide build`.
81
81
  const zipFile = `${skillName}.zip`;
82
- writeFileSync(resolve(process.cwd(), zipFile), await zipDir(process.cwd(), skillName));
82
+ const generated = [opts.config, MANIFEST_FILE, SKILL_FILE, SYNTAX_FILE];
83
+ writeFileSync(resolve(process.cwd(), zipFile), await zipDir(process.cwd(), skillName, config, generated));
83
84
  console.log(`WROTE ${zipFile}`);
84
85
  });
85
86
  await program.parseAsync(process.argv);
@@ -33,7 +33,7 @@ import { Automizer, modify } from "pptx-automizer";
33
33
  import { FILLERS } from "./fillers/filler.js";
34
34
  import { isImageFill } from "./fillers/image.js";
35
35
  import { applyNotesToSlide, sweepOrphanNotes } from "./notes.js";
36
- import { SlotType } from "./types.js";
36
+ import { SlotType, TEMPLATE_DIR, } from "./types.js";
37
37
  /**
38
38
  * Generate a PPTX file from a deck definition and a theme configuration.
39
39
  *
@@ -53,7 +53,7 @@ export async function generate(deck, config, options = {}) {
53
53
  const outDir = dirname(deck.output);
54
54
  const outFile = basename(deck.output);
55
55
  const automizer = new Automizer({
56
- templateDir: resolve(rootDir, "template"),
56
+ templateDir: resolve(rootDir, TEMPLATE_DIR),
57
57
  outputDir: outDir,
58
58
  removeExistingSlides: true,
59
59
  autoImportSlideMasters: true,
@@ -6,4 +6,4 @@ export { fillText, isTextFill } from "./fillers/text.js";
6
6
  export type { GenerateOptions } from "./generate.js";
7
7
  export { generate } from "./generate.js";
8
8
  export type { Block, BodyRows, Config, Deck, DeckStep, Frame, ImageFill, Layout, Slot, StyledParagraph, TableFill, TemplateFill, TemplateSegment, TextFill, TextRun, ThemeConfig, } from "./types.js";
9
- export { ImageFit, SlotType } from "./types.js";
9
+ export { ImageFit, SlotType, TEMPLATE_DIR } from "./types.js";
@@ -4,4 +4,4 @@ export { fillTable, isTableFill } from "./fillers/table.js";
4
4
  export { fillTemplate } from "./fillers/template.js";
5
5
  export { fillText, isTextFill } from "./fillers/text.js";
6
6
  export { generate } from "./generate.js";
7
- export { ImageFit, SlotType } from "./types.js";
7
+ export { ImageFit, SlotType, TEMPLATE_DIR } from "./types.js";
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Theme subdirectory holding the source `.pptx` template. Fixed by convention:
3
+ * `theme.json` names only the file, so the engine resolves the template under
4
+ * this directory and packaging reads it from the same place.
5
+ */
6
+ export declare const TEMPLATE_DIR = "template";
1
7
  /**
2
8
  * Fill-strategy discriminator carried on every Slot. Required — there is no
3
9
  * silent default. The engine dispatches to fillTemplate / fillText / fillTable /
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Theme subdirectory holding the source `.pptx` template. Fixed by convention:
3
+ * `theme.json` names only the file, so the engine resolves the template under
4
+ * this directory and packaging reads it from the same place.
5
+ */
6
+ export const TEMPLATE_DIR = "template";
1
7
  /**
2
8
  * Fill-strategy discriminator carried on every Slot. Required — there is no
3
9
  * silent default. The engine dispatches to fillTemplate / fillText / fillTable /
@@ -1,3 +1,4 @@
1
+ import type { CompilerThemeConfig } from "./markdown/types.js";
1
2
  /**
2
3
  * Rewrite the `name:` value in a skill.md's leading YAML frontmatter so the
3
4
  * packaged skill is named after the consuming theme, not the source template.
@@ -6,12 +7,10 @@
6
7
  */
7
8
  export declare function renameSkill(md: string, name: string): string;
8
9
  /**
9
- * Zip an entire theme directory into an uploadable Agent Skill archive whose
10
- * entries all live under a single root folder (e.g. `mz-slides/theme.json`),
11
- * matching Anthropic's custom-skill format. Recursively includes every file
12
- * except node_modules, hidden entries (any name starting with `.`), and
13
- * top-level build artifacts (.pptx/.pdf/.zip at the repo root; the template
14
- * .pptx under template/ is kept). Subdirectory structure is preserved with
15
- * POSIX slashes. Fails fast if nothing is left to zip.
10
+ * Zip a theme into an uploadable Agent Skill archive whose entries all live
11
+ * under a single root folder (e.g. `mz-slides/theme.json`), matching Anthropic's
12
+ * custom-skill format. `generated` names the files the caller just wrote (the
13
+ * config, manifest, skill.md, syntax.md). Optional support files are skipped
14
+ * when absent; anything the config declares but that is missing is an error.
16
15
  */
17
- export declare function zipDir(rootDir: string, folderName: string): Promise<Buffer>;
16
+ export declare function zipDir(rootDir: string, folderName: string, config: CompilerThemeConfig, generated: string[]): Promise<Buffer>;
package/dist/skillZip.js CHANGED
@@ -1,6 +1,7 @@
1
- import { readdirSync, readFileSync } from "node:fs";
2
- import { extname, join, relative, sep } from "node:path";
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
3
  import JSZip from "jszip";
4
+ import { TEMPLATE_DIR } from "./engine/index.js";
4
5
  const FRONTMATTER = /^---\n([\s\S]*?)\n---/;
5
6
  const NAME_LINE = /^name:[ \t]*.*$/m;
6
7
  /**
@@ -17,47 +18,50 @@ export function renameSkill(md, name) {
17
18
  throw new Error('skill.md frontmatter has no "name:" line');
18
19
  return md.replace(block[0], block[0].replace(NAME_LINE, `name: ${name}`));
19
20
  }
20
- // Never packaged: dependencies (npm install rebuilds them) and hidden entries
21
- // (name starting with "." — covers VCS, tooling, caches, secrets like .env/.npmrc).
22
- const EXCLUDE_DIRS = new Set(["node_modules"]);
23
- // Build artifacts, dropped ONLY at the repo root: decks build to cwd
24
- // (showcase.pptx, deck.pptx, the output .zip), while the template .pptx lives
25
- // under template/ and must be kept — so these extensions are pruned top-level only.
26
- const ROOT_ARTIFACT_EXTS = new Set([".pptx", ".pdf", ".zip"]);
27
21
  /**
28
- * Zip an entire theme directory into an uploadable Agent Skill archive whose
29
- * entries all live under a single root folder (e.g. `mz-slides/theme.json`),
30
- * matching Anthropic's custom-skill format. Recursively includes every file
31
- * except node_modules, hidden entries (any name starting with `.`), and
32
- * top-level build artifacts (.pptx/.pdf/.zip at the repo root; the template
33
- * .pptx under template/ is kept). Subdirectory structure is preserved with
34
- * POSIX slashes. Fails fast if nothing is left to zip.
22
+ * Files a packaged skill needs beyond the theme's own declarations. `package.json`
23
+ * matters most: the unzip flow is `npm install` -> `npx tycoslide build`, so it
24
+ * restores the engine and any npm-resolved brand fonts. The lockfile is taken
25
+ * when present so that install is reproducible.
35
26
  */
36
- export async function zipDir(rootDir, folderName) {
27
+ const SUPPORT_FILES = ["package.json", "package-lock.json"];
28
+ /**
29
+ * Every path a packaged theme needs, relative to `rootDir` and POSIX-separated.
30
+ *
31
+ * Derived from the theme config rather than filtered out of a directory walk:
32
+ * the config already declares its template and its whole asset catalog, so an
33
+ * allowlist stays correct no matter what else sits in the working directory --
34
+ * built decks, PDFs, slide PNGs, scratch files. Font paths are deliberately
35
+ * absent: they resolve from node_modules, which `npm install` restores.
36
+ */
37
+ function skillPaths(config, generated) {
38
+ const assets = Object.values(config.assets).flatMap((category) => Object.values(category).map((entry) => entry.path));
39
+ return [...SUPPORT_FILES, ...generated, `${TEMPLATE_DIR}/${config.template}`, ...assets];
40
+ }
41
+ /**
42
+ * Zip a theme into an uploadable Agent Skill archive whose entries all live
43
+ * under a single root folder (e.g. `mz-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). Optional support files are skipped
46
+ * when absent; anything the config declares but that is missing is an error.
47
+ */
48
+ export async function zipDir(rootDir, folderName, config, generated) {
37
49
  const zip = new JSZip();
38
50
  const folder = zip.folder(folderName);
39
51
  if (!folder)
40
52
  throw new Error(`Failed to create zip folder: ${folderName}`);
53
+ const optional = new Set(SUPPORT_FILES);
41
54
  let count = 0;
42
- const walk = (dir) => {
43
- const atRoot = dir === rootDir;
44
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
45
- if (entry.name.startsWith("."))
55
+ for (const rel of skillPaths(config, generated)) {
56
+ const abs = join(rootDir, ...rel.split("/"));
57
+ if (!existsSync(abs)) {
58
+ if (optional.has(rel))
46
59
  continue;
47
- const abs = join(dir, entry.name);
48
- if (entry.isDirectory()) {
49
- if (!EXCLUDE_DIRS.has(entry.name))
50
- walk(abs);
51
- }
52
- else if (entry.isFile()) {
53
- if (atRoot && ROOT_ARTIFACT_EXTS.has(extname(entry.name)))
54
- continue;
55
- folder.file(relative(rootDir, abs).split(sep).join("/"), readFileSync(abs));
56
- count++;
57
- }
60
+ throw new Error(`Theme declares "${rel}", but no such file exists`);
58
61
  }
59
- };
60
- walk(rootDir);
62
+ folder.file(rel, readFileSync(abs));
63
+ count++;
64
+ }
61
65
  if (count === 0)
62
66
  throw new Error(`No files to zip in directory: ${rootDir}`);
63
67
  return zip.generateAsync({ type: "nodebuffer" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tycoworks/tycoslide",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
4
4
  "description": "Create editable, on-brand PowerPoint slides from markdown.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -23,12 +23,13 @@
23
23
  "format": "biome format --write ."
24
24
  },
25
25
  "dependencies": {
26
+ "@playwright/browser-chromium": "^1.62.1",
26
27
  "@xmldom/xmldom": "^0.9.10",
27
28
  "commander": "^15.0.0",
28
29
  "image-size": "^2.0.2",
29
30
  "jszip": "^3.10.1",
30
31
  "mermaid": "^11.16.1",
31
- "playwright": "^1.58.2",
32
+ "playwright": "^1.62.1",
32
33
  "pptx-automizer": "^0.8.2",
33
34
  "remark-gfm": "^4.0.1",
34
35
  "remark-ins": "^1.2.5",