@taloswork/ose-shared 0.1.8 → 0.1.10

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 (208) hide show
  1. package/mcp/gen/mcp.json +2 -2
  2. package/package.json +3 -3
  3. package/skills/cozy-static-svg/SKILL.md +76 -0
  4. package/skills/cozy-static-svg/references/approved-preview.png +0 -0
  5. package/skills/cozy-static-svg/references/cross-platform.md +73 -0
  6. package/skills/cozy-static-svg/references/moon_keeps_watch.svg +229 -0
  7. package/skills/cozy-static-svg/references/style-anchor.md +271 -0
  8. package/skills/cozy-static-svg/scripts/artkit.py +321 -0
  9. package/skills/cozy-static-svg/scripts/to_png.py +333 -0
  10. package/skills/diagram-design/SKILL.md +568 -0
  11. package/skills/diagram-design/assets/example-architecture-dark.html +182 -0
  12. package/skills/diagram-design/assets/example-architecture-full.html +194 -0
  13. package/skills/diagram-design/assets/example-architecture.html +183 -0
  14. package/skills/diagram-design/assets/example-bar-dark.html +129 -0
  15. package/skills/diagram-design/assets/example-bar-full.html +105 -0
  16. package/skills/diagram-design/assets/example-bar.html +129 -0
  17. package/skills/diagram-design/assets/example-data-flow-dark.html +143 -0
  18. package/skills/diagram-design/assets/example-data-flow-full.html +185 -0
  19. package/skills/diagram-design/assets/example-data-flow.html +143 -0
  20. package/skills/diagram-design/assets/example-datalake-dark.html +243 -0
  21. package/skills/diagram-design/assets/example-datalake-full.html +248 -0
  22. package/skills/diagram-design/assets/example-datalake.html +269 -0
  23. package/skills/diagram-design/assets/example-dp-integration-dark.html +80 -0
  24. package/skills/diagram-design/assets/example-dp-integration-full.html +90 -0
  25. package/skills/diagram-design/assets/example-dp-integration.html +80 -0
  26. package/skills/diagram-design/assets/example-dp-security-matrix-dark.html +31 -0
  27. package/skills/diagram-design/assets/example-dp-security-matrix-full.html +32 -0
  28. package/skills/diagram-design/assets/example-dp-security-matrix.html +88 -0
  29. package/skills/diagram-design/assets/example-er-dark.html +202 -0
  30. package/skills/diagram-design/assets/example-er-full.html +205 -0
  31. package/skills/diagram-design/assets/example-er.html +202 -0
  32. package/skills/diagram-design/assets/example-flowchart-dark.html +156 -0
  33. package/skills/diagram-design/assets/example-flowchart-full.html +159 -0
  34. package/skills/diagram-design/assets/example-flowchart.html +156 -0
  35. package/skills/diagram-design/assets/example-gantt-dark.html +137 -0
  36. package/skills/diagram-design/assets/example-gantt-full.html +127 -0
  37. package/skills/diagram-design/assets/example-gantt.html +137 -0
  38. package/skills/diagram-design/assets/example-high-level-dark.html +251 -0
  39. package/skills/diagram-design/assets/example-high-level-full.html +247 -0
  40. package/skills/diagram-design/assets/example-high-level-vertical-dark.html +274 -0
  41. package/skills/diagram-design/assets/example-high-level-vertical-full.html +252 -0
  42. package/skills/diagram-design/assets/example-high-level-vertical.html +325 -0
  43. package/skills/diagram-design/assets/example-high-level.html +301 -0
  44. package/skills/diagram-design/assets/example-import-drawio.html +185 -0
  45. package/skills/diagram-design/assets/example-import-mermaid.html +70 -0
  46. package/skills/diagram-design/assets/example-it-state-dark.html +90 -0
  47. package/skills/diagram-design/assets/example-it-state-full.html +69 -0
  48. package/skills/diagram-design/assets/example-it-state.html +128 -0
  49. package/skills/diagram-design/assets/example-layers-dark.html +123 -0
  50. package/skills/diagram-design/assets/example-layers-full.html +126 -0
  51. package/skills/diagram-design/assets/example-layers.html +123 -0
  52. package/skills/diagram-design/assets/example-line-dark.html +111 -0
  53. package/skills/diagram-design/assets/example-line-full.html +105 -0
  54. package/skills/diagram-design/assets/example-line.html +111 -0
  55. package/skills/diagram-design/assets/example-loop-dark.html +100 -0
  56. package/skills/diagram-design/assets/example-loop-full.html +137 -0
  57. package/skills/diagram-design/assets/example-loop-terminal.html +347 -0
  58. package/skills/diagram-design/assets/example-loop.html +100 -0
  59. package/skills/diagram-design/assets/example-medallion-dark.html +159 -0
  60. package/skills/diagram-design/assets/example-medallion-full.html +201 -0
  61. package/skills/diagram-design/assets/example-medallion.html +159 -0
  62. package/skills/diagram-design/assets/example-nested-dark.html +132 -0
  63. package/skills/diagram-design/assets/example-nested-full.html +135 -0
  64. package/skills/diagram-design/assets/example-nested.html +136 -0
  65. package/skills/diagram-design/assets/example-org-chart-dark.html +45 -0
  66. package/skills/diagram-design/assets/example-org-chart-full.html +45 -0
  67. package/skills/diagram-design/assets/example-org-chart.html +45 -0
  68. package/skills/diagram-design/assets/example-paved-road-animated.html +546 -0
  69. package/skills/diagram-design/assets/example-policy-trace-animated.html +490 -0
  70. package/skills/diagram-design/assets/example-process-dark.html +64 -0
  71. package/skills/diagram-design/assets/example-process-full.html +74 -0
  72. package/skills/diagram-design/assets/example-process.html +64 -0
  73. package/skills/diagram-design/assets/example-pyramid-dark.html +119 -0
  74. package/skills/diagram-design/assets/example-pyramid-full.html +122 -0
  75. package/skills/diagram-design/assets/example-pyramid.html +119 -0
  76. package/skills/diagram-design/assets/example-quadrant-consultant.html +166 -0
  77. package/skills/diagram-design/assets/example-quadrant-dark.html +135 -0
  78. package/skills/diagram-design/assets/example-quadrant-full.html +138 -0
  79. package/skills/diagram-design/assets/example-quadrant.html +135 -0
  80. package/skills/diagram-design/assets/example-queue-animated.html +577 -0
  81. package/skills/diagram-design/assets/example-radar-dark.html +139 -0
  82. package/skills/diagram-design/assets/example-radar-full.html +135 -0
  83. package/skills/diagram-design/assets/example-radar.html +139 -0
  84. package/skills/diagram-design/assets/example-scatter-dark.html +141 -0
  85. package/skills/diagram-design/assets/example-scatter-full.html +116 -0
  86. package/skills/diagram-design/assets/example-scatter.html +141 -0
  87. package/skills/diagram-design/assets/example-sequence-dark.html +222 -0
  88. package/skills/diagram-design/assets/example-sequence-full.html +388 -0
  89. package/skills/diagram-design/assets/example-sequence-oauth-dark.html +188 -0
  90. package/skills/diagram-design/assets/example-sequence-oauth-full.html +350 -0
  91. package/skills/diagram-design/assets/example-sequence-oauth.html +227 -0
  92. package/skills/diagram-design/assets/example-sequence.html +222 -0
  93. package/skills/diagram-design/assets/example-state-dark.html +147 -0
  94. package/skills/diagram-design/assets/example-state-full.html +150 -0
  95. package/skills/diagram-design/assets/example-state.html +147 -0
  96. package/skills/diagram-design/assets/example-swimlane-dark.html +172 -0
  97. package/skills/diagram-design/assets/example-swimlane-full.html +175 -0
  98. package/skills/diagram-design/assets/example-swimlane.html +172 -0
  99. package/skills/diagram-design/assets/example-timeline-dark.html +135 -0
  100. package/skills/diagram-design/assets/example-timeline-full.html +138 -0
  101. package/skills/diagram-design/assets/example-timeline.html +135 -0
  102. package/skills/diagram-design/assets/example-tree-dark.html +173 -0
  103. package/skills/diagram-design/assets/example-tree-full.html +176 -0
  104. package/skills/diagram-design/assets/example-tree.html +173 -0
  105. package/skills/diagram-design/assets/example-venn-dark.html +132 -0
  106. package/skills/diagram-design/assets/example-venn-full.html +135 -0
  107. package/skills/diagram-design/assets/example-venn.html +110 -0
  108. package/skills/diagram-design/assets/icons.html +230 -0
  109. package/skills/diagram-design/assets/index.html +369 -0
  110. package/skills/diagram-design/assets/template-dark.html +85 -0
  111. package/skills/diagram-design/assets/template-full.html +413 -0
  112. package/skills/diagram-design/assets/template-motion.html +434 -0
  113. package/skills/diagram-design/assets/template-terminal.html +173 -0
  114. package/skills/diagram-design/assets/template.html +86 -0
  115. package/skills/diagram-design/references/animation.md +141 -0
  116. package/skills/diagram-design/references/export.md +136 -0
  117. package/skills/diagram-design/references/import-drawio.md +171 -0
  118. package/skills/diagram-design/references/import-mermaid.md +126 -0
  119. package/skills/diagram-design/references/onboarding.md +297 -0
  120. package/skills/diagram-design/references/output-spec.md +181 -0
  121. package/skills/diagram-design/references/primitive-annotation.md +36 -0
  122. package/skills/diagram-design/references/primitive-icons.md +827 -0
  123. package/skills/diagram-design/references/primitive-sketchy.md +43 -0
  124. package/skills/diagram-design/references/primitive-terminal.md +76 -0
  125. package/skills/diagram-design/references/semantic-patterns.md +122 -0
  126. package/skills/diagram-design/references/style-guide.md +139 -0
  127. package/skills/diagram-design/references/type-architecture.md +78 -0
  128. package/skills/diagram-design/references/type-bar.md +48 -0
  129. package/skills/diagram-design/references/type-data-flow.md +374 -0
  130. package/skills/diagram-design/references/type-dp-integration.md +410 -0
  131. package/skills/diagram-design/references/type-dp-security-matrix.md +379 -0
  132. package/skills/diagram-design/references/type-er.md +23 -0
  133. package/skills/diagram-design/references/type-flowchart.md +23 -0
  134. package/skills/diagram-design/references/type-gantt.md +45 -0
  135. package/skills/diagram-design/references/type-high-level.md +458 -0
  136. package/skills/diagram-design/references/type-it-state.md +470 -0
  137. package/skills/diagram-design/references/type-layers.md +26 -0
  138. package/skills/diagram-design/references/type-line.md +44 -0
  139. package/skills/diagram-design/references/type-loop.md +223 -0
  140. package/skills/diagram-design/references/type-medallion.md +356 -0
  141. package/skills/diagram-design/references/type-nested.md +22 -0
  142. package/skills/diagram-design/references/type-org-chart.md +44 -0
  143. package/skills/diagram-design/references/type-process.md +495 -0
  144. package/skills/diagram-design/references/type-pyramid.md +33 -0
  145. package/skills/diagram-design/references/type-quadrant.md +81 -0
  146. package/skills/diagram-design/references/type-radar.md +80 -0
  147. package/skills/diagram-design/references/type-scatter.md +39 -0
  148. package/skills/diagram-design/references/type-sequence.md +130 -0
  149. package/skills/diagram-design/references/type-state.md +21 -0
  150. package/skills/diagram-design/references/type-swimlane.md +20 -0
  151. package/skills/diagram-design/references/type-timeline.md +20 -0
  152. package/skills/diagram-design/references/type-tree.md +24 -0
  153. package/skills/diagram-design/references/type-venn.md +26 -0
  154. package/skills/diagram-design/scripts/drawio_extract.py +856 -0
  155. package/skills/diagram-design/scripts/mermaid_extract.py +1285 -0
  156. package/skills/diagram-design/scripts/self_check.py +389 -0
  157. package/skills/document/SKILL.md +63 -75
  158. package/skills/document/bin/deck-build.cjs +51 -1
  159. package/skills/document/bin/deck-qa.cjs +18745 -279
  160. package/skills/document/bin/doc-convert.cjs +9280 -76489
  161. package/skills/document/bin/doc-fill.cjs +0 -0
  162. package/skills/document/bin/doc-gen.cjs +3043 -46825
  163. package/skills/document/bin/doc-run.cjs +65095 -1967
  164. package/skills/document/bin/doc-scan.cjs +0 -0
  165. package/skills/document/bin/doc-text.cjs +0 -0
  166. package/skills/document/bin/doc-validate.cjs +18067 -226
  167. package/skills/document/bin/doc-xml.cjs +1 -1
  168. package/skills/document/bin/docx-annotate.cjs +0 -0
  169. package/skills/document/bin/html2pptx.cjs +32498 -145280
  170. package/skills/document/bin/ocr-worker.cjs +0 -0
  171. package/skills/document/bin/ocr.cjs +0 -0
  172. package/skills/document/bin/pdf-form.cjs +0 -0
  173. package/skills/document/bin/pdf-images.cjs +0 -0
  174. package/skills/document/bin/pdf-ops.cjs +0 -0
  175. package/skills/document/bin/pdf-render.cjs +11 -9
  176. package/skills/document/bin/pdf-tables.cjs +0 -0
  177. package/skills/document/bin/pptx-clone.cjs +21 -25
  178. package/skills/document/bin/pptx-embed-fonts.cjs +8 -6
  179. package/skills/document/bin/pptx-set-image.cjs +0 -0
  180. package/skills/document/bin/pptx-slide.cjs +22 -32
  181. package/skills/document/bin/pptx-theme.cjs +1 -20
  182. package/skills/document/bin/pptx-tokenize.cjs +1 -1
  183. package/skills/document/bin/xlsx-query.cjs +0 -0
  184. package/skills/document/bin/xlsx-recalc.cjs +0 -0
  185. package/skills/document/bin-reference.md +35 -0
  186. package/skills/document/content-pipeline.md +19 -18
  187. package/skills/document/deck-design.md +110 -211
  188. package/skills/document/layouts/README.md +4 -33
  189. package/skills/document/motion/README.md +4 -2
  190. package/skills/document/office.md +5 -1
  191. package/skills/document/rewrite.md +43 -99
  192. package/skills/document/styles/README.md +0 -7
  193. package/skills/document/theme-system.md +1 -0
  194. package/skills/document/themes/README.md +5 -5
  195. package/skills/document/themes/anthropic-clay.json +1 -7
  196. package/skills/document/themes/apple-keynote.json +1 -7
  197. package/skills/document/themes/black-gold.json +1 -2
  198. package/skills/document/themes/cinereel.json +1 -2
  199. package/skills/document/themes/cool-tech.json +1 -2
  200. package/skills/document/themes/hand-drawn.json +1 -2
  201. package/skills/document/themes/ivory-gold.json +1 -2
  202. package/skills/document/themes/magazine-ink.json +1 -2
  203. package/skills/document/themes/mckinsey-consulting.json +1 -7
  204. package/skills/document/themes/mono.json +1 -2
  205. package/skills/document/themes/swiss-grid.json +1 -2
  206. package/skills/document/themes/warm-editorial.json +1 -2
  207. package/skills/image-layout/SKILL.md +1 -1
  208. package/skills/image-layout/bin/html2img.cjs +0 -0
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -47,20 +47,22 @@ function die(msg, code = 1) {
47
47
  process.exit(code);
48
48
  }
49
49
  function parsePages(spec) {
50
- const m = /^(\d+)(?:-(\d+))?$/.exec(spec.trim());
51
- if (!m) die(`bad page spec: ${spec} (use N or N-M)`, 64);
52
- const from = Number(m[1]);
53
- const to = m[2] ? Number(m[2]) : from;
54
- if (from < 1 || to < from) die(`bad page range: ${spec}`, 64);
55
- const pages = [];
56
- for (let p = from; p <= to; p++) pages.push(p);
57
- return pages;
50
+ const pages = /* @__PURE__ */ new Set();
51
+ for (const part of spec.split(",")) {
52
+ const m = /^(\d+)(?:-(\d+))?$/.exec(part.trim());
53
+ if (!m) die(`bad page spec: ${spec} (use N, N-M or a list like 3,7-8)`, 64);
54
+ const from = Number(m[1]);
55
+ const to = m[2] ? Number(m[2]) : from;
56
+ if (from < 1 || to < from) die(`bad page range: ${part.trim()}`, 64);
57
+ for (let p = from; p <= to; p++) pages.add(p);
58
+ }
59
+ return [...pages].sort((a, b) => a - b);
58
60
  }
59
61
  async function main() {
60
62
  const file = process.argv[2];
61
63
  const spec = process.argv[3];
62
64
  const scaleArg = process.argv[4];
63
- if (!file || !spec) die("usage: pdf-render <file.pdf> <pages|all> [scale]", 64);
65
+ if (!file || !spec) die("usage: pdf-render <file.pdf> <pages|all> [scale] (pages: N | N-M | 3,7-8)", 64);
64
66
  let scale = 2;
65
67
  if (scaleArg !== void 0) {
66
68
  scale = Number(scaleArg);
File without changes
@@ -9855,14 +9855,14 @@ function unescXml(s) {
9855
9855
  return "'";
9856
9856
  default: {
9857
9857
  const n = code.startsWith("#x") || code.startsWith("#X") ? parseInt(code.slice(2), 16) : parseInt(code.slice(1), 10);
9858
- return Number.isFinite(n) ? String.fromCodePoint(n) : whole;
9858
+ return Number.isInteger(n) && n >= 0 && n <= 1114111 ? String.fromCodePoint(n) : whole;
9859
9859
  }
9860
9860
  }
9861
9861
  });
9862
9862
  }
9863
- function visualWidth(text3) {
9863
+ function visualWidth(text) {
9864
9864
  let w = 0;
9865
- for (const ch of text3) {
9865
+ for (const ch of text) {
9866
9866
  const cp = ch.codePointAt(0);
9867
9867
  if (ch === " " || ch === " ")
9868
9868
  w += 0.35;
@@ -9891,9 +9891,9 @@ function replaceTextAcrossRuns(xml, find, replacement, opts) {
9891
9891
  const segs = [];
9892
9892
  let flat = "";
9893
9893
  for (const m of para.matchAll(textRe)) {
9894
- const text3 = unescXml(m[2]);
9895
- segs.push({ el: m[0], attrs: m[1] ?? "", text: text3, start: flat.length });
9896
- flat += text3;
9894
+ const text = unescXml(m[2]);
9895
+ segs.push({ el: m[0], attrs: m[1] ?? "", text, start: flat.length });
9896
+ flat += text;
9897
9897
  }
9898
9898
  if (!flat.includes(find))
9899
9899
  return para;
@@ -9936,28 +9936,30 @@ function replaceTextAcrossRuns(xml, find, replacement, opts) {
9936
9936
  return { xml: rewritten, count };
9937
9937
  }
9938
9938
 
9939
- // packages/core/dist/document/pptx-register-slide.js
9940
- var SLIDE_CT = "application/vnd.openxmlformats-officedocument.presentationml.slide+xml";
9941
- var SLIDE_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide";
9942
- async function text(zip, path) {
9939
+ // packages/core/dist/document/pptx-parts.js
9940
+ async function readPptxPart(zip, path) {
9943
9941
  const f = zip.file(path);
9944
9942
  if (!f)
9945
9943
  throw new Error(`not a PowerPoint file (${path} missing)`);
9946
9944
  return f.async("string");
9947
9945
  }
9946
+
9947
+ // packages/core/dist/document/pptx-register-slide.js
9948
+ var SLIDE_CT = "application/vnd.openxmlformats-officedocument.presentationml.slide+xml";
9949
+ var SLIDE_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide";
9948
9950
  async function registerSlidePart(zip, newNum, newXmlPath) {
9949
9951
  const ctPath = "[Content_Types].xml";
9950
- const ct = await text(zip, ctPath);
9952
+ const ct = await readPptxPart(zip, ctPath);
9951
9953
  if (!ct.includes(`PartName="/${newXmlPath}"`)) {
9952
9954
  zip.file(ctPath, ct.replace("</Types>", `<Override PartName="/${newXmlPath}" ContentType="${SLIDE_CT}"/></Types>`));
9953
9955
  }
9954
9956
  const presRelsPath = "ppt/_rels/presentation.xml.rels";
9955
- const presRels = await text(zip, presRelsPath);
9957
+ const presRels = await readPptxPart(zip, presRelsPath);
9956
9958
  const maxRid = Math.max(0, ...[...presRels.matchAll(/Id="rId(\d+)"/g)].map((m) => Number(m[1])));
9957
9959
  const newRid = `rId${maxRid + 1}`;
9958
9960
  zip.file(presRelsPath, presRels.replace("</Relationships>", `<Relationship Id="${newRid}" Type="${SLIDE_REL_TYPE}" Target="slides/slide${newNum}.xml"/></Relationships>`));
9959
9961
  const presPath = "ppt/presentation.xml";
9960
- const pres = await text(zip, presPath);
9962
+ const pres = await readPptxPart(zip, presPath);
9961
9963
  const maxSldId = Math.max(255, ...[...pres.matchAll(/<p:sldId id="(\d+)"/g)].map((m) => Number(m[1])));
9962
9964
  const updated = pres.replace("</p:sldIdLst>", `<p:sldId id="${maxSldId + 1}" r:id="${newRid}"/></p:sldIdLst>`);
9963
9965
  if (updated === pres)
@@ -9998,12 +10000,6 @@ function capacityWarning(slideXml, token, value) {
9998
10000
  return void 0;
9999
10001
  return `${token}: replacement is ~${Math.ceil(width)} width-units, slot fits ~${Math.floor(capacity)} (${Math.round(widthPt)}\xD7${Math.round(heightPt)}pt box @ ${fontPt}pt) \u2014 likely overflow. Shorten the wording; do NOT shrink the font to make it fit.`;
10000
10002
  }
10001
- async function text2(zip, path) {
10002
- const f = zip.file(path);
10003
- if (!f)
10004
- throw new Error(`not a PowerPoint file (${path} missing)`);
10005
- return f.async("string");
10006
- }
10007
10003
  async function clonePptxSlide(bytes, slideNumber, replacements = {}) {
10008
10004
  if (!Number.isInteger(slideNumber) || slideNumber < 1) {
10009
10005
  throw new Error(`slide number must be a positive integer, got ${slideNumber}`);
@@ -10023,7 +10019,7 @@ async function clonePptxSlide(bytes, slideNumber, replacements = {}) {
10023
10019
  const newNum = Math.max(...slideNums) + 1;
10024
10020
  const srcXmlPath = `ppt/slides/slide${slideNumber}.xml`;
10025
10021
  const newXmlPath = `ppt/slides/slide${newNum}.xml`;
10026
- let slideXml = await text2(zip, srcXmlPath);
10022
+ let slideXml = await readPptxPart(zip, srcXmlPath);
10027
10023
  const replaced = [];
10028
10024
  const missing = [];
10029
10025
  const warnings = [];
@@ -10076,7 +10072,7 @@ async function removePptxSlide(bytes, slideNumber) {
10076
10072
  const xmlPath = `ppt/slides/slide${slideNumber}.xml`;
10077
10073
  const relsPath = `ppt/slides/_rels/slide${slideNumber}.xml.rels`;
10078
10074
  const presRelsPath = "ppt/_rels/presentation.xml.rels";
10079
- const presRels = await text2(zip, presRelsPath);
10075
+ const presRels = await readPptxPart(zip, presRelsPath);
10080
10076
  const targetRe = new RegExp(`Target="(/ppt/)?slides/slide${slideNumber}\\.xml"`);
10081
10077
  const relMatch = findElement(presRels, "Relationship", (r) => r.includes(SLIDE_REL_TYPE) && targetRe.test(r));
10082
10078
  if (!relMatch)
@@ -10086,19 +10082,19 @@ async function removePptxSlide(bytes, slideNumber) {
10086
10082
  throw new Error(`slide ${slideNumber}'s relationship carries no Id (corrupt deck?)`);
10087
10083
  zip.file(presRelsPath, presRels.replace(relMatch, ""));
10088
10084
  const presPath = "ppt/presentation.xml";
10089
- const pres = await text2(zip, presPath);
10085
+ const pres = await readPptxPart(zip, presPath);
10090
10086
  const sld = removeElement(pres, "p:sldId", (el) => el.includes(`r:id="${rId}"`));
10091
10087
  if (!sld.removed)
10092
10088
  throw new Error(`slide ${slideNumber} (${rId}) not found in the slide list (corrupt deck?)`);
10093
10089
  zip.file(presPath, sld.xml);
10094
- const slideRels = zip.file(relsPath) ? await text2(zip, relsPath) : "";
10090
+ const slideRels = zip.file(relsPath) ? await readPptxPart(zip, relsPath) : "";
10095
10091
  const notesTarget = /<Relationship\b[^>]*Type="[^"]*\/notesSlide"[^>]*Target="\.\.\/(notesSlides\/notesSlide\d+\.xml)"/.exec(slideRels)?.[1];
10096
10092
  const removedParts = [xmlPath, relsPath];
10097
10093
  if (notesTarget) {
10098
10094
  const otherSlideRels = Object.keys(zip.files).filter((p) => /^ppt\/slides\/_rels\/slide\d+\.xml\.rels$/.test(p) && p !== relsPath);
10099
10095
  let shared = false;
10100
10096
  for (const p of otherSlideRels) {
10101
- if ((await text2(zip, p)).includes(notesTarget)) {
10097
+ if ((await readPptxPart(zip, p)).includes(notesTarget)) {
10102
10098
  shared = true;
10103
10099
  break;
10104
10100
  }
@@ -10106,7 +10102,7 @@ async function removePptxSlide(bytes, slideNumber) {
10106
10102
  if (!shared)
10107
10103
  removedParts.push(`ppt/${notesTarget}`, `ppt/notesSlides/_rels/${notesTarget.split("/")[1]}.rels`);
10108
10104
  }
10109
- let ct = await text2(zip, "[Content_Types].xml");
10105
+ let ct = await readPptxPart(zip, "[Content_Types].xml");
10110
10106
  for (const part of removedParts) {
10111
10107
  zip.remove(part);
10112
10108
  ct = removeElement(ct, "Override", (el) => el.includes(`PartName="/${part}"`)).xml;
@@ -9826,14 +9826,16 @@ function escXml(s) {
9826
9826
  return s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&apos;" })[c]);
9827
9827
  }
9828
9828
 
9829
- // packages/core/dist/document/pptx-fonts.js
9830
- var FONT_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/font";
9831
- async function text(zip, path) {
9829
+ // packages/core/dist/document/pptx-parts.js
9830
+ async function readPptxPart(zip, path) {
9832
9831
  const f = zip.file(path);
9833
9832
  if (!f)
9834
9833
  throw new Error(`not a PowerPoint file (${path} missing)`);
9835
9834
  return f.async("string");
9836
9835
  }
9836
+
9837
+ // packages/core/dist/document/pptx-fonts.js
9838
+ var FONT_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/font";
9837
9839
  async function embedFonts(bytes, fonts) {
9838
9840
  if (!fonts.length)
9839
9841
  return bytes;
@@ -9843,13 +9845,13 @@ async function embedFonts(bytes, fonts) {
9843
9845
  } catch (e) {
9844
9846
  throw new Error(`not a readable .pptx: ${e.message}`);
9845
9847
  }
9846
- let ct = await text(zip, "[Content_Types].xml");
9848
+ let ct = await readPptxPart(zip, "[Content_Types].xml");
9847
9849
  if (!ct.includes('Extension="fntdata"')) {
9848
9850
  ct = ct.replace("</Types>", `<Default Extension="fntdata" ContentType="application/x-fontdata"/></Types>`);
9849
9851
  zip.file("[Content_Types].xml", ct);
9850
9852
  }
9851
9853
  const relsPath = "ppt/_rels/presentation.xml.rels";
9852
- let rels = await text(zip, relsPath);
9854
+ let rels = await readPptxPart(zip, relsPath);
9853
9855
  let rid = Math.max(0, ...[...rels.matchAll(/Id="rId(\d+)"/g)].map((m) => Number(m[1])));
9854
9856
  let fontNo = Object.keys(zip.files).filter((p) => /^ppt\/fonts\/font\d+\.fntdata$/.test(p)).length;
9855
9857
  const byFace = /* @__PURE__ */ new Map();
@@ -9875,7 +9877,7 @@ async function embedFonts(bytes, fonts) {
9875
9877
  }
9876
9878
  zip.file(relsPath, rels);
9877
9879
  const presPath = "ppt/presentation.xml";
9878
- let pres = await text(zip, presPath);
9880
+ let pres = await readPptxPart(zip, presPath);
9879
9881
  if (!/<p:presentation\b[^>]*\bembedTrueTypeFonts=/.test(pres)) {
9880
9882
  pres = pres.replace(/<p:presentation\b/, '<p:presentation embedTrueTypeFonts="1"');
9881
9883
  }
File without changes
@@ -9821,28 +9821,30 @@ var import_promises = require("node:fs/promises");
9821
9821
  // packages/core/dist/document/pptx-slide.js
9822
9822
  var import_jszip = __toESM(require_lib3(), 1);
9823
9823
 
9824
- // packages/core/dist/document/pptx-register-slide.js
9825
- var SLIDE_CT = "application/vnd.openxmlformats-officedocument.presentationml.slide+xml";
9826
- var SLIDE_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide";
9827
- async function text(zip, path) {
9824
+ // packages/core/dist/document/pptx-parts.js
9825
+ async function readPptxPart(zip, path) {
9828
9826
  const f = zip.file(path);
9829
9827
  if (!f)
9830
9828
  throw new Error(`not a PowerPoint file (${path} missing)`);
9831
9829
  return f.async("string");
9832
9830
  }
9831
+
9832
+ // packages/core/dist/document/pptx-register-slide.js
9833
+ var SLIDE_CT = "application/vnd.openxmlformats-officedocument.presentationml.slide+xml";
9834
+ var SLIDE_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide";
9833
9835
  async function registerSlidePart(zip, newNum, newXmlPath) {
9834
9836
  const ctPath = "[Content_Types].xml";
9835
- const ct = await text(zip, ctPath);
9837
+ const ct = await readPptxPart(zip, ctPath);
9836
9838
  if (!ct.includes(`PartName="/${newXmlPath}"`)) {
9837
9839
  zip.file(ctPath, ct.replace("</Types>", `<Override PartName="/${newXmlPath}" ContentType="${SLIDE_CT}"/></Types>`));
9838
9840
  }
9839
9841
  const presRelsPath = "ppt/_rels/presentation.xml.rels";
9840
- const presRels = await text(zip, presRelsPath);
9842
+ const presRels = await readPptxPart(zip, presRelsPath);
9841
9843
  const maxRid = Math.max(0, ...[...presRels.matchAll(/Id="rId(\d+)"/g)].map((m) => Number(m[1])));
9842
9844
  const newRid = `rId${maxRid + 1}`;
9843
9845
  zip.file(presRelsPath, presRels.replace("</Relationships>", `<Relationship Id="${newRid}" Type="${SLIDE_REL_TYPE}" Target="slides/slide${newNum}.xml"/></Relationships>`));
9844
9846
  const presPath = "ppt/presentation.xml";
9845
- const pres = await text(zip, presPath);
9847
+ const pres = await readPptxPart(zip, presPath);
9846
9848
  const maxSldId = Math.max(255, ...[...pres.matchAll(/<p:sldId id="(\d+)"/g)].map((m) => Number(m[1])));
9847
9849
  const updated = pres.replace("</p:sldIdLst>", `<p:sldId id="${maxSldId + 1}" r:id="${newRid}"/></p:sldIdLst>`);
9848
9850
  if (updated === pres)
@@ -9853,12 +9855,6 @@ async function registerSlidePart(zip, newNum, newXmlPath) {
9853
9855
  // packages/core/dist/document/pptx-slide.js
9854
9856
  var LAYOUT_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout";
9855
9857
  var BLANK_SLIDE_XML = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"><p:cSld><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr/></p:spTree></p:cSld><p:clrMapOvr><a:masterClrMapping/></p:clrMapOvr></p:sld>';
9856
- async function text2(zip, path) {
9857
- const f = zip.file(path);
9858
- if (!f)
9859
- throw new Error(`not a PowerPoint file (${path} missing)`);
9860
- return f.async("string");
9861
- }
9862
9858
  async function load(bytes) {
9863
9859
  try {
9864
9860
  return await import_jszip.default.loadAsync(Buffer.from(bytes));
@@ -9908,7 +9904,7 @@ async function addBlankSlide(bytes, opts = {}) {
9908
9904
  async function reorderPptxSlides(bytes, order) {
9909
9905
  const zip = await load(bytes);
9910
9906
  const presPath = "ppt/presentation.xml";
9911
- const pres = await text2(zip, presPath);
9907
+ const pres = await readPptxPart(zip, presPath);
9912
9908
  const listMatch = /(<p:sldIdLst\b[^>]*>)([\s\S]*?)(<\/p:sldIdLst>)/.exec(pres);
9913
9909
  if (!listMatch)
9914
9910
  throw new Error("no <p:sldIdLst> in presentation.xml (corrupt deck?)");
@@ -9974,14 +9970,14 @@ function unescXml(s) {
9974
9970
  return "'";
9975
9971
  default: {
9976
9972
  const n = code.startsWith("#x") || code.startsWith("#X") ? parseInt(code.slice(2), 16) : parseInt(code.slice(1), 10);
9977
- return Number.isFinite(n) ? String.fromCodePoint(n) : whole;
9973
+ return Number.isInteger(n) && n >= 0 && n <= 1114111 ? String.fromCodePoint(n) : whole;
9978
9974
  }
9979
9975
  }
9980
9976
  });
9981
9977
  }
9982
- function visualWidth(text4) {
9978
+ function visualWidth(text) {
9983
9979
  let w = 0;
9984
- for (const ch of text4) {
9980
+ for (const ch of text) {
9985
9981
  const cp = ch.codePointAt(0);
9986
9982
  if (ch === " " || ch === " ")
9987
9983
  w += 0.35;
@@ -10010,9 +10006,9 @@ function replaceTextAcrossRuns(xml, find, replacement, opts) {
10010
10006
  const segs = [];
10011
10007
  let flat = "";
10012
10008
  for (const m of para.matchAll(textRe)) {
10013
- const text4 = unescXml(m[2]);
10014
- segs.push({ el: m[0], attrs: m[1] ?? "", text: text4, start: flat.length });
10015
- flat += text4;
10009
+ const text = unescXml(m[2]);
10010
+ segs.push({ el: m[0], attrs: m[1] ?? "", text, start: flat.length });
10011
+ flat += text;
10016
10012
  }
10017
10013
  if (!flat.includes(find))
10018
10014
  return para;
@@ -10088,12 +10084,6 @@ function capacityWarning(slideXml, token, value) {
10088
10084
  return void 0;
10089
10085
  return `${token}: replacement is ~${Math.ceil(width)} width-units, slot fits ~${Math.floor(capacity)} (${Math.round(widthPt)}\xD7${Math.round(heightPt)}pt box @ ${fontPt}pt) \u2014 likely overflow. Shorten the wording; do NOT shrink the font to make it fit.`;
10090
10086
  }
10091
- async function text3(zip, path) {
10092
- const f = zip.file(path);
10093
- if (!f)
10094
- throw new Error(`not a PowerPoint file (${path} missing)`);
10095
- return f.async("string");
10096
- }
10097
10087
  async function clonePptxSlide(bytes, slideNumber, replacements = {}) {
10098
10088
  if (!Number.isInteger(slideNumber) || slideNumber < 1) {
10099
10089
  throw new Error(`slide number must be a positive integer, got ${slideNumber}`);
@@ -10113,7 +10103,7 @@ async function clonePptxSlide(bytes, slideNumber, replacements = {}) {
10113
10103
  const newNum = Math.max(...slideNums) + 1;
10114
10104
  const srcXmlPath = `ppt/slides/slide${slideNumber}.xml`;
10115
10105
  const newXmlPath = `ppt/slides/slide${newNum}.xml`;
10116
- let slideXml = await text3(zip, srcXmlPath);
10106
+ let slideXml = await readPptxPart(zip, srcXmlPath);
10117
10107
  const replaced = [];
10118
10108
  const missing = [];
10119
10109
  const warnings = [];
@@ -10166,7 +10156,7 @@ async function removePptxSlide(bytes, slideNumber) {
10166
10156
  const xmlPath = `ppt/slides/slide${slideNumber}.xml`;
10167
10157
  const relsPath = `ppt/slides/_rels/slide${slideNumber}.xml.rels`;
10168
10158
  const presRelsPath = "ppt/_rels/presentation.xml.rels";
10169
- const presRels = await text3(zip, presRelsPath);
10159
+ const presRels = await readPptxPart(zip, presRelsPath);
10170
10160
  const targetRe = new RegExp(`Target="(/ppt/)?slides/slide${slideNumber}\\.xml"`);
10171
10161
  const relMatch = findElement(presRels, "Relationship", (r) => r.includes(SLIDE_REL_TYPE) && targetRe.test(r));
10172
10162
  if (!relMatch)
@@ -10176,19 +10166,19 @@ async function removePptxSlide(bytes, slideNumber) {
10176
10166
  throw new Error(`slide ${slideNumber}'s relationship carries no Id (corrupt deck?)`);
10177
10167
  zip.file(presRelsPath, presRels.replace(relMatch, ""));
10178
10168
  const presPath = "ppt/presentation.xml";
10179
- const pres = await text3(zip, presPath);
10169
+ const pres = await readPptxPart(zip, presPath);
10180
10170
  const sld = removeElement(pres, "p:sldId", (el) => el.includes(`r:id="${rId}"`));
10181
10171
  if (!sld.removed)
10182
10172
  throw new Error(`slide ${slideNumber} (${rId}) not found in the slide list (corrupt deck?)`);
10183
10173
  zip.file(presPath, sld.xml);
10184
- const slideRels = zip.file(relsPath) ? await text3(zip, relsPath) : "";
10174
+ const slideRels = zip.file(relsPath) ? await readPptxPart(zip, relsPath) : "";
10185
10175
  const notesTarget = /<Relationship\b[^>]*Type="[^"]*\/notesSlide"[^>]*Target="\.\.\/(notesSlides\/notesSlide\d+\.xml)"/.exec(slideRels)?.[1];
10186
10176
  const removedParts = [xmlPath, relsPath];
10187
10177
  if (notesTarget) {
10188
10178
  const otherSlideRels = Object.keys(zip.files).filter((p) => /^ppt\/slides\/_rels\/slide\d+\.xml\.rels$/.test(p) && p !== relsPath);
10189
10179
  let shared = false;
10190
10180
  for (const p of otherSlideRels) {
10191
- if ((await text3(zip, p)).includes(notesTarget)) {
10181
+ if ((await readPptxPart(zip, p)).includes(notesTarget)) {
10192
10182
  shared = true;
10193
10183
  break;
10194
10184
  }
@@ -10196,7 +10186,7 @@ async function removePptxSlide(bytes, slideNumber) {
10196
10186
  if (!shared)
10197
10187
  removedParts.push(`ppt/${notesTarget}`, `ppt/notesSlides/_rels/${notesTarget.split("/")[1]}.rels`);
10198
10188
  }
10199
- let ct = await text3(zip, "[Content_Types].xml");
10189
+ let ct = await readPptxPart(zip, "[Content_Types].xml");
10200
10190
  for (const part of removedParts) {
10201
10191
  zip.remove(part);
10202
10192
  ct = removeElement(ct, "Override", (el) => el.includes(`PartName="/${part}"`)).xml;
@@ -9852,18 +9852,6 @@ function fontFace(fontScheme, which) {
9852
9852
  const ea = /<a:ea typeface="([^"]*)"/.exec(seg)?.[1];
9853
9853
  return latin || ea || void 0;
9854
9854
  }
9855
- function masterSize(master, block) {
9856
- const seg = new RegExp(`<p:${block}>([\\s\\S]*?)</p:${block}>`).exec(master)?.[1];
9857
- const lvl1 = seg && /<a:lvl1pPr[\s\S]*?<a:defRPr[^>]*\bsz="(\d+)"/.exec(seg)?.[1];
9858
- return lvl1 ? Number(lvl1) / 100 : void 0;
9859
- }
9860
- function clamp(v, lo, hi, label, warnings) {
9861
- if (v < lo || v > hi) {
9862
- warnings.push(`${label} ${v}pt is outside the generated-deck band [${lo}, ${hi}] \u2014 clamped`);
9863
- return Math.min(hi, Math.max(lo, v));
9864
- }
9865
- return v;
9866
- }
9867
9855
  async function extractPptxTheme(bytes) {
9868
9856
  let zip;
9869
9857
  try {
@@ -9905,10 +9893,6 @@ async function extractPptxTheme(bytes) {
9905
9893
  const fontScheme = /<a:fontScheme[\s\S]*?<\/a:fontScheme>/.exec(themeXml)?.[0] ?? "";
9906
9894
  const heading = fontFace(fontScheme, "majorFont");
9907
9895
  const body = fontFace(fontScheme, "minorFont");
9908
- const masterFile = zip.file("ppt/slideMasters/slideMaster1.xml") ?? zip.file(/^ppt\/slideMasters\/slideMaster\d+\.xml$/)[0];
9909
- const master = masterFile ? await masterFile.async("string") : "";
9910
- const rawTitle = masterSize(master, "titleStyle");
9911
- const rawBody = masterSize(master, "bodyStyle");
9912
9896
  const theme = {
9913
9897
  ...heading || body ? { fonts: { ...heading ? { heading } : {}, ...body ? { body } : {} } } : {},
9914
9898
  colors: {
@@ -9922,10 +9906,7 @@ async function extractPptxTheme(bytes) {
9922
9906
  pptx: {
9923
9907
  background: lt1,
9924
9908
  titleColor: accent1,
9925
- textColor: dk1,
9926
- ...rawTitle ? { titleSize: clamp(rawTitle, 20, 40, "master title size", warnings) } : {},
9927
- ...rawBody ? { bodySize: clamp(rawBody, 12, 20, "master body size", warnings) } : {},
9928
- slideNumber: true
9909
+ textColor: dk1
9929
9910
  }
9930
9911
  };
9931
9912
  return { theme, warnings };
@@ -9837,7 +9837,7 @@ function unescXml(s) {
9837
9837
  return "'";
9838
9838
  default: {
9839
9839
  const n = code.startsWith("#x") || code.startsWith("#X") ? parseInt(code.slice(2), 16) : parseInt(code.slice(1), 10);
9840
- return Number.isFinite(n) ? String.fromCodePoint(n) : whole;
9840
+ return Number.isInteger(n) && n >= 0 && n <= 1114111 ? String.fromCodePoint(n) : whole;
9841
9841
  }
9842
9842
  }
9843
9843
  });
File without changes
File without changes
@@ -0,0 +1,35 @@
1
+ # Script reference (L3)
2
+
3
+ Every bin in `bin/`, with its full contract. SKILL.md lists them one line each;
4
+ read the entry here before the first call of a bin you have not used in this task.
5
+
6
+ - `bin/doc-scan.cjs <file.pdf>` — per-page signals as JSON.
7
+ - `bin/doc-text.cjs <file.pdf>` — extract a PDF's text layer.
8
+ - `bin/pdf-render.cjs <file.pdf> <pages|all> [scale]` — render pages to PNG for vision (`pages` = `3` | `2-5` | `3,7-8` | `all`). `scale` (default `2` ≈ 144dpi) trades resolution for vision tokens: pass `1` (≈72dpi, ¼ the pixels ≈ ¼ the tokens) for slide-composition/layout QA; keep `2` when fine print must stay legible (formulas/tables). Reading previews is the priciest step in the deck loop — render cheap + read only what changed (a deck's final Office pass hands you its images: `deck-qa --final`).
9
+ - `bin/pdf-images.cjs <file.pdf> [pages]` — extract EMBEDDED images at native resolution with page placement (`pages` = `N` | `N-M` | `all`, default all). Prints a JSON manifest (page/index/bbox/size/path). Use this — not a full-page `pdf-render` — when you need the original figures/photos as their own files (e.g. rebuilding a deck). Vector/text-only pages yield nothing → fall back to `pdf-render`.
10
+ - `bin/pdf-tables.cjs <file.pdf> [page] [minCols]` — recover text-layer tables as rows (ruled/scanned tables need render + vision instead).
11
+ - `bin/ocr.cjs <image.png|jpg> [lang]` — read text off a scanned image (tesseract.js). For a scanned PDF, `pdf-render` a page first, then OCR the PNG. Language data downloads on first use / set `$TALOS_TESSDATA` for offline. **Prefer vision** when a vision model is available; OCR is the text-only fallback.
12
+ - `bin/xlsx-query.cjs <file> [--summary | --agg <sheet> <col> <op> | --sample <sheet> [n]]` — spreadsheet compute.
13
+ - `bin/doc-gen.cjs <docx|xlsx|pdf> <out> <spec.json> [--theme <theme.json>] [--topic <subject> | --palette <id>] [--no-preview]` — generate documents from scratch. DOCX/PDF body fields are exactly `paragraphs` and/or `blocks` (never `sections`); in a PDF, any http(s) URL written in body text, list items or table cells becomes a clickable link and wraps inside the page on its own — never split a URL by hand (there is no other link syntax); unknown fields and empty bodies fail with `DOC_SPEC_INVALID` before any output is written (`allowCoverOnly:true` is the explicit title-only opt-in). Correct the reported JSON path and rerun. `--theme` applies a presentation theme (colors/fonts/cover/header/footer — see rewrite.md "Themes"; built-ins in `themes/`). **`--topic <subject>` (when no `--theme`) picks a topic-adaptive palette** instead of the same default every time. `doc-gen pptx` exits 64: decks are built with html2pptx. `--palette <id>` forces a specific palette (`doc-gen --list-palettes` prints them). **`--accent <#hex>` / `--style <skin>` (docx/pdf)**: white-paper-black-text base with ONE brand color on H1/H2 + a faint table-header tint — the practical doc shape (a document is read/printed/edited, not projected, so body text stays near-black). `--accent` takes a literal hex; `--style <skin>` pulls just the `--accent` from `styles/<skin>/skin.css` (the deck skin's full palette is NOT carried onto paper). Precedence: `--theme` > spec `"theme"` > `--accent`/`--style` > `--palette`/`--topic` > default.
14
+ - `bin/doc-convert.cjs <in.docx|pptx|xlsx> <out.pdf> [--cache-dir <dir>]` — faithful docx/pptx/xlsx → PDF conversion. Engine precedence (best fidelity first, each downgrade tagged on stdout `[pdf (…)]` + warned on stderr): (1) **LibreOffice** if a system `soffice` is found — true layout render (page geometry, headers/footers, text-box positions, native charts), offline; (2) **LibreOffice sidecar** if `TALOS_OFFICE_URL` is set — same true-layout fidelity via a remote converter (e.g. Gotenberg), needs network; (3) **Chromium** — reconstructed HTML, content/structure/images preserved but in-document layout is not (pptx = one page per slide): the app's service in-app, else a headless Chromium; with none of these it exits 69 — say the PDF could not be made. `--cache-dir` (or `TALOS_OFFICE_CACHE_DIR`) reuses a content-hashed local LibreOffice result for the exact same immutable artifact; caching is opt-in so document bytes are not retained by default. Surface the resulting `[pdf (engine)]` caveat to the user. NOTE: spreadsheets (xlsx/xls/ods) render ONLY via (1)–(2), a real LibreOffice; Chromium does not rebuild spreadsheets, so with no LibreOffice available the command errors (never a degraded text-only sheet).
15
+ - `bin/doc-validate.cjs <file.docx|pptx|xlsx|pdf> [--strict] [--design-lint]` — structural validation gate: OOXML zip/parts/rels/Content_Types/XML well-formedness; a PDF must parse and hold at least one page (reports `pages` and a `pdf-parse-only` warning — how it looks is render-and-look's job; `--strict` / `--design-lint` are OOXML-only; `--strict` on a PDF adds a `strict-not-applicable` warning). Run after every generation or edit; exit 1 = broken, fix before delivering. `--strict` adds full ECMA-376 schema (XSD) validation of every recognised part — REQUIRED after custom-code (doc-run/html2pptx) or raw-XML output; a strict failure means the file is non-standard even if Office opens it. `--design-lint` (pptx only) adds an ADVISORY design pass — flags `text-only-slide` (a wall of text with no visual anchor), `low-contrast-text`, `placeholder-text` (a layout pasted but never filled — 替换为…/占位/Lorem still in the slide), `font-mismatch` (CJK text set in a Latin-only font → 豆腐; expected language defaults to the system locale, override with `TALOS_DECK_LANG`), `off-canvas`, `tiny-font`, `shape-overlap`; findings are warnings (never change the exit code) but are real design bugs — fix them. See deck-design.md "Design principles".
16
+ - `bin/deck-qa.cjs <file.pptx> [--json] [--rendered <office.pdf> | --final [--cache-dir <dir>]]` — MECHANICAL design QA (deterministic, token-free): reads slide XML and flags `off-canvas` (content clipped past the edge), `tiny-font` (<10pt), `shape-overlap` (colliding text), plus `text-only-slide` / `low-contrast-text` / `placeholder-text` / `font-mismatch` (CJK text in a Latin-only font → 豆腐). Advisory (exit 0 even with findings). `--json` → `{ok,findings:[{slide,code,message}]}` for the visual-QA loop. It is the CHEAP GATE before spending vision tokens — run it every QA round and fix all findings first; then do the vision critic + per-slide regeneration (deck-design.md "Verify before delivering"). `html2pptx` already prints this lint as `qa` after every build. **`--final` is the deck's delivery sign-off in one call**: strict XSD validation (failure exits 1), the lint, an Office render through `doc-convert` (cached), `render-overflow` / `render-collision` where Office re-laid the native text past its box, and `review` + `previews` — the only slides (findings + native charts) to look at again, already rendered at ≈72dpi (deck-design.md "Verify before delivering"). `--rendered <pdf>` is the same render check against a PDF you converted yourself; a page count that differs from the deck exits 1.
17
+ - `bin/doc-xml.cjs unpack <file> <dir>` / `pack <dir> <out>` — raw-XML escape hatch (pretty-printed unpack → edit → repack); always doc-validate afterwards. `replace <in.docx> <out.docx> <find> <replacement>` — cross-run character-offset replace (finds text even when Word split it over runs, preserves formatting; covers body + headers/footers) — try this FIRST for plain text edits before unpacking. `merge-runs <in.docx> <out.docx>` repairs run fragmentation when you do need manual XML edits.
18
+ - `bin/pptx-clone.cjs <template.pptx> <slideNo> <out.pptx> [replacements.json]` — clone a styled template slide once per data item, replacing `{{tokens}}` in the clone only (tokens split across runs are still found); `pptx-clone remove <in> <slideNo> <out>` drops a slide (e.g. the leftover token template). CAPACITY warnings on stderr mean the text likely overflows its slot — shorten the wording, never shrink the font. See rewrite.md.
19
+ - `bin/pptx-slide.cjs add <in.pptx> <out.pptx> [--layout N]` / `dup <in> <slideNo> <out>` / `remove <in> <slideNo> <out>` / `reorder <in> <order> <out>` — slide-lifecycle surgery on an EXISTING deck WITHOUT a `{{token}}` template: append a blank slide bound to a layout (inherits the deck's master/theme), duplicate or drop a slide, or permute deck order (`pptx-slide reorder in.pptx 3,1,2 out.pptx` — comma-list of current 1-based positions in the new sequence). Lossless & structural — parts/rels/Content_Types are bookkept for you, but in-slide TEXT/content edits still go through doc-xml. Always doc-validate.
20
+ - `bin/pptx-embed-fonts.cjs <in.pptx> <out.pptx> --font "<Typeface>=<path.ttf>" [--bold …] [--italic …]` — EMBED fonts into a deck (OOXML font embedding) so it looks identical on a machine lacking the font. Full-font embed → use for SMALL custom/brand fonts; NOT big CJK fonts (openers have system CJK, and the file would balloon). `<Typeface>` must match the deck's text-run font name. doc-validate after.
21
+ - `bin/pptx-theme.cjs <ref.pptx> [out.json]` — extract a theme.json (palette/fonts) from an existing deck, then `html2pptx --theme` (or `doc-gen --theme` for a matching docx/pdf) it: new content in that deck's look. Palette only — reusing actual layouts is pptx-clone/doc-fill territory.
22
+ - `bin/doc-fill.cjs <template.docx|pptx|xlsx> <values.json> <out>` — fill `{{placeholders}}` in an existing template, preserving its styling (see rewrite.md).
23
+ - `bin/pptx-tokenize.cjs <user.pptx> <template.pptx> [schema.json]` — turn a user's UPLOADED deck into a reusable `{{token}}` template: keeps its exact layouts/master/brand, replaces each content slot's text with a clean single-run token, and emits a `schema.json` cataloging every token (slide/role/type/multiline/original/capacity). Plain text boxes (no `<p:ph>`) are tokenized too. Then `doc-fill` it — fill EVERY token (pass `original` for slots you keep, else they blank). The B2 "reuse their layout" path; see rewrite.md.
24
+ - `bin/pptx-set-image.cjs <in.pptx> <slideNo> <rId> <image> <out.pptx>` — swap the picture behind a slide's `rId` (from pptx-tokenize's image tokens) with a new image, keeping the frame (position/size) put. add-and-repoint (old media orphaned). Aspect-mismatch STRETCHES — pre-crop to the slot ratio. Always doc-validate.
25
+ - `bin/docx-annotate.cjs comment <in.docx> <out.docx> <comments.json>` / `suggest <in.docx> <out.docx> <author> <text>` / `accept <in.docx> <out.docx>` / `redline-check <original.docx> <edited.docx>` — add Word comments (paragraph-anchored, threaded replies via `replyTo`), a tracked insertion (reviewer suggestion), accept ALL tracked changes, or PROVE an edited file changed nothing outside tracked changes (run after every redline edit; non-zero exit = an untracked content change slipped in).
26
+ - `bin/pdf-form.cjs list <in.pdf>` / `fill <in.pdf> <values.json> <out.pdf> [--flatten]` / `overlay <filled.pdf> <out.pdf>` — inspect/fill PDF forms; `overlay` boxes every field in red for a render-and-look fill check (see pdf.md).
27
+ - `bin/pdf-ops.cjs merge <out> <in…>` / `split <in> <from-to> <out>` / `rotate <in> <deg> <out> [pages]` / `watermark <in> <text> <out>` / `stamp <in.pdf> <stamps.json> <out.pdf>` / `encrypt <in> <out> <user-pw> [owner-pw]` / `decrypt <in> <pw> <out>` — PDF page ops, coordinate text stamping onto a flat (non-form) PDF (see pdf.md), and AES-256 password protection.
28
+ - `bin/xlsx-recalc.cjs <in.xlsx> <out.xlsx>` — re-evaluate all formulas (HyperFormula) and cache the results, keeping the formulas. Use when a spreadsheet's formula cells show stale/blank values.
29
+ - `bin/doc-run.cjs <script.js> [args…]` — run a custom Node script with the engine's libraries bundled in (`require("pptxgenjs"|"docx"|"xlsx"|"exceljs"|"pdf-lib"|"jszip"|"pizzip"|"hyperformula")` guaranteed on any machine; `--list` prints the registry; nothing else is installed). For a **formatted workbook** (fills, font colors, borders, merged cells) use `exceljs` — `xlsx` (SheetJS community) writes no cell styles at all and drops them without an error; `doc-gen xlsx` covers only a bold header, column widths, number formats and formulas. The long-tail escape hatch when no verb fits — see rewrite.md "Custom scripts"; ALWAYS doc-validate the output.
30
+ - `bin/html2pptx.cjs <deck.html|snapshot.layout.json> [out.pptx|out.pdf] [--style <id>] [--theme-v2] [--media-style <id>] [--backdrop <variant>] [--editable] [--rasterize] [--preview-dir <dir>] [--preview-pages <all|N,N-M>]` — **THE deck path**: write slide HTML (`<section class="slide" data-layout="…">` on a 1280×720 canvas), Chromium lays it out → NATIVE editable pptx. An `out.pdf` prints the same styled deck instead: one 16:9 vector page per slide, text selectable, links clickable (HTML input only; not with `--editable`/`--rasterize`/`--notes-doc`). `--preview-dir` is a preview-only fast path that writes slide PNGs directly from the styled HTML and skips PPTX/LibreOffice; use all pages once, then `--preview-pages` for changed pages. `--theme-v2` composes the selected family's existing paint with its semantic component profile, default media style and bound art direction; `--media-style` overrides only the media treatment. Entries in a `classic-v1` catalog collection reject both flags; omit them for the frozen V1 path. `--backdrop <variant>` picks a signature backdrop. **DEFAULT = hybrid**: each slide's visuals bake into a full-fidelity raster backdrop with native EDITABLE text on top; auto-falls back to plain native if backdrop capture is unavailable. `--editable` = plain native only (effects flatten). `--rasterize` = full image slides (ZERO editability — tell the user). Advisory warnings on hardcoded brand colors + repeated layouts (P3). Runs in the app's Chromium OR bundled headless Chromium (Playwright) — works off-app (CI/cloud); layout JSON works anywhere. **Tailwind + `size="1".."7"` scale available** (vendored offline). See theme-system.md + deck-design.md + layouts/README.md. `[--theme <theme.json>]` is a separate legacy document recolor path.
31
+ - `bin/deck-build.cjs <spec.json> <deck.html>` — **THE FAST deck path** for catalog slides: emit a COMPACT spec with `style`, independent `layoutPack`, and per-slide `intent` + `layout` + content. Modern entries add `themeVersion:2` plus optional `mediaStyle`/`decorRhythm`; frozen `classic-v1` entries must omit those fields and use only pre-V2 layouts. Use `layouts/profiles.json` to match content intent before selecting geometry; under V2 the same intent derives the art scene and deck-level decoration rhythm. The CLI warns on an intent/layout mismatch and errors on an unknown intent. `scenario` (the confirmed scenario) decides whether the family's motion preset is applied — read decks (内审/报告/培训…) get none, `motion: true|false` overrides, and the result JSON reports `motion: {on, reason}` (deck-design.md "Motion"). `layoutPack:"none"` forces shared layouts; omitting `layoutPack` preserves the old behavior where a `kind:"pack"` style implicitly uses its same-name layouts. `slots`, `rows`, and `chart` are still validated against `layouts/slots.json`; any unknown layout/slot/field or missing figure input exits 1 and writes nothing. See theme-system.md for the full schema and compatibility contract.
32
+
33
+ All scripts are Node and print a machine-readable result plus a human note. On a
34
+ missing dependency or unreadable file they exit non-zero with a clear message —
35
+ report that to the user, do not fabricate content.
@@ -269,24 +269,23 @@ that matches its content structure (by P1/P2/P3, `layouts/README.md`), **copy th
269
269
  content** — leave color to the skin (never hardcode a brand hex). Render
270
270
  `bin/html2pptx.cjs deck.html out.pptx --style <skin>`. Where the catalog doesn't fit
271
271
  (bespoke hero covers, layered architecture, magazine) author bespoke HTML per
272
- **deck-design.md** (still using the skin's semantic classes). Fall back to
273
- `doc-gen … --theme` archetypes only when headless with no browser or a
274
- structured-spec deck is wanted.
272
+ **deck-design.md** (still using the skin's semantic classes). With no browser at
273
+ all, say so and stop — there is no spec-deck fallback.
275
274
 
276
275
  ### 4.1 Content structure → layout
277
276
 
278
277
  | Content structure | Core elements | → `layouts/*` (P1/P2/P3) |
279
278
  |---|---|---|
280
- | **Data / Trend** | 数据标题 + 指标 + 一句数据故事 | `chart-kpi` (`kpiCards`+`chart` / `timeline`) |
281
- | **Single impact** | 一个关键数字 + 上下文 | `big-stat` (`bigStat`) |
282
- | **Argument / Concept+visual** | 论点/定义 + 3–4 证据/组件 | `split-two-col` (`cards`/`iconList`+`quote`) |
283
- | **Cards (并列要点)** | 3 个并列要点 | `cards-3` (`cards` accentBar/numbered) |
284
- | **Comparison** | 框架 + 2–3 选项 × 维度 + 推荐 | bespoke (deck-design.md) / `comparison` |
285
- | **Process** | 目标 + 3–5 步骤 | bespoke / `steps` |
286
- | **Framework/Matrix** | 两轴 + 四象限 | bespoke / `quadrant` |
287
- | **Case Study** | 背景/挑战/方案/前后 | `split-two-col` + `big-stat` (`splitImage`+`kpiCards`) |
279
+ | **Data / Trend** | 数据标题 + 指标 + 一句数据故事 | `chart-kpi` |
280
+ | **Single impact** | 一个关键数字 + 上下文 | `big-stat` |
281
+ | **Argument / Concept+visual** | 论点/定义 + 3–4 证据/组件 | `split-two-col` |
282
+ | **Cards (并列要点)** | 3 个并列要点 | `cards-3` |
283
+ | **Comparison** | 框架 + 2–3 选项 × 维度 + 推荐 | `comparison` |
284
+ | **Process** | 目标 + 3–5 步骤 | `steps` / `steps-vertical` |
285
+ | **Framework/Matrix** | 两轴 + 四象限 | `matrix-2x2` |
286
+ | **Case Study** | 背景/挑战/方案/前后 | `split-two-col` + `big-stat` |
288
287
 
289
- Role pages: `cover` (`cool/cover`) · `toc` · `section` (`cool/section`) · `ending`.
288
+ Role pages: `cover` · `toc` · `section` · `ending` (and their variants in `layouts/README.md`). Anything no layout fits → a bespoke slide (deck-design.md).
290
289
 
291
290
  ### 4.2 Pick the exact template — priority order (P1 → P2 → P3)
292
291
 
@@ -296,7 +295,7 @@ this **priority order** — P1 outranks P2 outranks P3; never trade layout-fit f
296
295
  - **P1 — Layout match (most important): image presence + position.** No image → a
297
296
  text-only template (`cards-3` / a text-column split). Image present → `split-two-col`
298
297
  with the visual on the side it reads best (mirror the `.tpl` for image-left). Data →
299
- `chart-kpi` (native `data-chart`). Never force text into an image slot or an image
298
+ `chart-kpi` (chart choice — inline SVG vs native `data-chart` — per deck-design.md "Charts"). Never force text into an image slot or an image
300
299
  into a text slot; delete an unused image slot rather than leaving a placeholder.
301
300
  - **P2 — Density match: point count → variant.** 1–2 points / one hero number →
302
301
  spacious·centered (`big-stat`, hero cover); 3–4 points → balanced two-column
@@ -330,7 +329,7 @@ this **priority order** — P1 outranks P2 outranks P3; never trade layout-fit f
330
329
  - **Rich backgrounds** — never pure white/black; theme gradient + a decorative motif.
331
330
  **≥1–2 dark-accent slides** across the deck (Visual Rhythm).
332
331
  - **≥2 decorative/visual elements per content slide**; **charts > text for data**, one
333
- insight per chart (native `data-chart`, editable), highlight the key datapoint, no junk.
332
+ insight per chart (SVG or native `data-chart` per deck-design.md "Charts"), highlight the key datapoint, no junk.
334
333
  - **Emotional opening & closing** — cover and ending are STATEMENTS (feeling, not
335
334
  facts): strong visual + few powerful words.
336
335
  - **Visual diversity** — enforced by P3 (§4.2); a deck of one repeated layout reads as
@@ -346,10 +345,12 @@ Use a two-stage loop; do not pay the Office cold-start cost on every visual edit
346
345
  `bin/html2pptx.cjs deck.html --preview-dir <dir> [--preview-pages <spec>]` and inspect every page on
347
346
  the first pass. Fix HTML/CSS, then preview only changed pages. Repeat until the HTML
348
347
  composition is clean.
349
- 2. **Delivery loop (PPTX, once after HTML is clean):** generate the PPTX, run
350
- `deck-qa --json` and `doc-validate --strict`, fixing objective findings before
351
- visual review. Then perform one faithful `doc-convert → pdf-render all` pass to
352
- catch PPTX/Office-only drift. If that final pass finds drift, fix and rerun only as
348
+ 2. **Delivery loop (PPTX, once after HTML is clean):** build the PPTX and fix the
349
+ `qa` findings the build prints. Then sign off with one `deck-qa deck.pptx --final`:
350
+ strict validation, the Office render and its drift check, and `previews` — the
351
+ only slides to look at again; every other slide was already judged in the HTML
352
+ preview (deck-design.md "Verify before delivering", step 3, and its
353
+ read-everything exceptions). If that pass finds drift, fix and rerun only as
353
354
  required; never use PPTX→PDF as the routine authoring preview.
354
355
 
355
356
  Assume there are defects and inspect text overflow/clipping, collisions, tofu/blank