@ncukondo/slide-generation 0.7.0 → 0.8.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 (34) hide show
  1. package/dist/cli/index.js +588 -321
  2. package/dist/cli/index.js.map +1 -1
  3. package/dist/index.d.ts +33 -1
  4. package/dist/index.js +273 -15
  5. package/dist/index.js.map +1 -1
  6. package/icons/fetched/LICENSE +202 -0
  7. package/icons/fetched/NOTICE +14 -0
  8. package/icons/fetched/heroicons/arrow-right.svg +1 -0
  9. package/icons/fetched/material-icons/account_balance.svg +1 -0
  10. package/icons/fetched/material-icons/analytics.svg +1 -0
  11. package/icons/fetched/material-icons/business.svg +1 -0
  12. package/icons/fetched/material-icons/check_circle.svg +1 -0
  13. package/icons/fetched/material-icons/computer.svg +1 -0
  14. package/icons/fetched/material-icons/description.svg +1 -0
  15. package/icons/fetched/material-icons/done_all.svg +1 -0
  16. package/icons/fetched/material-icons/event_note.svg +1 -0
  17. package/icons/fetched/material-icons/lightbulb.svg +1 -0
  18. package/icons/fetched/material-icons/person.svg +1 -0
  19. package/icons/fetched/material-icons/play_arrow.svg +1 -0
  20. package/icons/fetched/material-icons/replay.svg +1 -0
  21. package/icons/fetched/material-icons/thumb_up.svg +1 -0
  22. package/icons/fetched/material-icons/trending_up.svg +1 -0
  23. package/icons/registry.yaml +1 -0
  24. package/package.json +1 -1
  25. package/templates/diagrams/flow-chart.yaml +5 -5
  26. package/templates/diagrams/hierarchy.yaml +4 -1
  27. package/templates/layouts/before-after.yaml +15 -15
  28. package/templates/layouts/gallery.yaml +12 -12
  29. package/templates/layouts/image-caption.yaml +6 -6
  30. package/templates/layouts/image-full.yaml +9 -9
  31. package/templates/layouts/image-text.yaml +26 -24
  32. package/templates/layouts/three-column.yaml +5 -5
  33. package/templates/layouts/two-column.yaml +3 -3
  34. package/icons/fetched/.gitkeep +0 -0
package/dist/cli/index.js CHANGED
@@ -338,6 +338,13 @@ ${slidesContent}`;
338
338
  lines.push(`${key}: ${this.formatFrontMatterValue(value)}`);
339
339
  }
340
340
  }
341
+ if (options?.templateCss && options.templateCss.trim()) {
342
+ lines.push("style: |");
343
+ const cssLines = options.templateCss.split("\n");
344
+ for (const line of cssLines) {
345
+ lines.push(` ${line}`);
346
+ }
347
+ }
341
348
  lines.push("---");
342
349
  return lines.join("\n");
343
350
  }
@@ -374,7 +381,7 @@ ${this.renderSpeakerNotes(note)}`;
374
381
  }
375
382
  parts.push(slideContent);
376
383
  }
377
- return parts.map((slide) => `---
384
+ return parts.map((slide, index) => index === 0 ? slide : `---
378
385
 
379
386
  ${slide}`).join("\n\n");
380
387
  }
@@ -615,6 +622,46 @@ var init_loader = __esm({
615
622
  }
616
623
  });
617
624
 
625
+ // src/templates/css-collector.ts
626
+ var CSSCollector;
627
+ var init_css_collector = __esm({
628
+ "src/templates/css-collector.ts"() {
629
+ "use strict";
630
+ CSSCollector = class {
631
+ constructor(templateLoader) {
632
+ this.templateLoader = templateLoader;
633
+ }
634
+ /**
635
+ * Collect CSS from the specified templates
636
+ * @param templateNames - Names of templates to collect CSS from
637
+ * @returns Combined CSS string from all templates
638
+ */
639
+ collect(templateNames) {
640
+ const uniqueNames = [...new Set(templateNames)];
641
+ const cssBlocks = [];
642
+ for (const name of uniqueNames) {
643
+ const template = this.templateLoader.get(name);
644
+ if (template?.css) {
645
+ cssBlocks.push(template.css);
646
+ }
647
+ }
648
+ return cssBlocks.join("\n\n");
649
+ }
650
+ };
651
+ }
652
+ });
653
+
654
+ // src/templates/index.ts
655
+ var init_templates = __esm({
656
+ "src/templates/index.ts"() {
657
+ "use strict";
658
+ init_engine();
659
+ init_loader();
660
+ init_validators();
661
+ init_css_collector();
662
+ }
663
+ });
664
+
618
665
  // src/icons/schema.ts
619
666
  import { z as z4 } from "zod";
620
667
  var iconSourceTypeSchema, iconSourceSchema, iconDefaultsSchema, iconRegistrySchema;
@@ -757,24 +804,217 @@ var init_registry = __esm({
757
804
  }
758
805
  });
759
806
 
760
- // src/icons/resolver.ts
807
+ // src/icons/fetcher.ts
761
808
  import * as fs3 from "fs/promises";
762
809
  import * as path2 from "path";
810
+ import { stringify as stringifyYaml, parse as parseYaml3 } from "yaml";
811
+ function isExternalSource(prefix) {
812
+ return ICON_SOURCES[prefix]?.external ?? false;
813
+ }
814
+ function isValidIconName(name, allowPath = false) {
815
+ if (!name || name.length > 100) {
816
+ return false;
817
+ }
818
+ if (name.includes("..") || name.startsWith("/") || name.endsWith("/")) {
819
+ return false;
820
+ }
821
+ const pattern = allowPath ? VALID_ICON_NAME_WITH_PATH : VALID_ICON_NAME;
822
+ return pattern.test(name);
823
+ }
824
+ var ICON_SOURCES, VALID_ICON_NAME, VALID_ICON_NAME_WITH_PATH, IconFetcher;
825
+ var init_fetcher = __esm({
826
+ "src/icons/fetcher.ts"() {
827
+ "use strict";
828
+ ICON_SOURCES = {
829
+ health: { set: "healthicons", license: "MIT", external: true },
830
+ ms: { set: "material-symbols", license: "Apache-2.0", external: true },
831
+ hero: { set: "heroicons", license: "MIT", external: true },
832
+ mi: { set: "material-icons", license: "Apache-2.0", external: false },
833
+ mdi: { set: "mdi", license: "Apache-2.0", external: true },
834
+ iconify: { set: "iconify", license: "Various", external: true }
835
+ };
836
+ VALID_ICON_NAME = /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/i;
837
+ VALID_ICON_NAME_WITH_PATH = /^[a-z0-9][a-z0-9-/]*[a-z0-9]$|^[a-z0-9]$/i;
838
+ IconFetcher = class {
839
+ fetchedDir;
840
+ saveLocally;
841
+ timeoutMs;
842
+ constructor(options = {}) {
843
+ this.fetchedDir = options.fetchedDir ?? "icons/fetched";
844
+ this.saveLocally = options.saveLocally ?? true;
845
+ this.timeoutMs = options.timeoutMs ?? 1e4;
846
+ }
847
+ /**
848
+ * Parse an icon reference string (e.g., "health:stethoscope")
849
+ * Returns null if the format is invalid or contains unsafe characters
850
+ */
851
+ parseReference(iconRef) {
852
+ const colonIndex = iconRef.indexOf(":");
853
+ if (colonIndex === -1) {
854
+ return null;
855
+ }
856
+ const prefix = iconRef.substring(0, colonIndex);
857
+ const name = iconRef.substring(colonIndex + 1);
858
+ if (!/^[a-z0-9]+$/i.test(prefix)) {
859
+ return null;
860
+ }
861
+ if (!isValidIconName(name)) {
862
+ return null;
863
+ }
864
+ return { prefix, name };
865
+ }
866
+ /**
867
+ * Get the Iconify set name for a prefix
868
+ */
869
+ getIconifySet(prefix) {
870
+ return ICON_SOURCES[prefix]?.set ?? prefix;
871
+ }
872
+ /**
873
+ * Get the local file path for an icon reference
874
+ */
875
+ getLocalPath(iconRef) {
876
+ const parsed = this.parseReference(iconRef);
877
+ if (!parsed) {
878
+ throw new Error(`Invalid icon reference: ${iconRef}`);
879
+ }
880
+ const setDir = this.getIconifySet(parsed.prefix);
881
+ return path2.join(this.fetchedDir, setDir, `${parsed.name}.svg`);
882
+ }
883
+ /**
884
+ * Check if an icon exists locally
885
+ */
886
+ async existsLocally(iconRef) {
887
+ const localPath = this.getLocalPath(iconRef);
888
+ try {
889
+ await fs3.access(localPath);
890
+ return true;
891
+ } catch {
892
+ return false;
893
+ }
894
+ }
895
+ /**
896
+ * Build the Iconify API URL for an icon
897
+ */
898
+ buildUrl(prefix, name) {
899
+ const set = this.getIconifySet(prefix);
900
+ return `https://api.iconify.design/${set}/${name}.svg`;
901
+ }
902
+ /**
903
+ * Resolve an icon reference (local first, then fetch)
904
+ */
905
+ async resolve(iconRef) {
906
+ if (await this.existsLocally(iconRef)) {
907
+ const localPath = this.getLocalPath(iconRef);
908
+ return await fs3.readFile(localPath, "utf-8");
909
+ }
910
+ return await this.fetchAndSave(iconRef);
911
+ }
912
+ /**
913
+ * Fetch an icon from external source and save locally
914
+ */
915
+ async fetchAndSave(iconRef) {
916
+ const parsed = this.parseReference(iconRef);
917
+ if (!parsed) {
918
+ throw new Error(`Invalid icon reference: ${iconRef}`);
919
+ }
920
+ const url = this.buildUrl(parsed.prefix, parsed.name);
921
+ const controller = new AbortController();
922
+ const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
923
+ let response;
924
+ try {
925
+ response = await fetch(url, { signal: controller.signal });
926
+ } catch (error) {
927
+ if (error instanceof Error && error.name === "AbortError") {
928
+ throw new Error(`Fetch timeout: ${iconRef} (exceeded ${this.timeoutMs}ms)`);
929
+ }
930
+ throw error;
931
+ } finally {
932
+ clearTimeout(timeoutId);
933
+ }
934
+ if (!response.ok) {
935
+ throw new Error(`Icon not found: ${iconRef} (HTTP ${response.status})`);
936
+ }
937
+ const svg = await response.text();
938
+ if (this.saveLocally) {
939
+ await this.saveLocallyInternal(parsed.prefix, parsed.name, svg, url);
940
+ }
941
+ return svg;
942
+ }
943
+ /**
944
+ * Save icon to local filesystem
945
+ */
946
+ async saveLocallyInternal(prefix, name, svg, sourceUrl) {
947
+ const setDir = this.getIconifySet(prefix);
948
+ const dirPath = path2.join(this.fetchedDir, setDir);
949
+ const filePath = path2.join(dirPath, `${name}.svg`);
950
+ await fs3.mkdir(dirPath, { recursive: true });
951
+ await fs3.writeFile(filePath, svg, "utf-8");
952
+ await this.updateSourcesYaml(setDir, name, sourceUrl);
953
+ }
954
+ /**
955
+ * Update _sources.yaml with source information
956
+ */
957
+ async updateSourcesYaml(setDir, name, sourceUrl) {
958
+ const sourcesPath = path2.join(this.fetchedDir, "_sources.yaml");
959
+ let sources = {};
960
+ try {
961
+ const content = await fs3.readFile(sourcesPath, "utf-8");
962
+ sources = parseYaml3(content) ?? {};
963
+ } catch {
964
+ }
965
+ const key = `${setDir}/${name}.svg`;
966
+ sources[key] = {
967
+ source: sourceUrl,
968
+ fetched_at: (/* @__PURE__ */ new Date()).toISOString(),
969
+ license: this.getLicense(setDir)
970
+ };
971
+ const header = `# Fetched icon sources
972
+ # This file tracks where icons were fetched from for traceability
973
+
974
+ `;
975
+ await fs3.writeFile(sourcesPath, header + stringifyYaml(sources), "utf-8");
976
+ }
977
+ /**
978
+ * Get the license for an icon set
979
+ */
980
+ getLicense(setDir) {
981
+ for (const config of Object.values(ICON_SOURCES)) {
982
+ if (config.set === setDir) {
983
+ return config.license;
984
+ }
985
+ }
986
+ return "Unknown";
987
+ }
988
+ };
989
+ }
990
+ });
991
+
992
+ // src/icons/resolver.ts
993
+ import * as fs4 from "fs/promises";
994
+ import * as path3 from "path";
763
995
  import nunjucks2 from "nunjucks";
764
996
  var IconResolver;
765
997
  var init_resolver = __esm({
766
998
  "src/icons/resolver.ts"() {
767
999
  "use strict";
1000
+ init_fetcher();
768
1001
  IconResolver = class {
769
1002
  constructor(registry, options = {}) {
770
1003
  this.registry = registry;
771
1004
  this.nunjucksEnv = new nunjucks2.Environment(null, {
772
1005
  autoescape: false
773
1006
  });
774
- this.options = options;
1007
+ this.options = {
1008
+ autoFetch: true,
1009
+ ...options
1010
+ };
1011
+ this.fetcher = new IconFetcher({
1012
+ fetchedDir: options.fetchedDir ?? "icons/fetched"
1013
+ });
775
1014
  }
776
1015
  nunjucksEnv;
777
1016
  options;
1017
+ fetcher;
778
1018
  /**
779
1019
  * Render an icon by name or alias
780
1020
  */
@@ -796,6 +1036,17 @@ var init_resolver = __esm({
796
1036
  color: this.resolveColor(options?.color) ?? defaults.color,
797
1037
  ...options?.class !== void 0 ? { class: options.class } : {}
798
1038
  };
1039
+ const cachedSvg = await this.tryGetCachedSvg(parsed.prefix, parsed.name);
1040
+ if (cachedSvg) {
1041
+ return this.processSvg(cachedSvg, parsed.name, mergedOptions);
1042
+ }
1043
+ if (this.options.autoFetch && this.isFetchableSource(parsed.prefix)) {
1044
+ try {
1045
+ const fetchedSvg = await this.fetcher.fetchAndSave(`${parsed.prefix}:${parsed.name}`);
1046
+ return this.processSvg(fetchedSvg, parsed.name, mergedOptions);
1047
+ } catch {
1048
+ }
1049
+ }
799
1050
  switch (source.type) {
800
1051
  case "web-font":
801
1052
  return this.renderWebFont(source, parsed.name, mergedOptions);
@@ -809,6 +1060,29 @@ var init_resolver = __esm({
809
1060
  throw new Error(`Unsupported icon source type: "${source.type}"`);
810
1061
  }
811
1062
  }
1063
+ /**
1064
+ * Try to get a cached SVG from the fetched directory
1065
+ */
1066
+ async tryGetCachedSvg(prefix, name) {
1067
+ const iconSourceConfig = ICON_SOURCES[prefix];
1068
+ if (!iconSourceConfig) {
1069
+ return null;
1070
+ }
1071
+ const setDir = iconSourceConfig.set;
1072
+ const fetchedDir = this.options.fetchedDir ?? "icons/fetched";
1073
+ const svgPath = path3.join(fetchedDir, setDir, `${name}.svg`);
1074
+ try {
1075
+ return await fs4.readFile(svgPath, "utf-8");
1076
+ } catch {
1077
+ return null;
1078
+ }
1079
+ }
1080
+ /**
1081
+ * Check if a source prefix is fetchable from external API
1082
+ */
1083
+ isFetchableSource(prefix) {
1084
+ return prefix in ICON_SOURCES;
1085
+ }
812
1086
  /**
813
1087
  * Render a web-font icon
814
1088
  */
@@ -833,9 +1107,9 @@ var init_resolver = __esm({
833
1107
  if (!source.path) {
834
1108
  throw new Error(`Local SVG source "${source.name}" has no path defined`);
835
1109
  }
836
- const svgPath = path2.join(source.path, `${name}.svg`);
1110
+ const svgPath = path3.join(source.path, `${name}.svg`);
837
1111
  try {
838
- const svgContent = await fs3.readFile(svgPath, "utf-8");
1112
+ const svgContent = await fs4.readFile(svgPath, "utf-8");
839
1113
  return this.processSvg(svgContent, name, options);
840
1114
  } catch (error) {
841
1115
  if (error.code === "ENOENT") {
@@ -982,6 +1256,8 @@ var init_manager = __esm({
982
1256
  }
983
1257
  /**
984
1258
  * Get multiple references by IDs using ref export for better performance
1259
+ * Note: ref export returns exit code 1 when references are not found,
1260
+ * but still outputs valid JSON (empty array or partial results) to stdout
985
1261
  */
986
1262
  async getByIds(ids) {
987
1263
  if (ids.length === 0) {
@@ -989,7 +1265,8 @@ var init_manager = __esm({
989
1265
  }
990
1266
  const idsArg = ids.map((id) => `"${id}"`).join(" ");
991
1267
  const result = await this.execCommand(
992
- `${this.command} export ${idsArg}`
1268
+ `${this.command} export ${idsArg}`,
1269
+ true
993
1270
  );
994
1271
  const items = this.parseJSON(result);
995
1272
  const map = /* @__PURE__ */ new Map();
@@ -998,10 +1275,10 @@ var init_manager = __esm({
998
1275
  }
999
1276
  return map;
1000
1277
  }
1001
- execCommand(cmd) {
1278
+ execCommand(cmd, ignoreExitCode = false) {
1002
1279
  return new Promise((resolve8, reject) => {
1003
1280
  exec(cmd, (error, stdout) => {
1004
- if (error) {
1281
+ if (error && !ignoreExitCode) {
1005
1282
  reject(
1006
1283
  new ReferenceManagerError(`Failed to execute: ${cmd}`, error)
1007
1284
  );
@@ -1462,7 +1739,7 @@ var init_bibliography = __esm({
1462
1739
  });
1463
1740
 
1464
1741
  // src/core/pipeline.ts
1465
- import { writeFile } from "fs/promises";
1742
+ import { writeFile as writeFile2 } from "fs/promises";
1466
1743
  var PipelineError, Pipeline;
1467
1744
  var init_pipeline = __esm({
1468
1745
  "src/core/pipeline.ts"() {
@@ -1471,7 +1748,7 @@ var init_pipeline = __esm({
1471
1748
  init_transformer();
1472
1749
  init_renderer();
1473
1750
  init_engine();
1474
- init_loader();
1751
+ init_templates();
1475
1752
  init_registry();
1476
1753
  init_resolver();
1477
1754
  init_manager();
@@ -1520,6 +1797,7 @@ var init_pipeline = __esm({
1520
1797
  this.citationFormatter
1521
1798
  );
1522
1799
  this.renderer = new Renderer();
1800
+ this.cssCollector = new CSSCollector(this.templateLoader);
1523
1801
  }
1524
1802
  parser;
1525
1803
  templateEngine;
@@ -1532,6 +1810,7 @@ var init_pipeline = __esm({
1532
1810
  bibliographyGenerator;
1533
1811
  transformer;
1534
1812
  renderer;
1813
+ cssCollector;
1535
1814
  warnings = [];
1536
1815
  /**
1537
1816
  * Run the full conversion pipeline
@@ -1546,7 +1825,7 @@ var init_pipeline = __esm({
1546
1825
  const transformedSlides = await this.transformSlides(presentation);
1547
1826
  const output = this.render(transformedSlides, presentation);
1548
1827
  if (options?.outputPath) {
1549
- await writeFile(options.outputPath, output, "utf-8");
1828
+ await writeFile2(options.outputPath, output, "utf-8");
1550
1829
  }
1551
1830
  return output;
1552
1831
  } catch (error) {
@@ -1573,7 +1852,7 @@ var init_pipeline = __esm({
1573
1852
  const transformedSlides = await this.transformSlides(presentation);
1574
1853
  const output = this.render(transformedSlides, presentation);
1575
1854
  if (options?.outputPath) {
1576
- await writeFile(options.outputPath, output, "utf-8");
1855
+ await writeFile2(options.outputPath, output, "utf-8");
1577
1856
  }
1578
1857
  return {
1579
1858
  output,
@@ -1687,9 +1966,12 @@ var init_pipeline = __esm({
1687
1966
  render(slides, presentation) {
1688
1967
  try {
1689
1968
  const notes = presentation.slides.map((slide) => slide.notes);
1969
+ const templateNames = presentation.slides.map((slide) => slide.template);
1970
+ const templateCss = this.cssCollector.collect(templateNames);
1690
1971
  const renderOptions = {
1691
1972
  includeTheme: true,
1692
- notes
1973
+ notes,
1974
+ ...templateCss ? { templateCss } : {}
1693
1975
  };
1694
1976
  return this.renderer.render(slides, presentation.meta, renderOptions);
1695
1977
  } catch (error) {
@@ -1834,9 +2116,9 @@ var init_schema2 = __esm({
1834
2116
  });
1835
2117
 
1836
2118
  // src/config/loader.ts
1837
- import { access, readFile as readFile5 } from "fs/promises";
1838
- import { join as join3 } from "path";
1839
- import { parse as parseYaml3 } from "yaml";
2119
+ import { access as access2, readFile as readFile6 } from "fs/promises";
2120
+ import { join as join4 } from "path";
2121
+ import { parse as parseYaml4 } from "yaml";
1840
2122
  var CONFIG_NAMES, ConfigLoader;
1841
2123
  var init_loader2 = __esm({
1842
2124
  "src/config/loader.ts"() {
@@ -1850,9 +2132,9 @@ var init_loader2 = __esm({
1850
2132
  }
1851
2133
  async findConfig(directory) {
1852
2134
  for (const name of CONFIG_NAMES) {
1853
- const path12 = join3(directory, name);
2135
+ const path12 = join4(directory, name);
1854
2136
  try {
1855
- await access(path12);
2137
+ await access2(path12);
1856
2138
  return path12;
1857
2139
  } catch {
1858
2140
  }
@@ -1862,8 +2144,8 @@ var init_loader2 = __esm({
1862
2144
  async loadFile(configPath) {
1863
2145
  if (!configPath) return {};
1864
2146
  try {
1865
- const content = await readFile5(configPath, "utf-8");
1866
- return parseYaml3(content) ?? {};
2147
+ const content = await readFile6(configPath, "utf-8");
2148
+ return parseYaml4(content) ?? {};
1867
2149
  } catch (error) {
1868
2150
  if (error.code === "ENOENT") {
1869
2151
  return {};
@@ -1995,9 +2277,9 @@ var init_schema3 = __esm({
1995
2277
  });
1996
2278
 
1997
2279
  // src/images/metadata-loader.ts
1998
- import * as fs4 from "fs/promises";
1999
- import * as path3 from "path";
2000
- import { parse as parseYaml4 } from "yaml";
2280
+ import * as fs5 from "fs/promises";
2281
+ import * as path4 from "path";
2282
+ import { parse as parseYaml5 } from "yaml";
2001
2283
  var metadataWarnings, ImageMetadataLoader;
2002
2284
  var init_metadata_loader = __esm({
2003
2285
  "src/images/metadata-loader.ts"() {
@@ -2026,16 +2308,16 @@ var init_metadata_loader = __esm({
2026
2308
  */
2027
2309
  async loadDirectory(dirPath) {
2028
2310
  const result = /* @__PURE__ */ new Map();
2029
- const fullDirPath = path3.join(this.baseDir, dirPath);
2311
+ const fullDirPath = path4.join(this.baseDir, dirPath);
2030
2312
  try {
2031
- await fs4.access(fullDirPath);
2313
+ await fs5.access(fullDirPath);
2032
2314
  } catch {
2033
2315
  return result;
2034
2316
  }
2035
- const files = await fs4.readdir(fullDirPath);
2317
+ const files = await fs5.readdir(fullDirPath);
2036
2318
  const imageFiles = files.filter((f) => isImageFile(f));
2037
2319
  for (const file of imageFiles) {
2038
- const imagePath = path3.join(dirPath, file);
2320
+ const imagePath = path4.join(dirPath, file);
2039
2321
  const metadata = await this.load(imagePath);
2040
2322
  result.set(file, metadata);
2041
2323
  }
@@ -2047,7 +2329,7 @@ var init_metadata_loader = __esm({
2047
2329
  async hasMetadata(imagePath) {
2048
2330
  const individualPath = this.getIndividualMetadataPath(imagePath);
2049
2331
  try {
2050
- await fs4.access(individualPath);
2332
+ await fs5.access(individualPath);
2051
2333
  return true;
2052
2334
  } catch {
2053
2335
  }
@@ -2055,7 +2337,7 @@ var init_metadata_loader = __esm({
2055
2337
  if (dirMetadata === null) {
2056
2338
  return false;
2057
2339
  }
2058
- const filename = path3.basename(imagePath);
2340
+ const filename = path4.basename(imagePath);
2059
2341
  return filename in dirMetadata && filename !== "_defaults";
2060
2342
  }
2061
2343
  /**
@@ -2069,14 +2351,14 @@ var init_metadata_loader = __esm({
2069
2351
  * Get the path to individual metadata file
2070
2352
  */
2071
2353
  getIndividualMetadataPath(imagePath) {
2072
- return path3.join(this.baseDir, `${imagePath}.meta.yaml`);
2354
+ return path4.join(this.baseDir, `${imagePath}.meta.yaml`);
2073
2355
  }
2074
2356
  /**
2075
2357
  * Get the path to directory metadata file
2076
2358
  */
2077
2359
  getDirectoryMetadataPath(imagePath) {
2078
- const dir = path3.dirname(imagePath);
2079
- return path3.join(this.baseDir, dir, "images.yaml");
2360
+ const dir = path4.dirname(imagePath);
2361
+ return path4.join(this.baseDir, dir, "images.yaml");
2080
2362
  }
2081
2363
  /**
2082
2364
  * Load individual metadata file (.meta.yaml)
@@ -2084,13 +2366,13 @@ var init_metadata_loader = __esm({
2084
2366
  async loadIndividualMetadata(imagePath) {
2085
2367
  const metadataPath = this.getIndividualMetadataPath(imagePath);
2086
2368
  try {
2087
- await fs4.access(metadataPath);
2369
+ await fs5.access(metadataPath);
2088
2370
  } catch {
2089
2371
  return null;
2090
2372
  }
2091
2373
  try {
2092
- const content = await fs4.readFile(metadataPath, "utf-8");
2093
- const parsed = parseYaml4(content);
2374
+ const content = await fs5.readFile(metadataPath, "utf-8");
2375
+ const parsed = parseYaml5(content);
2094
2376
  const validated = individualMetadataSchema.safeParse(parsed);
2095
2377
  if (validated.success) {
2096
2378
  return validated.data;
@@ -2115,13 +2397,13 @@ var init_metadata_loader = __esm({
2115
2397
  async loadDirectoryMetadataFile(imagePath) {
2116
2398
  const metadataPath = this.getDirectoryMetadataPath(imagePath);
2117
2399
  try {
2118
- await fs4.access(metadataPath);
2400
+ await fs5.access(metadataPath);
2119
2401
  } catch {
2120
2402
  return null;
2121
2403
  }
2122
2404
  try {
2123
- const content = await fs4.readFile(metadataPath, "utf-8");
2124
- const parsed = parseYaml4(content);
2405
+ const content = await fs5.readFile(metadataPath, "utf-8");
2406
+ const parsed = parseYaml5(content);
2125
2407
  const validated = directoryMetadataSchema.safeParse(parsed);
2126
2408
  if (validated.success) {
2127
2409
  return validated.data;
@@ -2148,7 +2430,7 @@ var init_metadata_loader = __esm({
2148
2430
  if (dirMetadata === null) {
2149
2431
  return {};
2150
2432
  }
2151
- const filename = path3.basename(imagePath);
2433
+ const filename = path4.basename(imagePath);
2152
2434
  const defaults = dirMetadata["_defaults"] ?? {};
2153
2435
  const fileMetadata = dirMetadata[filename] ?? {};
2154
2436
  return this.mergeMetadata(defaults, fileMetadata);
@@ -2183,8 +2465,8 @@ var init_metadata_loader = __esm({
2183
2465
  });
2184
2466
 
2185
2467
  // src/images/validator.ts
2186
- import * as fs5 from "fs/promises";
2187
- import * as path4 from "path";
2468
+ import * as fs6 from "fs/promises";
2469
+ import * as path5 from "path";
2188
2470
  import imageSize from "image-size";
2189
2471
  var ImageValidator;
2190
2472
  var init_validator = __esm({
@@ -2204,14 +2486,14 @@ var init_validator = __esm({
2204
2486
  async validateImageExists(imagePath) {
2205
2487
  const errors = [];
2206
2488
  const warnings = [];
2207
- const fullPath = path4.join(this.baseDir, imagePath);
2208
- const ext = path4.extname(imagePath).toLowerCase();
2489
+ const fullPath = path5.join(this.baseDir, imagePath);
2490
+ const ext = path5.extname(imagePath).toLowerCase();
2209
2491
  if (!IMAGE_EXTENSIONS.has(ext)) {
2210
2492
  errors.push(`Unsupported image format: ${imagePath}`);
2211
2493
  return { valid: false, errors, warnings };
2212
2494
  }
2213
2495
  try {
2214
- await fs5.access(fullPath);
2496
+ await fs6.access(fullPath);
2215
2497
  } catch {
2216
2498
  errors.push(`Image not found: ${imagePath}`);
2217
2499
  return { valid: false, errors, warnings };
@@ -2222,9 +2504,9 @@ var init_validator = __esm({
2222
2504
  * Get image dimensions
2223
2505
  */
2224
2506
  async getImageDimensions(imagePath) {
2225
- const fullPath = path4.join(this.baseDir, imagePath);
2507
+ const fullPath = path5.join(this.baseDir, imagePath);
2226
2508
  try {
2227
- const buffer = await fs5.readFile(fullPath);
2509
+ const buffer = await fs6.readFile(fullPath);
2228
2510
  const dimensions = imageSize(buffer);
2229
2511
  if (dimensions.width && dimensions.height) {
2230
2512
  return {
@@ -2559,8 +2841,8 @@ var init_processor = __esm({
2559
2841
  });
2560
2842
 
2561
2843
  // src/images/processing-pipeline.ts
2562
- import * as fs6 from "fs/promises";
2563
- import * as path5 from "path";
2844
+ import * as fs7 from "fs/promises";
2845
+ import * as path6 from "path";
2564
2846
  var ImageProcessingPipeline;
2565
2847
  var init_processing_pipeline = __esm({
2566
2848
  "src/images/processing-pipeline.ts"() {
@@ -2573,7 +2855,7 @@ var init_processing_pipeline = __esm({
2573
2855
  this.baseDir = baseDir;
2574
2856
  this.metadataLoader = new ImageMetadataLoader(baseDir);
2575
2857
  this.processor = new ImageProcessor();
2576
- this.outputDir = options?.outputDir ?? path5.join(baseDir, ".processed");
2858
+ this.outputDir = options?.outputDir ?? path6.join(baseDir, ".processed");
2577
2859
  }
2578
2860
  metadataLoader;
2579
2861
  processor;
@@ -2582,7 +2864,7 @@ var init_processing_pipeline = __esm({
2582
2864
  * Process a single image based on its metadata instructions
2583
2865
  */
2584
2866
  async processImage(imagePath) {
2585
- const fullPath = path5.join(this.baseDir, imagePath);
2867
+ const fullPath = path6.join(this.baseDir, imagePath);
2586
2868
  const result = {
2587
2869
  success: true,
2588
2870
  originalPath: fullPath
@@ -2593,7 +2875,7 @@ var init_processing_pipeline = __esm({
2593
2875
  result.skipped = true;
2594
2876
  return result;
2595
2877
  }
2596
- await fs6.mkdir(this.outputDir, { recursive: true });
2878
+ await fs7.mkdir(this.outputDir, { recursive: true });
2597
2879
  const processedPath = await this.applyInstructions(
2598
2880
  fullPath,
2599
2881
  imagePath,
@@ -2622,7 +2904,7 @@ var init_processing_pipeline = __esm({
2622
2904
  imageMap: /* @__PURE__ */ new Map()
2623
2905
  };
2624
2906
  try {
2625
- const files = await fs6.readdir(this.baseDir);
2907
+ const files = await fs7.readdir(this.baseDir);
2626
2908
  const imageFiles = files.filter((f) => isImageFile(f));
2627
2909
  result.totalImages = imageFiles.length;
2628
2910
  for (const imageFile of imageFiles) {
@@ -2646,11 +2928,11 @@ var init_processing_pipeline = __esm({
2646
2928
  * Apply processing instructions to an image
2647
2929
  */
2648
2930
  async applyInstructions(inputPath, relativePath, instructions) {
2649
- const outputFilename = path5.basename(relativePath);
2650
- const finalOutputPath = path5.join(this.outputDir, outputFilename);
2931
+ const outputFilename = path6.basename(relativePath);
2932
+ const finalOutputPath = path6.join(this.outputDir, outputFilename);
2651
2933
  const tempPaths = [
2652
- path5.join(this.outputDir, `temp_${outputFilename}`),
2653
- path5.join(this.outputDir, `temp2_${outputFilename}`)
2934
+ path6.join(this.outputDir, `temp_${outputFilename}`),
2935
+ path6.join(this.outputDir, `temp2_${outputFilename}`)
2654
2936
  ];
2655
2937
  let currentInputPath = inputPath;
2656
2938
  for (let i = 0; i < instructions.length; i++) {
@@ -2663,11 +2945,11 @@ var init_processing_pipeline = __esm({
2663
2945
  }
2664
2946
  }
2665
2947
  try {
2666
- await fs6.unlink(path5.join(this.outputDir, `temp_${outputFilename}`));
2948
+ await fs7.unlink(path6.join(this.outputDir, `temp_${outputFilename}`));
2667
2949
  } catch {
2668
2950
  }
2669
2951
  try {
2670
- await fs6.unlink(path5.join(this.outputDir, `temp2_${outputFilename}`));
2952
+ await fs7.unlink(path6.join(this.outputDir, `temp2_${outputFilename}`));
2671
2953
  } catch {
2672
2954
  }
2673
2955
  return finalOutputPath;
@@ -2753,19 +3035,19 @@ var init_images = __esm({
2753
3035
 
2754
3036
  // src/cli/commands/convert.ts
2755
3037
  import { Command } from "commander";
2756
- import { access as access4, readFile as readFile8 } from "fs/promises";
2757
- import { basename as basename3, dirname as dirname2, join as join7 } from "path";
3038
+ import { access as access5, readFile as readFile9 } from "fs/promises";
3039
+ import { basename as basename3, dirname as dirname2, join as join8 } from "path";
2758
3040
  import chalk from "chalk";
2759
3041
  import ora from "ora";
2760
- import { parse as parseYaml5 } from "yaml";
3042
+ import { parse as parseYaml6 } from "yaml";
2761
3043
  function getDefaultOutputPath(inputPath) {
2762
3044
  const dir = dirname2(inputPath);
2763
3045
  const base = basename3(inputPath, ".yaml");
2764
- return join7(dir, `${base}.md`);
3046
+ return join8(dir, `${base}.md`);
2765
3047
  }
2766
3048
  async function extractImageDirectories(inputPath, baseDir) {
2767
- const content = await readFile8(inputPath, "utf-8");
2768
- const parsed = parseYaml5(content);
3049
+ const content = await readFile9(inputPath, "utf-8");
3050
+ const parsed = parseYaml6(content);
2769
3051
  const imageDirs = /* @__PURE__ */ new Set();
2770
3052
  if (!parsed.slides) return [];
2771
3053
  for (const slide of parsed.slides) {
@@ -2788,7 +3070,7 @@ async function extractImageDirectories(inputPath, baseDir) {
2788
3070
  for (const imagePath of imagePaths) {
2789
3071
  const dir = dirname2(imagePath);
2790
3072
  if (dir && dir !== ".") {
2791
- imageDirs.add(join7(baseDir, dir));
3073
+ imageDirs.add(join8(baseDir, dir));
2792
3074
  }
2793
3075
  }
2794
3076
  }
@@ -2799,7 +3081,7 @@ async function processImages(inputPath, baseDir) {
2799
3081
  let totalProcessed = 0;
2800
3082
  for (const imageDir of imageDirs) {
2801
3083
  try {
2802
- await access4(imageDir);
3084
+ await access5(imageDir);
2803
3085
  const pipeline = new ImageProcessingPipeline(imageDir);
2804
3086
  const result = await pipeline.processDirectory();
2805
3087
  totalProcessed += result.processedImages;
@@ -2824,7 +3106,7 @@ async function executeConvert(inputPath, options) {
2824
3106
  try {
2825
3107
  spinner?.start(`Reading ${inputPath}...`);
2826
3108
  try {
2827
- await access4(inputPath);
3109
+ await access5(inputPath);
2828
3110
  } catch {
2829
3111
  spinner?.fail(`File not found: ${inputPath}`);
2830
3112
  console.error(chalk.red(`Error: Input file not found: ${inputPath}`));
@@ -2942,7 +3224,7 @@ var init_convert = __esm({
2942
3224
 
2943
3225
  // src/cli/utils/marp-runner.ts
2944
3226
  import { existsSync } from "fs";
2945
- import { join as join8, resolve, dirname as dirname4 } from "path";
3227
+ import { join as join9, resolve, dirname as dirname4 } from "path";
2946
3228
  import {
2947
3229
  execFileSync,
2948
3230
  spawn
@@ -2951,7 +3233,7 @@ function getMarpCommand(projectDir) {
2951
3233
  const startDir = resolve(projectDir ?? process.cwd());
2952
3234
  let currentDir = startDir;
2953
3235
  while (true) {
2954
- const localMarp = join8(currentDir, "node_modules", ".bin", "marp");
3236
+ const localMarp = join9(currentDir, "node_modules", ".bin", "marp");
2955
3237
  if (existsSync(localMarp)) {
2956
3238
  return localMarp;
2957
3239
  }
@@ -3013,13 +3295,20 @@ function parseMarpBrowserError(errorOutput) {
3013
3295
  }
3014
3296
  return null;
3015
3297
  }
3298
+ function addHtmlOption(args) {
3299
+ if (args.includes("--html") || args.includes("--no-html")) {
3300
+ return args;
3301
+ }
3302
+ return ["--html", ...args];
3303
+ }
3016
3304
  function runMarp(args, options = {}) {
3017
3305
  const { projectDir, ...execOptions } = options;
3018
3306
  const marpCmd = getMarpCommand(projectDir);
3019
3307
  if (!marpCmd) {
3020
3308
  throw new Error("Marp CLI not found. Install it with: npm install -D @marp-team/marp-cli");
3021
3309
  }
3022
- execFileSync(marpCmd, args, execOptions);
3310
+ const finalArgs = addHtmlOption(args);
3311
+ execFileSync(marpCmd, finalArgs, execOptions);
3023
3312
  }
3024
3313
  function spawnMarp(args, options = {}) {
3025
3314
  const { projectDir, ...spawnOptions } = options;
@@ -3027,7 +3316,8 @@ function spawnMarp(args, options = {}) {
3027
3316
  if (!marpCmd) {
3028
3317
  throw new Error("Marp CLI not found. Install it with: npm install -D @marp-team/marp-cli");
3029
3318
  }
3030
- return spawn(marpCmd, args, spawnOptions);
3319
+ const finalArgs = addHtmlOption(args);
3320
+ return spawn(marpCmd, finalArgs, spawnOptions);
3031
3321
  }
3032
3322
  var init_marp_runner = __esm({
3033
3323
  "src/cli/utils/marp-runner.ts"() {
@@ -3051,17 +3341,17 @@ __export(screenshot_exports, {
3051
3341
  generateContactSheet: () => generateContactSheet
3052
3342
  });
3053
3343
  import { Command as Command4 } from "commander";
3054
- import { access as access7, mkdir as mkdir3, readdir as readdir5, rename, unlink as unlink2 } from "fs/promises";
3055
- import { basename as basename5, dirname as dirname6, extname as extname3, join as join10 } from "path";
3344
+ import { access as access8, mkdir as mkdir4, readdir as readdir5, rename, unlink as unlink2 } from "fs/promises";
3345
+ import { basename as basename5, dirname as dirname6, extname as extname3, join as join11 } from "path";
3056
3346
  import chalk4 from "chalk";
3057
3347
  import ora3 from "ora";
3058
3348
  import sharp2 from "sharp";
3059
3349
  async function filterToSpecificSlide(outputDir, baseName, slideNumber, format) {
3060
3350
  const slideStr = slideNumber.toString().padStart(3, "0");
3061
3351
  const targetFileName = `${baseName}.${slideStr}.${format}`;
3062
- const targetPath = join10(outputDir, targetFileName);
3352
+ const targetPath = join11(outputDir, targetFileName);
3063
3353
  try {
3064
- await access7(targetPath);
3354
+ await access8(targetPath);
3065
3355
  } catch {
3066
3356
  return {
3067
3357
  success: false,
@@ -3074,7 +3364,7 @@ async function filterToSpecificSlide(outputDir, baseName, slideNumber, format) {
3074
3364
  );
3075
3365
  for (const file of slideFiles) {
3076
3366
  if (file !== targetFileName) {
3077
- await unlink2(join10(outputDir, file));
3367
+ await unlink2(join11(outputDir, file));
3078
3368
  }
3079
3369
  }
3080
3370
  return {
@@ -3091,8 +3381,8 @@ async function ensureFileExtensions(outputDir, baseName, format) {
3091
3381
  const renamedFiles = [];
3092
3382
  for (const file of files) {
3093
3383
  if (file.startsWith(baseName) && slidePattern.test(file)) {
3094
- const oldPath = join10(outputDir, file);
3095
- const newPath = join10(outputDir, `${file}.${format}`);
3384
+ const oldPath = join11(outputDir, file);
3385
+ const newPath = join11(outputDir, `${file}.${format}`);
3096
3386
  await rename(oldPath, newPath);
3097
3387
  renamedFiles.push(`${file}.${format}`);
3098
3388
  }
@@ -3186,14 +3476,14 @@ function formatAiOutput(options) {
3186
3476
  ""
3187
3477
  ];
3188
3478
  for (const file of files) {
3189
- lines.push(` ${join10(outputDir, file)}`);
3479
+ lines.push(` ${join11(outputDir, file)}`);
3190
3480
  }
3191
3481
  lines.push("");
3192
3482
  lines.push(`Estimated tokens: ~${totalTokens} (${files.length} ${imageLabel})`);
3193
3483
  if (files.length > 0) {
3194
3484
  lines.push("");
3195
3485
  lines.push("To review in Claude Code:");
3196
- lines.push(` Read ${join10(outputDir, files[0])}`);
3486
+ lines.push(` Read ${join11(outputDir, files[0])}`);
3197
3487
  }
3198
3488
  return lines.join("\n");
3199
3489
  }
@@ -3211,7 +3501,7 @@ function buildMarpCommandArgs(markdownPath, outputDir, options) {
3211
3501
  args.push("--jpeg-quality", String(quality));
3212
3502
  }
3213
3503
  const mdBaseName = basename5(markdownPath, extname3(markdownPath));
3214
- const outputPath = join10(outputDir, mdBaseName);
3504
+ const outputPath = join11(outputDir, mdBaseName);
3215
3505
  args.push("-o", outputPath);
3216
3506
  args.push(markdownPath);
3217
3507
  return args;
@@ -3226,7 +3516,7 @@ async function executeScreenshot(inputPath, options) {
3226
3516
  const spinner = options.verbose ? null : ora3();
3227
3517
  const outputDir = options.output || "./screenshots";
3228
3518
  try {
3229
- await access7(inputPath);
3519
+ await access8(inputPath);
3230
3520
  } catch {
3231
3521
  const message = `File not found: ${inputPath}`;
3232
3522
  console.error(chalk4.red(`Error: ${message}`));
@@ -3252,7 +3542,7 @@ async function executeScreenshot(inputPath, options) {
3252
3542
  }
3253
3543
  spinner?.succeed("Marp CLI found");
3254
3544
  try {
3255
- await mkdir3(outputDir, { recursive: true });
3545
+ await mkdir4(outputDir, { recursive: true });
3256
3546
  } catch {
3257
3547
  const message = `Failed to create output directory: ${outputDir}`;
3258
3548
  console.error(chalk4.red(`Error: ${message}`));
@@ -3359,7 +3649,7 @@ Error: ${browserError.message}
3359
3649
  const generatedFiles = allFiles.filter((f) => f.startsWith(mdBaseName) && f.endsWith(`.${actualFormat}`)).sort();
3360
3650
  if (generatedFiles.length > 0) {
3361
3651
  try {
3362
- const metadata = await sharp2(join10(outputDir, generatedFiles[0])).metadata();
3652
+ const metadata = await sharp2(join11(outputDir, generatedFiles[0])).metadata();
3363
3653
  if (metadata.width && metadata.height) {
3364
3654
  actualWidth = metadata.width;
3365
3655
  actualHeight = metadata.height;
@@ -3370,10 +3660,10 @@ Error: ${browserError.message}
3370
3660
  if (options.contactSheet && generatedFiles.length > 0) {
3371
3661
  spinner?.start("Generating contact sheet...");
3372
3662
  const slides = generatedFiles.map((file, index) => ({
3373
- path: join10(outputDir, file),
3663
+ path: join11(outputDir, file),
3374
3664
  index: index + 1
3375
3665
  }));
3376
- const contactSheetPath = join10(outputDir, `${mdBaseName}-contact.png`);
3666
+ const contactSheetPath = join11(outputDir, `${mdBaseName}-contact.png`);
3377
3667
  const contactResult = await generateContactSheet(slides, {
3378
3668
  outputPath: contactSheetPath,
3379
3669
  columns: options.columns || 2,
@@ -3433,13 +3723,9 @@ init_transformer();
3433
3723
  init_renderer();
3434
3724
  init_pipeline();
3435
3725
 
3436
- // src/templates/index.ts
3437
- init_engine();
3438
- init_loader();
3439
- init_validators();
3440
-
3441
3726
  // src/index.ts
3442
- var VERSION = "0.7.0";
3727
+ init_templates();
3728
+ var VERSION = "0.8.0";
3443
3729
 
3444
3730
  // src/cli/index.ts
3445
3731
  init_convert();
@@ -3451,11 +3737,11 @@ init_registry();
3451
3737
  init_loader2();
3452
3738
  init_convert();
3453
3739
  import { Command as Command2 } from "commander";
3454
- import { access as access5, readFile as readFile9 } from "fs/promises";
3740
+ import { access as access6, readFile as readFile10 } from "fs/promises";
3455
3741
  import { dirname as dirname3 } from "path";
3456
3742
  import chalk2 from "chalk";
3457
3743
  import ora2 from "ora";
3458
- import { parse as parseYaml6, stringify as yamlStringify } from "yaml";
3744
+ import { parse as parseYaml7, stringify as yamlStringify } from "yaml";
3459
3745
 
3460
3746
  // src/references/index.ts
3461
3747
  init_extractor();
@@ -3696,7 +3982,7 @@ async function executeValidate(inputPath, options) {
3696
3982
  try {
3697
3983
  spinner?.start(`Validating ${inputPath}...`);
3698
3984
  try {
3699
- await access5(inputPath);
3985
+ await access6(inputPath);
3700
3986
  } catch {
3701
3987
  spinner?.fail(`File not found: ${inputPath}`);
3702
3988
  result.errors.push(`File not found: ${inputPath}`);
@@ -3705,9 +3991,9 @@ async function executeValidate(inputPath, options) {
3705
3991
  process.exitCode = ExitCode.FileReadError;
3706
3992
  return;
3707
3993
  }
3708
- const content = await readFile9(inputPath, "utf-8");
3994
+ const content = await readFile10(inputPath, "utf-8");
3709
3995
  try {
3710
- parseYaml6(content);
3996
+ parseYaml7(content);
3711
3997
  result.stats.yamlSyntax = true;
3712
3998
  } catch (error) {
3713
3999
  const message = error instanceof Error ? error.message : "Unknown error";
@@ -4028,15 +4314,16 @@ function outputResult(result, options) {
4028
4314
  }
4029
4315
 
4030
4316
  // src/cli/commands/templates.ts
4317
+ init_templates();
4318
+ init_loader2();
4319
+ init_pipeline();
4320
+ init_convert();
4031
4321
  import { Command as Command5 } from "commander";
4032
4322
  import chalk5 from "chalk";
4033
4323
  import * as yaml2 from "yaml";
4034
- import { mkdir as mkdir4, writeFile as writeFile3, rm as rm2, readdir as readdir6 } from "fs/promises";
4035
- import { join as join11, basename as basename6 } from "path";
4324
+ import { mkdir as mkdir5, writeFile as writeFile4, rm as rm2, readdir as readdir6 } from "fs/promises";
4325
+ import { join as join12, basename as basename6 } from "path";
4036
4326
  import { tmpdir as tmpdir2 } from "os";
4037
- init_loader2();
4038
- init_pipeline();
4039
- init_convert();
4040
4327
 
4041
4328
  // src/cli/commands/preview.ts
4042
4329
  init_pipeline();
@@ -4044,9 +4331,9 @@ init_loader2();
4044
4331
  init_convert();
4045
4332
  init_marp_runner();
4046
4333
  import { Command as Command3 } from "commander";
4047
- import { access as access6, readdir as readdir4, mkdir as mkdir2, writeFile as writeFile2, readFile as readFile10, rm } from "fs/promises";
4048
- import { basename as basename4, dirname as dirname5, join as join9, extname as extname2 } from "path";
4049
- import * as path6 from "path";
4334
+ import { access as access7, readdir as readdir4, mkdir as mkdir3, writeFile as writeFile3, readFile as readFile11, rm } from "fs/promises";
4335
+ import { basename as basename4, dirname as dirname5, join as join10, extname as extname2 } from "path";
4336
+ import * as path7 from "path";
4050
4337
  import { tmpdir } from "os";
4051
4338
  import { createServer } from "http";
4052
4339
  import chalk3 from "chalk";
@@ -4164,7 +4451,7 @@ async function collectSlideInfo(dir, baseName, format) {
4164
4451
  if (match) {
4165
4452
  const index = parseInt(match[1], 10);
4166
4453
  slides.push({
4167
- path: join9(dir, file),
4454
+ path: join10(dir, file),
4168
4455
  title: `Slide ${index}`,
4169
4456
  index
4170
4457
  });
@@ -4180,7 +4467,7 @@ async function checkMarpCliAvailable(projectDir) {
4180
4467
  }
4181
4468
  function getTempPreviewDir(inputPath) {
4182
4469
  const base = basename4(inputPath, ".yaml");
4183
- return join9(tmpdir(), `slide-gen-preview-${base}-${Date.now()}`);
4470
+ return join10(tmpdir(), `slide-gen-preview-${base}-${Date.now()}`);
4184
4471
  }
4185
4472
  function buildMarpCommand(markdownDir, options) {
4186
4473
  const parts = ["marp", "--server", "-I", markdownDir];
@@ -4203,8 +4490,8 @@ function startStaticServer(baseDir, port, options = {}) {
4203
4490
  try {
4204
4491
  const urlPath = new URL(req.url || "/", `http://localhost`).pathname;
4205
4492
  const requestedPath = urlPath === "/" ? "/index.html" : urlPath;
4206
- const resolvedBaseDir = path6.resolve(baseDir);
4207
- const filePath = path6.resolve(baseDir, "." + requestedPath);
4493
+ const resolvedBaseDir = path7.resolve(baseDir);
4494
+ const filePath = path7.resolve(baseDir, "." + requestedPath);
4208
4495
  if (!filePath.startsWith(resolvedBaseDir + "/") && filePath !== resolvedBaseDir) {
4209
4496
  res.writeHead(403);
4210
4497
  res.end("Forbidden");
@@ -4212,7 +4499,7 @@ function startStaticServer(baseDir, port, options = {}) {
4212
4499
  }
4213
4500
  const ext = extname2(filePath);
4214
4501
  const contentType = mimeTypes[ext] || "application/octet-stream";
4215
- const data = await readFile10(filePath);
4502
+ const data = await readFile11(filePath);
4216
4503
  res.writeHead(200, { "Content-Type": contentType });
4217
4504
  res.end(data);
4218
4505
  } catch {
@@ -4234,9 +4521,9 @@ function startStaticServer(baseDir, port, options = {}) {
4234
4521
  async function executeGalleryPreview(inputPath, options) {
4235
4522
  const errors = [];
4236
4523
  const port = Number(options.port) || 8080;
4237
- const galleryDir = join9(tmpdir(), `slide-gen-gallery-${Date.now()}`);
4524
+ const galleryDir = join10(tmpdir(), `slide-gen-gallery-${Date.now()}`);
4238
4525
  try {
4239
- await access6(inputPath);
4526
+ await access7(inputPath);
4240
4527
  } catch {
4241
4528
  console.error(chalk3.red(`Error: File not found: ${inputPath}`));
4242
4529
  errors.push(`File not found: ${inputPath}`);
@@ -4257,7 +4544,7 @@ async function executeGalleryPreview(inputPath, options) {
4257
4544
  return { success: false, errors };
4258
4545
  }
4259
4546
  console.log(chalk3.green("\u2713") + " Marp CLI found");
4260
- await mkdir2(galleryDir, { recursive: true });
4547
+ await mkdir3(galleryDir, { recursive: true });
4261
4548
  const configLoader = new ConfigLoader();
4262
4549
  let configPath = options.config;
4263
4550
  if (!configPath) {
@@ -4276,7 +4563,7 @@ async function executeGalleryPreview(inputPath, options) {
4276
4563
  await rm(galleryDir, { recursive: true, force: true });
4277
4564
  return { success: false, errors };
4278
4565
  }
4279
- const tempMdPath = join9(galleryDir, "slides.md");
4566
+ const tempMdPath = join10(galleryDir, "slides.md");
4280
4567
  console.log(`Converting ${chalk3.cyan(inputPath)}...`);
4281
4568
  try {
4282
4569
  await pipeline.runWithResult(inputPath, { outputPath: tempMdPath });
@@ -4317,7 +4604,7 @@ async function executeGalleryPreview(inputPath, options) {
4317
4604
  path: basename4(s.path)
4318
4605
  }));
4319
4606
  const galleryHtml = generateGalleryHtml(relativeSlides);
4320
- await writeFile2(join9(galleryDir, "index.html"), galleryHtml);
4607
+ await writeFile3(join10(galleryDir, "index.html"), galleryHtml);
4321
4608
  console.log(`
4322
4609
  Starting gallery server on port ${chalk3.cyan(port)}...`);
4323
4610
  let server;
@@ -4381,7 +4668,7 @@ async function executePreview(inputPath, options) {
4381
4668
  const port = Number(options.port) || 8080;
4382
4669
  if (options.signal?.aborted) {
4383
4670
  try {
4384
- await access6(inputPath);
4671
+ await access7(inputPath);
4385
4672
  } catch {
4386
4673
  errors.push(`File not found: ${inputPath}`);
4387
4674
  return { success: false, errors };
@@ -4389,7 +4676,7 @@ async function executePreview(inputPath, options) {
4389
4676
  return { success: true, errors };
4390
4677
  }
4391
4678
  try {
4392
- await access6(inputPath);
4679
+ await access7(inputPath);
4393
4680
  } catch {
4394
4681
  console.error(chalk3.red(`Error: File not found: ${inputPath}`));
4395
4682
  errors.push(`File not found: ${inputPath}`);
@@ -4427,8 +4714,8 @@ async function executePreview(inputPath, options) {
4427
4714
  return { success: false, errors };
4428
4715
  }
4429
4716
  const tempDir = getTempPreviewDir(inputPath);
4430
- await mkdir2(tempDir, { recursive: true });
4431
- const tempMarkdownPath = join9(tempDir, "slides.md");
4717
+ await mkdir3(tempDir, { recursive: true });
4718
+ const tempMarkdownPath = join10(tempDir, "slides.md");
4432
4719
  console.log(`Converting ${chalk3.cyan(inputPath)}...`);
4433
4720
  try {
4434
4721
  await pipeline.runWithResult(inputPath, { outputPath: tempMarkdownPath });
@@ -4944,7 +5231,7 @@ async function executeTemplatePreview(name, options) {
4944
5231
  return;
4945
5232
  }
4946
5233
  const port = Number(options.port) || 8080;
4947
- const previewDir = join11(tmpdir2(), `slide-gen-template-preview-${Date.now()}`);
5234
+ const previewDir = join12(tmpdir2(), `slide-gen-template-preview-${Date.now()}`);
4948
5235
  console.log("Checking for Marp CLI...");
4949
5236
  const marpAvailable = await checkMarpCliAvailable();
4950
5237
  if (!marpAvailable) {
@@ -4981,7 +5268,7 @@ async function executeTemplatePreview(name, options) {
4981
5268
  return;
4982
5269
  }
4983
5270
  console.log(`Found ${templates.length} template(s) to preview`);
4984
- await mkdir4(previewDir, { recursive: true });
5271
+ await mkdir5(previewDir, { recursive: true });
4985
5272
  console.log("Initializing pipeline...");
4986
5273
  const pipeline = new Pipeline(config);
4987
5274
  try {
@@ -4997,9 +5284,9 @@ async function executeTemplatePreview(name, options) {
4997
5284
  for (const template of templates) {
4998
5285
  console.log(`Processing template: ${chalk5.cyan(template.name)}...`);
4999
5286
  const sampleYaml = generateSampleYaml(template);
5000
- const yamlPath = join11(previewDir, `${template.name}.yaml`);
5001
- await writeFile3(yamlPath, sampleYaml);
5002
- const mdPath = join11(previewDir, `${template.name}.md`);
5287
+ const yamlPath = join12(previewDir, `${template.name}.yaml`);
5288
+ await writeFile4(yamlPath, sampleYaml);
5289
+ const mdPath = join12(previewDir, `${template.name}.md`);
5003
5290
  try {
5004
5291
  await pipeline.runWithResult(yamlPath, { outputPath: mdPath });
5005
5292
  } catch (error) {
@@ -5032,7 +5319,7 @@ async function executeTemplatePreview(name, options) {
5032
5319
  return;
5033
5320
  }
5034
5321
  const previewHtml = generateTemplatePreviewHtml(templatePreviews);
5035
- await writeFile3(join11(previewDir, "index.html"), previewHtml);
5322
+ await writeFile4(join12(previewDir, "index.html"), previewHtml);
5036
5323
  console.log(`
5037
5324
  Starting preview server on port ${chalk5.cyan(port)}...`);
5038
5325
  let server;
@@ -5114,7 +5401,7 @@ async function executeTemplateScreenshot(name, options) {
5114
5401
  return { success: true, errors: [], files: [] };
5115
5402
  }
5116
5403
  const outputDir = options.output || "./template-screenshots";
5117
- await mkdir4(outputDir, { recursive: true });
5404
+ await mkdir5(outputDir, { recursive: true });
5118
5405
  const configLoader = new ConfigLoader();
5119
5406
  const configPath = options.config || await configLoader.findConfig(process.cwd());
5120
5407
  const config = await configLoader.load(configPath);
@@ -5127,8 +5414,8 @@ async function executeTemplateScreenshot(name, options) {
5127
5414
  process.exitCode = ExitCode.GeneralError;
5128
5415
  return { success: false, errors: [message] };
5129
5416
  }
5130
- const tempDir = join11(tmpdir2(), `slide-gen-template-screenshot-${Date.now()}`);
5131
- await mkdir4(tempDir, { recursive: true });
5417
+ const tempDir = join12(tmpdir2(), `slide-gen-template-screenshot-${Date.now()}`);
5418
+ await mkdir5(tempDir, { recursive: true });
5132
5419
  const isAiFormat = options.format === "ai";
5133
5420
  const imageFormat = isAiFormat ? "jpeg" : options.format || "png";
5134
5421
  const imageWidth = isAiFormat ? 640 : options.width || 1280;
@@ -5139,9 +5426,9 @@ async function executeTemplateScreenshot(name, options) {
5139
5426
  console.log(`Processing: ${template.name}`);
5140
5427
  }
5141
5428
  const sampleYaml = generateSampleYaml(template);
5142
- const yamlPath = join11(tempDir, `${template.name}.yaml`);
5143
- await writeFile3(yamlPath, sampleYaml);
5144
- const mdPath = join11(tempDir, `${template.name}.md`);
5429
+ const yamlPath = join12(tempDir, `${template.name}.yaml`);
5430
+ await writeFile4(yamlPath, sampleYaml);
5431
+ const mdPath = join12(tempDir, `${template.name}.md`);
5145
5432
  try {
5146
5433
  await pipeline.runWithResult(yamlPath, { outputPath: mdPath });
5147
5434
  } catch (error) {
@@ -5158,7 +5445,7 @@ async function executeTemplateScreenshot(name, options) {
5158
5445
  if (imageFormat === "jpeg") {
5159
5446
  marpArgs.push("--jpeg-quality", String(options.quality || 80));
5160
5447
  }
5161
- const outputBasePath = join11(tempDir, template.name);
5448
+ const outputBasePath = join12(tempDir, template.name);
5162
5449
  marpArgs.push("-o", outputBasePath);
5163
5450
  marpArgs.push(mdPath);
5164
5451
  try {
@@ -5178,7 +5465,7 @@ async function executeTemplateScreenshot(name, options) {
5178
5465
  (f) => f.startsWith(template.name) && f.endsWith(`.${imageFormat}`)
5179
5466
  );
5180
5467
  for (const slideFile of slideFiles) {
5181
- const sourceFile = join11(tempDir, slideFile);
5468
+ const sourceFile = join12(tempDir, slideFile);
5182
5469
  const match = slideFile.match(/\.(\d{3})\.\w+$/);
5183
5470
  let targetName;
5184
5471
  if (slideFiles.length === 1 || !match) {
@@ -5186,7 +5473,7 @@ async function executeTemplateScreenshot(name, options) {
5186
5473
  } else {
5187
5474
  targetName = slideFile;
5188
5475
  }
5189
- const targetFile = join11(outputDir, targetName);
5476
+ const targetFile = join12(outputDir, targetName);
5190
5477
  const { copyFile: fsCopyFile } = await import("fs/promises");
5191
5478
  await fsCopyFile(sourceFile, targetFile);
5192
5479
  generatedFiles.push(targetName);
@@ -5198,10 +5485,10 @@ async function executeTemplateScreenshot(name, options) {
5198
5485
  console.log("Generating contact sheet...");
5199
5486
  const { generateContactSheet: genContactSheet } = await Promise.resolve().then(() => (init_screenshot(), screenshot_exports));
5200
5487
  const slides = generatedFiles.map((file, index) => ({
5201
- path: join11(outputDir, file),
5488
+ path: join12(outputDir, file),
5202
5489
  index: index + 1
5203
5490
  }));
5204
- const contactSheetPath = join11(outputDir, `templates-contact.${imageFormat === "jpeg" ? "jpeg" : "png"}`);
5491
+ const contactSheetPath = join12(outputDir, `templates-contact.${imageFormat === "jpeg" ? "jpeg" : "png"}`);
5205
5492
  const contactResult = await genContactSheet(slides, {
5206
5493
  outputPath: contactSheetPath,
5207
5494
  columns: options.columns || 3,
@@ -5224,7 +5511,7 @@ async function executeTemplateScreenshot(name, options) {
5224
5511
  console.log("Screenshots saved (AI-optimized):");
5225
5512
  console.log("");
5226
5513
  for (const file of generatedFiles) {
5227
- console.log(` ${join11(outputDir, file)}`);
5514
+ console.log(` ${join12(outputDir, file)}`);
5228
5515
  }
5229
5516
  console.log("");
5230
5517
  console.log(`Estimated tokens: ~${totalTokens} (${generatedFiles.length} images)`);
@@ -5252,194 +5539,14 @@ function createTemplatesCommand() {
5252
5539
  // src/cli/commands/icons.ts
5253
5540
  init_registry();
5254
5541
  init_resolver();
5542
+ init_fetcher();
5543
+ init_loader2();
5544
+ init_convert();
5255
5545
  import { Command as Command6 } from "commander";
5256
5546
  import chalk6 from "chalk";
5257
5547
  import * as fs8 from "fs/promises";
5258
5548
  import * as path8 from "path";
5259
5549
  import { parse as parseYaml8, stringify as stringifyYaml2 } from "yaml";
5260
-
5261
- // src/icons/fetcher.ts
5262
- import * as fs7 from "fs/promises";
5263
- import * as path7 from "path";
5264
- import { stringify as stringifyYaml, parse as parseYaml7 } from "yaml";
5265
- var ICON_SOURCES = {
5266
- health: { set: "healthicons", license: "MIT", external: true },
5267
- ms: { set: "material-symbols", license: "Apache-2.0", external: true },
5268
- hero: { set: "heroicons", license: "MIT", external: true },
5269
- mi: { set: "material-icons", license: "Apache-2.0", external: false },
5270
- mdi: { set: "mdi", license: "Apache-2.0", external: true },
5271
- iconify: { set: "iconify", license: "Various", external: true }
5272
- };
5273
- function isExternalSource(prefix) {
5274
- return ICON_SOURCES[prefix]?.external ?? false;
5275
- }
5276
- var VALID_ICON_NAME = /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/i;
5277
- var VALID_ICON_NAME_WITH_PATH = /^[a-z0-9][a-z0-9-/]*[a-z0-9]$|^[a-z0-9]$/i;
5278
- function isValidIconName(name, allowPath = false) {
5279
- if (!name || name.length > 100) {
5280
- return false;
5281
- }
5282
- if (name.includes("..") || name.startsWith("/") || name.endsWith("/")) {
5283
- return false;
5284
- }
5285
- const pattern = allowPath ? VALID_ICON_NAME_WITH_PATH : VALID_ICON_NAME;
5286
- return pattern.test(name);
5287
- }
5288
- var IconFetcher = class {
5289
- fetchedDir;
5290
- saveLocally;
5291
- timeoutMs;
5292
- constructor(options = {}) {
5293
- this.fetchedDir = options.fetchedDir ?? "icons/fetched";
5294
- this.saveLocally = options.saveLocally ?? true;
5295
- this.timeoutMs = options.timeoutMs ?? 1e4;
5296
- }
5297
- /**
5298
- * Parse an icon reference string (e.g., "health:stethoscope")
5299
- * Returns null if the format is invalid or contains unsafe characters
5300
- */
5301
- parseReference(iconRef) {
5302
- const colonIndex = iconRef.indexOf(":");
5303
- if (colonIndex === -1) {
5304
- return null;
5305
- }
5306
- const prefix = iconRef.substring(0, colonIndex);
5307
- const name = iconRef.substring(colonIndex + 1);
5308
- if (!/^[a-z0-9]+$/i.test(prefix)) {
5309
- return null;
5310
- }
5311
- if (!isValidIconName(name)) {
5312
- return null;
5313
- }
5314
- return { prefix, name };
5315
- }
5316
- /**
5317
- * Get the Iconify set name for a prefix
5318
- */
5319
- getIconifySet(prefix) {
5320
- return ICON_SOURCES[prefix]?.set ?? prefix;
5321
- }
5322
- /**
5323
- * Get the local file path for an icon reference
5324
- */
5325
- getLocalPath(iconRef) {
5326
- const parsed = this.parseReference(iconRef);
5327
- if (!parsed) {
5328
- throw new Error(`Invalid icon reference: ${iconRef}`);
5329
- }
5330
- const setDir = this.getIconifySet(parsed.prefix);
5331
- return path7.join(this.fetchedDir, setDir, `${parsed.name}.svg`);
5332
- }
5333
- /**
5334
- * Check if an icon exists locally
5335
- */
5336
- async existsLocally(iconRef) {
5337
- const localPath = this.getLocalPath(iconRef);
5338
- try {
5339
- await fs7.access(localPath);
5340
- return true;
5341
- } catch {
5342
- return false;
5343
- }
5344
- }
5345
- /**
5346
- * Build the Iconify API URL for an icon
5347
- */
5348
- buildUrl(prefix, name) {
5349
- const set = this.getIconifySet(prefix);
5350
- return `https://api.iconify.design/${set}/${name}.svg`;
5351
- }
5352
- /**
5353
- * Resolve an icon reference (local first, then fetch)
5354
- */
5355
- async resolve(iconRef) {
5356
- if (await this.existsLocally(iconRef)) {
5357
- const localPath = this.getLocalPath(iconRef);
5358
- return await fs7.readFile(localPath, "utf-8");
5359
- }
5360
- return await this.fetchAndSave(iconRef);
5361
- }
5362
- /**
5363
- * Fetch an icon from external source and save locally
5364
- */
5365
- async fetchAndSave(iconRef) {
5366
- const parsed = this.parseReference(iconRef);
5367
- if (!parsed) {
5368
- throw new Error(`Invalid icon reference: ${iconRef}`);
5369
- }
5370
- const url = this.buildUrl(parsed.prefix, parsed.name);
5371
- const controller = new AbortController();
5372
- const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
5373
- let response;
5374
- try {
5375
- response = await fetch(url, { signal: controller.signal });
5376
- } catch (error) {
5377
- if (error instanceof Error && error.name === "AbortError") {
5378
- throw new Error(`Fetch timeout: ${iconRef} (exceeded ${this.timeoutMs}ms)`);
5379
- }
5380
- throw error;
5381
- } finally {
5382
- clearTimeout(timeoutId);
5383
- }
5384
- if (!response.ok) {
5385
- throw new Error(`Icon not found: ${iconRef} (HTTP ${response.status})`);
5386
- }
5387
- const svg = await response.text();
5388
- if (this.saveLocally) {
5389
- await this.saveLocallyInternal(parsed.prefix, parsed.name, svg, url);
5390
- }
5391
- return svg;
5392
- }
5393
- /**
5394
- * Save icon to local filesystem
5395
- */
5396
- async saveLocallyInternal(prefix, name, svg, sourceUrl) {
5397
- const setDir = this.getIconifySet(prefix);
5398
- const dirPath = path7.join(this.fetchedDir, setDir);
5399
- const filePath = path7.join(dirPath, `${name}.svg`);
5400
- await fs7.mkdir(dirPath, { recursive: true });
5401
- await fs7.writeFile(filePath, svg, "utf-8");
5402
- await this.updateSourcesYaml(setDir, name, sourceUrl);
5403
- }
5404
- /**
5405
- * Update _sources.yaml with source information
5406
- */
5407
- async updateSourcesYaml(setDir, name, sourceUrl) {
5408
- const sourcesPath = path7.join(this.fetchedDir, "_sources.yaml");
5409
- let sources = {};
5410
- try {
5411
- const content = await fs7.readFile(sourcesPath, "utf-8");
5412
- sources = parseYaml7(content) ?? {};
5413
- } catch {
5414
- }
5415
- const key = `${setDir}/${name}.svg`;
5416
- sources[key] = {
5417
- source: sourceUrl,
5418
- fetched_at: (/* @__PURE__ */ new Date()).toISOString(),
5419
- license: this.getLicense(setDir)
5420
- };
5421
- const header = `# Fetched icon sources
5422
- # This file tracks where icons were fetched from for traceability
5423
-
5424
- `;
5425
- await fs7.writeFile(sourcesPath, header + stringifyYaml(sources), "utf-8");
5426
- }
5427
- /**
5428
- * Get the license for an icon set
5429
- */
5430
- getLicense(setDir) {
5431
- for (const config of Object.values(ICON_SOURCES)) {
5432
- if (config.set === setDir) {
5433
- return config.license;
5434
- }
5435
- }
5436
- return "Unknown";
5437
- }
5438
- };
5439
-
5440
- // src/cli/commands/icons.ts
5441
- init_loader2();
5442
- init_convert();
5443
5550
  function extractIconReferences(presentation) {
5444
5551
  const icons = /* @__PURE__ */ new Set();
5445
5552
  function extractFromValue(value) {
@@ -7014,6 +7121,73 @@ Take new screenshots and verify improvements.
7014
7121
  7. \`Read ./screenshots/presentation.003.jpeg\`
7015
7122
  8. Verify fix, move to next slide
7016
7123
 
7124
+ ## Custom Template Creation
7125
+
7126
+ When creating or modifying templates, follow these critical rules.
7127
+
7128
+ ### Critical Rules
7129
+
7130
+ #### 1. CSS Selectors (Marp Scoping)
7131
+
7132
+ When using \`<!-- _class: foo -->\`:
7133
+ - The class is added to \`<section>\` element itself
7134
+ - Use \`section.foo\` when targeting the \`<section>\` element itself
7135
+
7136
+ \`\`\`yaml
7137
+ # Correct
7138
+ output: |
7139
+ <!-- _class: my-slide -->
7140
+ <div class="container">...</div>
7141
+
7142
+ css: |
7143
+ section.my-slide .container { display: flex; }
7144
+ \`\`\`
7145
+
7146
+ \`\`\`yaml
7147
+ # WRONG - This does not target the section element itself
7148
+ css: |
7149
+ .my-slide .container { display: flex; }
7150
+ \`\`\`
7151
+
7152
+ Note: \`.my-slide .container\` is valid for descendants inside the section, but it does not target the \`<section>\` element itself.
7153
+
7154
+ #### 2. HTML + Markdown (CommonMark)
7155
+
7156
+ Markdown inside HTML is only parsed with blank lines:
7157
+
7158
+ \`\`\`yaml
7159
+ # Correct - blank line after <div>
7160
+ output: |
7161
+ <div class="container">
7162
+
7163
+ ## Heading works
7164
+ - List works
7165
+
7166
+ </div>
7167
+ \`\`\`
7168
+
7169
+ \`\`\`yaml
7170
+ # WRONG - no blank line, Markdown won't parse
7171
+ output: |
7172
+ <div class="container">
7173
+ ## This is plain text
7174
+ - Not a list
7175
+ </div>
7176
+ \`\`\`
7177
+
7178
+ ### Visual Regression Test (Required)
7179
+
7180
+ After creating/modifying templates, ALWAYS verify with screenshots:
7181
+
7182
+ \`\`\`bash
7183
+ slide-gen templates screenshot <template-name> --format png -o /tmp/test
7184
+ \`\`\`
7185
+
7186
+ **Check that:**
7187
+ - Layout is correct (columns, grids, flexbox)
7188
+ - Markdown is parsed (headings, lists rendered properly)
7189
+ - CSS is not silently failing
7190
+
7017
7191
  ## Reference Management
7018
7192
 
7019
7193
  For academic presentations, manage citations and references:
@@ -7312,6 +7486,92 @@ Image with text side by side.
7312
7486
  - \`items\` or \`text\`: Content (required)
7313
7487
 
7314
7488
  ## Run \`slide-gen templates list --format llm\` for full list.
7489
+
7490
+ ---
7491
+
7492
+ ## Creating Custom Templates
7493
+
7494
+ ### Template File Structure
7495
+
7496
+ \`\`\`yaml
7497
+ name: my-template
7498
+ description: "Description for LLM"
7499
+ category: custom
7500
+
7501
+ schema:
7502
+ type: object
7503
+ required: [title]
7504
+ properties:
7505
+ title: { type: string }
7506
+ content: { type: string }
7507
+
7508
+ example:
7509
+ title: "Sample"
7510
+ content: "Sample content"
7511
+
7512
+ output: |
7513
+ ---
7514
+ <!-- _class: my-template -->
7515
+
7516
+ # {{ title }}
7517
+
7518
+ <div class="content">
7519
+
7520
+ {{ content }}
7521
+
7522
+ </div>
7523
+
7524
+ css: |
7525
+ section.my-template .content {
7526
+ padding: 1em;
7527
+ }
7528
+ \`\`\`
7529
+
7530
+ ### Critical CSS Rule: Marp Scoping
7531
+
7532
+ When using \`<!-- _class: foo -->\`, Marp adds class to the \`<section>\` element.
7533
+
7534
+ **Correct (targeting the section element):**
7535
+ \`\`\`css
7536
+ section.foo .child { ... }
7537
+ \`\`\`
7538
+
7539
+ **Wrong (for targeting the section element):**
7540
+ \`\`\`css
7541
+ .foo .child { ... }
7542
+ \`\`\`
7543
+
7544
+ Note: \`.foo .child\` is valid for descendants inside the section, but it does not target the \`<section>\` element itself.
7545
+
7546
+ ### Critical HTML Rule: CommonMark
7547
+
7548
+ Markdown inside HTML only parses with blank lines after tags:
7549
+
7550
+ **Correct:**
7551
+ \`\`\`html
7552
+ <div>
7553
+
7554
+ ## Heading works
7555
+
7556
+ </div>
7557
+ \`\`\`
7558
+
7559
+ **Wrong:**
7560
+ \`\`\`html
7561
+ <div>
7562
+ ## Plain text, not heading
7563
+ </div>
7564
+ \`\`\`
7565
+
7566
+ ### Visual Verification
7567
+
7568
+ Always test templates with screenshots:
7569
+
7570
+ \`\`\`bash
7571
+ slide-gen templates screenshot <template-name> --format png -o /tmp/test
7572
+ \`\`\`
7573
+
7574
+ Check: Layout, Markdown parsing, CSS application.
7315
7575
  `;
7316
7576
  }
7317
7577
 
@@ -7989,7 +8249,8 @@ slide-gen validate presentation.yaml
7989
8249
  // src/cli/commands/init.ts
7990
8250
  function getPackageRoot() {
7991
8251
  const __dirname = dirname8(fileURLToPath(import.meta.url));
7992
- if (__dirname.includes(`${sep}src${sep}`) || __dirname.includes("/src/")) {
8252
+ const isInSourceDir = __dirname.endsWith(`${sep}src${sep}cli${sep}commands`) || __dirname.endsWith("/src/cli/commands");
8253
+ if (isInSourceDir) {
7993
8254
  return join17(__dirname, "..", "..", "..");
7994
8255
  }
7995
8256
  return join17(__dirname, "..", "..");
@@ -8565,14 +8826,20 @@ async function executeWatch(inputPath, options) {
8565
8826
  }, debounceMs);
8566
8827
  };
8567
8828
  state.start();
8829
+ const isWSL = !!process.env["WSL_DISTRO_NAME"];
8568
8830
  watcher = chokidarWatch2(inputPath, {
8569
8831
  persistent: true,
8570
8832
  ignoreInitial: true,
8833
+ usePolling: isWSL,
8834
+ ...isWSL && { interval: 100 },
8571
8835
  awaitWriteFinish: {
8572
8836
  stabilityThreshold: 100,
8573
8837
  pollInterval: 50
8574
8838
  }
8575
8839
  });
8840
+ await new Promise((resolve8) => {
8841
+ watcher.on("ready", resolve8);
8842
+ });
8576
8843
  watcher.on("change", handleChange);
8577
8844
  const cleanup = () => {
8578
8845
  state.stop();