@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
@@ -0,0 +1,389 @@
1
+ #!/usr/bin/env python3
2
+ """Self-check a generated diagram HTML file, with no third-party deps.
3
+
4
+ Ships inside the skill so an installed agent can verify its own output:
5
+
6
+ python3 <skill-dir>/scripts/self_check.py my-diagram.html
7
+
8
+ Checks the accessible-SVG contract, the single-file safety rules (no remote
9
+ assets beyond the approved Google Fonts stylesheet, no executable attributes,
10
+ no scripts other than the one canonical motion controller), and — when motion
11
+ markup is present — the structural motion contract. This is a distilled
12
+ subset of the repository gates (`lint-skin.py`, `verify-motion.py`), which
13
+ remain the authority for contributions to the repository itself.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import argparse
19
+ import re
20
+ import sys
21
+ from collections import Counter
22
+ from html.parser import HTMLParser
23
+ from pathlib import Path
24
+ from urllib.parse import urlparse
25
+
26
+ SKILL_DIR = Path(__file__).resolve().parent.parent
27
+ MOTION_TEMPLATE = SKILL_DIR / "assets" / "template-motion.html"
28
+ MODES = {"none", "reveal", "step", "loop"}
29
+ ACTIONS = {"play", "pause", "replay", "prev", "next"}
30
+ ASCII_DECIMAL_RE = re.compile(r"^[0-9]+$")
31
+ REFERENCE_ATTRS = {"src", "href", "xlink:href", "poster", "srcset", "action", "formaction"}
32
+
33
+
34
+ class DiagramParser(HTMLParser):
35
+ def __init__(self) -> None:
36
+ super().__init__(convert_charrefs=True)
37
+ self.roots: list[dict[str, str]] = []
38
+ self.items: list[dict[str, str]] = []
39
+ self.actions: set[str] = set()
40
+ self.controls = 0
41
+ self.statuses: list[dict[str, str]] = []
42
+ self.statuses_in_controls = 0
43
+ self.scripts: list[dict[str, object]] = []
44
+ self.styles: list[str] = []
45
+ self.svgs: list[dict[str, object]] = []
46
+ self.unsafe: list[str] = []
47
+ self.references: list[tuple[str, str, str]] = []
48
+ self._svg_depth = 0
49
+ self._current_svg: dict[str, object] | None = None
50
+ self._capture: str | None = None
51
+ self._current_script: dict[str, object] | None = None
52
+ self._in_style = False
53
+ self._element_stack: list[str] = []
54
+ self._motion_root_depth: int | None = None
55
+ self._controls_depth: int | None = None
56
+
57
+ def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
58
+ tag = tag.casefold()
59
+ normalized_attrs = [(key.casefold(), value or "") for key, value in attrs]
60
+ data = {key: value for key, value in normalized_attrs}
61
+ if tag in {"base", "embed", "object", "iframe"}:
62
+ self.unsafe.append(f"<{tag}> is not allowed in a diagram file")
63
+ for key, value in normalized_attrs:
64
+ if key.startswith("on"):
65
+ self.unsafe.append(f"executable attribute {key} on <{tag}>")
66
+ if key == "srcdoc":
67
+ self.unsafe.append(f"srcdoc attribute on <{tag}>")
68
+ if key in REFERENCE_ATTRS:
69
+ self.references.append((tag, data.get("rel", ""), value))
70
+ if "data-motion-root" in data:
71
+ self.roots.append(data)
72
+ if self._motion_root_depth is None:
73
+ self._motion_root_depth = len(self._element_stack)
74
+ if self._motion_root_depth is not None:
75
+ if "data-motion-item" in data:
76
+ self.items.append(data)
77
+ if "data-motion-action" in data:
78
+ self.actions.add(data["data-motion-action"])
79
+ if "data-motion-controls" in data:
80
+ self.controls += 1
81
+ if self._controls_depth is None:
82
+ self._controls_depth = len(self._element_stack)
83
+ if "data-motion-status" in data:
84
+ self.statuses.append(data)
85
+ if self._controls_depth is not None:
86
+ self.statuses_in_controls += 1
87
+ if tag == "script":
88
+ self._current_script = {
89
+ "attrs": data,
90
+ "attr_names": [name for name, _value in normalized_attrs],
91
+ "body": [],
92
+ "closed": False,
93
+ }
94
+ self.scripts.append(self._current_script)
95
+ if tag == "style":
96
+ self._in_style = True
97
+ self._element_stack.append(tag)
98
+ if tag == "svg" and self._svg_depth == 0:
99
+ self._svg_depth = 1
100
+ self._current_svg = {"attrs": data, "first": None, "title": {}, "desc": {}}
101
+ self.svgs.append(self._current_svg)
102
+ return
103
+ if self._svg_depth:
104
+ self._svg_depth += 1
105
+ assert self._current_svg is not None
106
+ if self._svg_depth == 2 and self._current_svg["first"] is None:
107
+ self._current_svg["first"] = tag
108
+ if self._svg_depth == 2 and tag in {"title", "desc"}:
109
+ self._current_svg[tag] = {"attrs": data, "text": ""}
110
+ self._capture = tag
111
+
112
+ def handle_endtag(self, tag: str) -> None:
113
+ tag = tag.casefold()
114
+ if tag == "script" and self._current_script is not None:
115
+ self._current_script["closed"] = True
116
+ self._current_script = None
117
+ if tag == "style":
118
+ self._in_style = False
119
+ if self._svg_depth:
120
+ if tag in {"title", "desc"}:
121
+ self._capture = None
122
+ self._svg_depth -= 1
123
+ if self._svg_depth == 0:
124
+ self._current_svg = None
125
+ for index in range(len(self._element_stack) - 1, -1, -1):
126
+ if self._element_stack[index] == tag:
127
+ del self._element_stack[index:]
128
+ break
129
+ if (
130
+ self._motion_root_depth is not None
131
+ and len(self._element_stack) <= self._motion_root_depth
132
+ ):
133
+ self._motion_root_depth = None
134
+ if (
135
+ self._controls_depth is not None
136
+ and len(self._element_stack) <= self._controls_depth
137
+ ):
138
+ self._controls_depth = None
139
+
140
+ def handle_data(self, data: str) -> None:
141
+ if self._current_script is not None:
142
+ body = self._current_script["body"]
143
+ assert isinstance(body, list)
144
+ body.append(data)
145
+ if self._in_style:
146
+ self.styles.append(data)
147
+ if self._capture and self._current_svg:
148
+ node = self._current_svg[self._capture]
149
+ assert isinstance(node, dict)
150
+ node["text"] = str(node.get("text", "")) + data
151
+
152
+
153
+ def normalized_controller(body: str) -> str:
154
+ return body.replace("\r\n", "\n").replace("\r", "\n").strip()
155
+
156
+
157
+ def parsed_document(source: str) -> DiagramParser:
158
+ parser = DiagramParser()
159
+ parser.feed(source)
160
+ parser.close()
161
+ return parser
162
+
163
+
164
+ def is_approved_google_fonts_stylesheet(value: str) -> bool:
165
+ try:
166
+ parsed = urlparse(value)
167
+ except ValueError:
168
+ return False
169
+ return (
170
+ parsed.scheme == "https"
171
+ and parsed.hostname is not None
172
+ and parsed.hostname.casefold() == "fonts.googleapis.com"
173
+ and parsed.port is None
174
+ and parsed.path == "/css2"
175
+ and not parsed.fragment
176
+ )
177
+
178
+
179
+ def reference_error(tag: str, rel: str, value: str) -> str | None:
180
+ stripped = value.strip()
181
+ lowered = stripped.casefold()
182
+ if not stripped or stripped.startswith("#"):
183
+ return None
184
+ if lowered.startswith("javascript:") or lowered.startswith("data:text/html"):
185
+ return f"executable URL on <{tag}>: {stripped[:80]}"
186
+ remote = lowered.startswith(("http://", "https://", "//")) or (
187
+ ":" in stripped.split("/", 1)[0] and not lowered.startswith("data:")
188
+ )
189
+ if not remote:
190
+ if lowered.startswith("data:") and not lowered.startswith("data:image/"):
191
+ return f"non-image data URL on <{tag}>: {stripped[:80]}"
192
+ return None
193
+ if tag == "link" and "stylesheet" in rel.casefold().split():
194
+ if is_approved_google_fonts_stylesheet(stripped):
195
+ return None
196
+ return f"remote stylesheet is not the approved Google Fonts /css2 URL: {stripped[:80]}"
197
+ return f"remote reference on <{tag}>: {stripped[:80]}"
198
+
199
+
200
+ def canonical_controller() -> str:
201
+ if not MOTION_TEMPLATE.is_file():
202
+ raise RuntimeError(
203
+ f"cannot find the canonical controller at {MOTION_TEMPLATE}; "
204
+ "run self_check.py from its shipped location inside the skill"
205
+ )
206
+ parser = parsed_document(MOTION_TEMPLATE.read_text(encoding="utf-8"))
207
+ if len(parser.scripts) != 1 or not parser.scripts[0]["closed"]:
208
+ raise RuntimeError("template-motion.html must contain one closed controller")
209
+ body = parser.scripts[0]["body"]
210
+ assert isinstance(body, list)
211
+ return normalized_controller("".join(body))
212
+
213
+
214
+ def check_svgs(parser: DiagramParser, errors: list[str]) -> None:
215
+ checkable = [
216
+ svg
217
+ for svg in parser.svgs
218
+ if isinstance(svg["attrs"], dict)
219
+ and str(svg["attrs"].get("aria-hidden", "")).casefold() != "true"
220
+ ]
221
+ if not checkable:
222
+ errors.append("diagram file needs at least one accessible (non-aria-hidden) SVG")
223
+ for number, svg in enumerate(checkable, 1):
224
+ attrs = svg["attrs"]
225
+ assert isinstance(attrs, dict)
226
+ if attrs.get("role") != "img":
227
+ errors.append(f"svg {number} needs role=img")
228
+ labelled = attrs.get("aria-labelledby", "").split()
229
+ title = svg["title"]
230
+ desc = svg["desc"]
231
+ assert isinstance(title, dict) and isinstance(desc, dict)
232
+ title_attrs = title.get("attrs", {})
233
+ desc_attrs = desc.get("attrs", {})
234
+ assert isinstance(title_attrs, dict) and isinstance(desc_attrs, dict)
235
+ if svg["first"] != "title":
236
+ errors.append(f"svg {number} title must be its first child")
237
+ if not str(title.get("text", "")).strip() or not str(desc.get("text", "")).strip():
238
+ errors.append(f"svg {number} needs non-empty title and desc")
239
+ title_id = title_attrs.get("id", "")
240
+ desc_id = desc_attrs.get("id", "")
241
+ if title_id in {"", "title"} or desc_id in {"", "desc"}:
242
+ errors.append(f"svg {number} title/desc IDs must be diagram-prefixed, never bare")
243
+ if labelled != [title_id, desc_id]:
244
+ errors.append(f"svg {number} aria-labelledby must name title then desc")
245
+
246
+
247
+ def check_scripts(parser: DiagramParser, errors: list[str]) -> None:
248
+ if not parser.scripts:
249
+ return
250
+ if len(parser.scripts) > 1:
251
+ errors.append(f"at most one script is allowed; found {len(parser.scripts)}")
252
+ for number, script in enumerate(parser.scripts, 1):
253
+ attrs = script["attrs"]
254
+ attr_names = script["attr_names"]
255
+ body = script["body"]
256
+ assert isinstance(attrs, dict) and isinstance(attr_names, list) and isinstance(body, list)
257
+ if not script["closed"]:
258
+ errors.append(f"script {number} must have a closing script tag")
259
+ if attr_names != ["data-diagram-controls"] or attrs.get("data-diagram-controls") != "":
260
+ errors.append(f"script {number} must carry only the canonical data-diagram-controls attribute")
261
+ continue
262
+ try:
263
+ if normalized_controller("".join(body)) != canonical_controller():
264
+ errors.append(f"script {number} must exactly match the controller in template-motion.html")
265
+ except RuntimeError as exc:
266
+ errors.append(str(exc))
267
+
268
+
269
+ def check_motion(parser: DiagramParser, source: str, errors: list[str]) -> None:
270
+ has_motion_markup = bool(parser.roots or parser.items or parser.scripts)
271
+ if not has_motion_markup:
272
+ return
273
+ if len(parser.roots) != 1:
274
+ errors.append(f"expected exactly one data-motion-root; found {len(parser.roots)}")
275
+ return
276
+ root = parser.roots[0]
277
+ mode = root.get("data-motion-mode", "")
278
+ if mode not in MODES:
279
+ errors.append(f"data-motion-mode must be one of {sorted(MODES)}; got {mode!r}")
280
+ raw_count = root.get("data-step-count", "")
281
+ if not ASCII_DECIMAL_RE.fullmatch(raw_count):
282
+ count = -1
283
+ errors.append("data-step-count must be an ASCII decimal integer")
284
+ else:
285
+ count = int(raw_count)
286
+ minimum_count = 0 if mode == "none" else 1
287
+ if count < minimum_count or count > 8:
288
+ errors.append(f"semantic step count must be {minimum_count}..8; got {count}")
289
+
290
+ if len(parser.items) > 12:
291
+ errors.append(f"motion item budget is 12; found {len(parser.items)}")
292
+ semantic_steps: list[int] = []
293
+ for index, item in enumerate(parser.items, 1):
294
+ raw_step = item.get("data-step", "")
295
+ if not ASCII_DECIMAL_RE.fullmatch(raw_step):
296
+ errors.append(f"motion item {index} has a non-ASCII-decimal data-step")
297
+ continue
298
+ step = int(raw_step)
299
+ decorative = "data-motion-decorative" in item
300
+ if not decorative:
301
+ semantic_steps.append(step)
302
+ if not item.get("aria-label", "").strip():
303
+ errors.append(f"semantic motion item {index} needs a non-color aria-label")
304
+ elif item.get("aria-hidden") != "true" or item.get("focusable") != "false":
305
+ errors.append(f"decorative motion item {index} needs aria-hidden=true and focusable=false")
306
+ inline = item.get("style", "").replace(" ", "").lower()
307
+ if any(token in inline for token in ("display:none", "visibility:hidden", "opacity:0")):
308
+ errors.append(f"motion item {index} is hidden in source; the fallback must be visible")
309
+
310
+ expected = set(range(1, count + 1)) if count > 0 else set()
311
+ if set(semantic_steps) != expected:
312
+ errors.append(f"semantic steps must be contiguous 1..{count}; found {sorted(set(semantic_steps))}")
313
+ crowded = {step: n for step, n in Counter(semantic_steps).items() if n > 2}
314
+ if crowded:
315
+ errors.append(f"no more than two semantic items may share a step; found {crowded}")
316
+
317
+ if mode in {"none", "loop"} and parser.scripts:
318
+ errors.append(f"{mode} mode must be script-free")
319
+ if mode in {"none", "loop"} and (parser.controls or parser.actions or parser.statuses):
320
+ errors.append(f"{mode} mode must not expose playback controls or live status")
321
+ controlled = mode == "step" or (mode == "reveal" and bool(parser.scripts))
322
+ if controlled:
323
+ if parser.controls != 1:
324
+ errors.append(f"controlled mode needs one in-root control group; found {parser.controls}")
325
+ missing = ACTIONS - parser.actions
326
+ if missing:
327
+ errors.append(f"controlled mode is missing actions: {', '.join(sorted(missing))}")
328
+ if not parser.statuses:
329
+ errors.append("controlled mode needs data-motion-status")
330
+ else:
331
+ status = parser.statuses[0]
332
+ if (
333
+ status.get("role") != "status"
334
+ or status.get("aria-live") != "polite"
335
+ or status.get("aria-atomic") != "true"
336
+ ):
337
+ errors.append("motion status needs role=status, aria-live=polite, aria-atomic=true")
338
+ if parser.statuses_in_controls:
339
+ errors.append("motion status must sit outside data-motion-controls")
340
+ if not parser.scripts:
341
+ errors.append("controlled mode needs the scoped control script")
342
+
343
+ style_source = "".join(parser.styles)
344
+ if parser.scripts:
345
+ if re.search(r"prefers-reduced-motion\s*:\s*reduce", style_source, re.IGNORECASE) is None:
346
+ errors.append("missing reduced-motion CSS fallback (prefers-reduced-motion)")
347
+ if re.search(r"@media\s+print\b", style_source, re.IGNORECASE) is None:
348
+ errors.append("missing print CSS fallback (@media print)")
349
+ if "<noscript" not in source.casefold():
350
+ errors.append("motion file needs a <noscript> explanation of the complete static frame")
351
+
352
+
353
+ def verify(path: Path) -> list[str]:
354
+ source = path.read_text(encoding="utf-8")
355
+ parser = parsed_document(source)
356
+ errors: list[str] = []
357
+ errors.extend(parser.unsafe)
358
+ for tag, rel, value in parser.references:
359
+ finding = reference_error(tag, rel, value)
360
+ if finding:
361
+ errors.append(finding)
362
+ check_svgs(parser, errors)
363
+ check_scripts(parser, errors)
364
+ check_motion(parser, source, errors)
365
+ return errors
366
+
367
+
368
+ def main() -> int:
369
+ argument_parser = argparse.ArgumentParser(description=__doc__)
370
+ argument_parser.add_argument("files", nargs="+", type=Path)
371
+ args = argument_parser.parse_args()
372
+ failed = False
373
+ for path in args.files:
374
+ try:
375
+ errors = verify(path)
376
+ except (OSError, UnicodeError) as exc:
377
+ errors = [str(exc)]
378
+ if errors:
379
+ failed = True
380
+ print(f"FAIL {path}")
381
+ for error in errors:
382
+ print(f" - {error}")
383
+ else:
384
+ print(f"OK {path}")
385
+ return 1 if failed else 0
386
+
387
+
388
+ if __name__ == "__main__":
389
+ sys.exit(main())
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: document
3
- description: "Use this skill whenever a document attachment (PDF, .docx, .pptx, or .xlsx) is the input and the user wants it read, summarized, translated, searched, or its tables/formulas/figures interpreted — OR whenever the user asks to CREATE/GENERATE a document FILE (PDF, Word, PowerPoint, Excel) from scratch, edit an existing one, or CONVERT a document to another format (转成/转换/导出 PDF, .docx/.pptx → PDF, convert/export to PDF). For generating any document file ALWAYS use this skill's doc-gen script, and for docx/pptx→PDF conversion ALWAYS use its doc-convert script — never hand-write a python / ReportLab / LibreOffice / textutil / headless-browser pipeline for document output. Talos routes most inbound documents to local text extraction by default; reach for this skill's scripts for page rendering (formulas/scanned/figures), spreadsheet computation, document generation, or format conversion. Do NOT trigger for images (handled natively) or plain .txt/.md/.csv already inlined as text."
3
+ description: "Use for document FILES. Reading: a PDF, .docx, .pptx or .xlsx attachment to read, summarize, translate or search when its pages must be rendered (formulas, scans, figures) or a sheet computed — others arrive as extracted text. Writing: any request to create or edit a Word, PowerPoint, Excel or PDF file, or convert one (转成/导出 PDF, .docx/.pptx → PDF). ALWAYS use its scripts — doc-gen (docs), html2pptx (decks, and a deck made here to PDF), doc-convert (an existing file to PDF); never hand-write a python/ReportLab/LibreOffice/browser pipeline. Do NOT trigger for images or plain .txt/.md/.csv."
4
4
  license: Proprietary. LICENSE.txt has complete terms
5
5
  ---
6
6
 
@@ -26,6 +26,14 @@ script only when:
26
26
  exact (don't do the arithmetic yourself).
27
27
  - **Formula / figure / scanned / exact layout matters** AND you can see images
28
28
  → read **vision.md**; `bin/pdf-render` a page, then look.
29
+ - **A deck you built here, as PDF**(PPT 要 PDF / 导出 PDF / deck as PDF)→ print
30
+ its `deck.html` with the same STYLE flags you rendered the .pptx with (leave out
31
+ `--editable` / `--rasterize` / `--notes-doc`, which are .pptx-only):
32
+ `bin/html2pptx.cjs deck.html out.pdf --style <id> [--theme-v2 …] [--backdrop …]`.
33
+ One 16:9 page per slide, vector text (selectable, searchable), links clickable.
34
+ Do not route it through `doc-convert` (that re-lays out the .pptx) and never
35
+ assemble slide PNGs into a PDF (image pages: no text, no links). If the result
36
+ reports `pages` ≠ `slides`, a slide overflows its canvas — fix it and print again.
29
37
  - **Convert** a .docx/.pptx/.xlsx to PDF(转成/转换/导出 PDF / convert to PDF)→
30
38
  `bin/doc-convert.cjs <in.docx|pptx|xlsx> <out.pdf>`. This is FAITHFUL conversion —
31
39
  the source's content, structure and images with neutral print styling; do not
@@ -39,6 +47,10 @@ script only when:
39
47
  template) → read **rewrite.md**, use `doc-gen` / `doc-fill`. **Always** produce
40
48
  document files via these scripts — never hand-write a python / ReportLab /
41
49
  LibreOffice script for document output.
50
+ **A length limit** (正文 1000 字以内 / under 800 words) is kept in the spec,
51
+ before generating: after a PDF, `doc-gen` prints `textChars` (spaces and URLs
52
+ not counted) — over the limit, shorten the spec and generate once more. Do not
53
+ extract text from the output to count it.
42
54
  **Any deck/presentation(路演/汇报/评审/宣讲/pitch/roadshow/介绍 PPT)**:
43
55
  **CLARIFY FIRST — HARD GATE (applies to EVERY deck).** Before authoring, collect
44
56
  three brief fields: **(1) content source** (提供材料 / 联网调研 / 自由创作);
@@ -117,53 +129,28 @@ script only when:
117
129
  Then render a cover+content sample of the result and confirm (same image-card step as
118
130
  a normal pick) before authoring; save the resulting preference to memory.
119
131
 
120
- **THE deck method — html2pptx HYBRID with catalog-governed V1/V2 composition.**
121
- Modern entries compose **Theme + component profile + media style + art direction +
122
- content-selected layouts**; frozen `classic-v1` entries keep family paint only:
123
- 1. **Read deck-design.md** (⭐ PREFERRED workflow) + **`layouts/README.md`**
124
- (every layout tagged role · structure · density · has-image; that index is
125
- the source of truth for which layouts exist).
126
- 2. **Plan pages, pick layouts** from `layouts/profiles.json` by intent, then by
127
- **P1/P2/P3** (structure → density → **≤2 dedup**;
128
- `html2pptx` warns on repeats). Paste each `layouts/<id>.tpl` into `deck.html`
129
- (they carry `data-layout`), fill REAL content — **never hardcode a brand hex**
130
- (leave color to the skin; `html2pptx` warns on hardcoded colors). 8 roles:
131
- cover/title/toc/section/transition/content/ending + special charts
132
- (gantt/radar/funnel/ring/org-tree/map/data-table/stats-chart).
133
- **A layout with a `{{#rows}}` block (gantt · radar · ring-progress ·
134
- map-distribution · team-row · brand-values · color-palette · steps-vertical ·
135
- toc-panel) must go through `deck-build`** — it repeats the block per row AND
136
- computes the geometry (bar spans, ring arcs, pin positions, the radar chart)
137
- from your numbers. Hand-pasting one leaves `{{#…}}` markers in the deck, or a
138
- drawing that contradicts its own labels.
139
- For Theme V2, keep each slide's semantic `intent`: `deck-build` uses it to derive
140
- the `hero/content/data/media/closing` art scene and balance low/medium/high decoration
141
- across the deck. Do not hand-assign every scene; override `scene` or `decorDensity`
142
- only for a deliberate exception. `decorRhythm:"steady"` disables auto balancing.
143
- For `classic-v1`, choose only pre-V2 layouts and omit scene/decor controls.
144
- 3. **Render:** modern entry → `bin/html2pptx.cjs deck.html out.pptx --style <id>
145
- --theme-v2 [--media-style <id>] [--backdrop <variant>]`; frozen classic entry →
146
- `bin/html2pptx.cjs deck.html out.pptx --style <id> [--backdrop <variant>]`.
147
- DEFAULT hybrid (no `--editable`/`--rasterize`): pixel-perfect
148
- visuals baked into a backdrop + native EDITABLE text and native `data-chart`
149
- on top. Theme V2 injects the compatible V1 family paint plus semantic component,
150
- media and art-direction layers; it never chooses a layout. Chromium gives the full CSS toolbox (Tailwind
151
- + a `size="1".."7"` scale, gradients, shadows, per-element SVG, `<img>`
152
- auto-inline, speaker notes) → NATIVE editable pptx.
153
- **For a substantive deck, first read content-pipeline.md** (premium-deck method:
154
- clarify + persist brief → research → narrative structure → one outline gate → author from
155
- `layouts/` → fast HTML preview → final PPTX QA; premium density = rich VISUAL + sparse TEXT, details to
156
- speaker notes). **Do NOT build a deck with `doc-gen pptx` archetypes or `doc-run`
157
- PptxGenJS — those deck paths are OFF.** (html2pptx needs a browser: the app's
158
- Chromium service or bundled Playwright; if neither, say so and stop — never
159
- silently fall back to a lesser deck.) End with the mandatory hostile-review loop
160
- (deck-design.md). *(The legacy `themes/*.json` + `--theme` recolor path is for
161
- doc-gen DOCUMENTS; for decks use `--style` + `layouts/`.)*
162
- - **After ANY generated/edited file** (doc-gen / doc-fill / docx-annotate /
163
- pptx-clone / doc-xml pack / hand-written scripts): run
164
- `bin/doc-validate.cjs <file>` — exit 1 means fix it before delivering. Then
165
- **render-and-look** at the output before delivery (see "Verify generated
166
- output" below) — `doc-validate` proves well-formed, not good-looking.
132
+ **Then build it — the method lives in deck-design.md.** For a substantive deck read
133
+ **content-pipeline.md** first (brief → research → narrative → one outline gate), then
134
+ **deck-design.md** (workflow and delivery sign-off) and **`layouts/README.md`** (the
135
+ layout index and fill rules). In short: pick layouts from `layouts/profiles.json` by
136
+ slide intent; fill them with `deck-build` (required for any layout with a `{{#rows}}`
137
+ block — it computes the geometry; put the confirmed scenario in its spec as
138
+ `"scenario"` — read decks such as 内审/报告/培训 then get no animation); render with `bin/html2pptx.cjs deck.html out.pptx
139
+ --style <id>` (+ `--theme-v2` for modern entries, never for `classic-v1`) — DEFAULT
140
+ hybrid: baked visuals, native editable text. `doc-gen` makes no decks and the doc-run
141
+ PptxGenJS deck path is OFF. html2pptx needs a browser (the app's Chromium or bundled
142
+ Playwright); without one, say so and stop — never fall back to a lesser deck.
143
+ - **Generators check their own output.** `doc-gen` and `html2pptx` report
144
+ validity in the step that wrote the file — `valid: false` exits 1, fix before
145
+ delivering; never re-run `doc-validate` on their output. `doc-gen pdf` prints
146
+ `{valid, pages, textChars, cjkChars, previews}` (a ≈72dpi PNG per page — read
147
+ those, do not re-render or count characters yourself); `doc-gen docx` prints
148
+ `{valid, textChars, cjkChars}`, `xlsx` `{valid}`; `html2pptx` adds `valid` and
149
+ `qa` (deck-qa's findings) to its result. **After any other edit** (doc-fill /
150
+ docx-annotate / pptx-clone / pptx-slide / doc-xml pack / hand-written scripts)
151
+ run `bin/doc-validate.cjs <file>` yourself — exit 1 means fix it. Valid is not
152
+ good-looking: **render-and-look** before delivery (see "Verify generated
153
+ output" below; decks sign off with `deck-qa --final`).
167
154
  - **Raw XML edit** (when no targeted tool fits): `doc-xml unpack` → edit →
168
155
  `doc-xml pack` → `doc-validate` (see rewrite.md "Raw XML editing").
169
156
  - **PDF** whose inlined text is garbled/missing where the answer should be →
@@ -204,39 +191,40 @@ Fidelity-decision self-correct, but pointed at your OWN output:
204
191
  blank CJK** (a missing-font symptom, not a content bug); colliding
205
192
  headers/footers; leftover `{{tokens}}` / "lorem" / "undefined".
206
193
  3. **Fix → re-render the affected pages.** One fix often creates another; repeat
207
- until a clean pass. Scale rigor to the artifact: a one-page memo = one look; a
194
+ until a clean pass. Fix what is broken — clipped, overlapping, unreadable, tofu —
195
+ not how full a page looks: unless the user asked for a page count, never
196
+ resize fonts, line height or margins to fill a short last page or squeeze onto
197
+ fewer pages. Page breaks already keep a heading with its text and never strand
198
+ a single line, so a heading near a page edge is not something to fix. Layout and styling taste gets at most
199
+ TWO fix rounds: after the second, deliver and name what is still imperfect instead
200
+ of regenerating again. Scale rigor to the artifact: a one-page memo = one look; a
208
201
  multi-page report/deck = every page, ideally a sub-agent for fresh eyes.
209
202
  4. **Vision unavailable**: say so and rely on `doc-validate` + a text re-extract
210
203
  (`doc-text`) as a weaker proxy — never claim the layout is verified.
211
204
 
212
205
  ## Scripts (run via bash; output returns to context, the script itself does not)
213
206
 
214
- - `bin/doc-scan.cjs <file.pdf>` — per-page signals as JSON.
215
- - `bin/doc-text.cjs <file.pdf>` — extract a PDF's text layer.
216
- - `bin/pdf-render.cjs <file.pdf> <pages|all> [scale]` — render pages to PNG for vision (`pages` = `3` | `2-5` | `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.
217
- - `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`.
218
- - `bin/pdf-tables.cjs <file.pdf> [page] [minCols]` — recover text-layer tables as rows (ruled/scanned tables need render + vision instead).
219
- - `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.
220
- - `bin/xlsx-query.cjs <file> [--summary | --agg <sheet> <col> <op> | --sample <sheet> [n]]` — spreadsheet compute.
221
- - `bin/doc-gen.cjs <docx|xlsx|pptx|pdf> <out> <spec.json> [--theme <theme.json>] [--topic <subject> | --palette <id>] [--design native|html]` — generate documents from scratch. DOCX/PDF body fields are exactly `paragraphs` and/or `blocks` (never `sections`); 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/pptx master — see rewrite.md "Themes"; built-ins in `themes/`). **`--topic <subject>` (pptx, when no `--theme`) picks a topic-adaptive palette** so the deck is designed-for-subject instead of template-flat, and turns on richness (motif + gradient wash + depth). `--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. `--design html` (pptx only, in-app) renders eligible slides through designed HTML templates for a magazine look — richer but a flat collage (no master structure); auto-falls back to native when headless or a slide has no template. Default `native` (structured, editable, charts, headless-safe).
222
- - `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** (in-app) — reconstructed HTML, content/structure/images preserved but in-document layout is not (pptx = one page per slide); (4) **pdf-lib text-only** headless last resort — images/tables flatten. `--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 tiers 1–2 (real LibreOffice); tiers 3–4 do not rebuild spreadsheets, so with no LibreOffice available the command errors (never a degraded text-only sheet).
223
- - `bin/doc-validate.cjs <file.docx|pptx|xlsx> [--strict] [--design-lint]` — structural OOXML validation gate (zip/parts/rels/Content_Types/XML well-formedness); 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".
224
- - `bin/deck-qa.cjs <file.pptx> [--json]` — 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").
225
- - `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.
226
- - `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.
227
- - `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.
228
- - `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.
229
- - `bin/pptx-theme.cjs <ref.pptx> [out.json]` — extract a theme.json (palette/fonts/sizes) from an existing deck, then `doc-gen --theme` it: new content in that deck's look. Palette only — reusing actual layouts is pptx-clone/doc-fill territory.
230
- - `bin/doc-fill.cjs <template.docx|pptx|xlsx> <values.json> <out>` — fill `{{placeholders}}` in an existing template, preserving its styling (see rewrite.md).
231
- - `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.
232
- - `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.
233
- - `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).
234
- - `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).
235
- - `bin/pdf-ops.cjs merge <out> <in…>` / `split <in> <from-to> <out>` / `rotate <in> <deg> <out> [pages]` / `watermark <in> <text> <out>` / `encrypt <in> <out> <user-pw> [owner-pw]` / `decrypt <in> <pw> <out>` — PDF page ops + AES-256 password protection.
236
- - `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.
237
- - `bin/doc-run.cjs <script.js> [args…]` — run a custom Node script with the engine's libraries bundled in (`require("pptxgenjs"|"docx"|"xlsx"|"pdf-lib"|"jszip"|"pizzip"|"hyperformula")` guaranteed on any machine; `--list` prints the registry). The long-tail escape hatch when no verb fits — see rewrite.md "Custom scripts"; ALWAYS doc-validate the output.
238
- - `bin/html2pptx.cjs <deck.html|snapshot.layout.json> [out.pptx] [--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. `--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.
239
- - `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. `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.
207
+ One line each — **read a bin's entry in `bin-reference.md` before its first use**
208
+ (flags, output shape, exit codes, gotchas). Do not read, grep or open `bin/*.cjs`
209
+ themselves — they are bundled builds; the answer is in `bin-reference.md`.
210
+
211
+ - Read: `bin/doc-scan.cjs <file.pdf>` page signals · `bin/doc-text.cjs <file.pdf>` text layer ·
212
+ `bin/pdf-render.cjs <file.pdf> <N|N-M|3,7-8|all> [scale]` pages → PNG (scale `1` ≈72dpi for layout) ·
213
+ `bin/pdf-images.cjs` embedded images · `bin/pdf-tables.cjs` text-layer tables ·
214
+ `bin/ocr.cjs <image> [lang]` · `bin/xlsx-query.cjs <file> --summary|--agg|--sample` exact spreadsheet compute.
215
+ - Generate: `bin/doc-gen.cjs <docx|xlsx|pdf> <out> <spec.json>` (self-checks) ·
216
+ `bin/doc-run.cjs <script.js>` custom script with the bundled libraries ·
217
+ `bin/doc-convert.cjs <in.docx|pptx|xlsx> <out.pdf>` faithful → PDF.
218
+ - Decks: `bin/deck-build.cjs <spec.json> <deck.html>` catalog slides from a compact spec ·
219
+ `bin/html2pptx.cjs <deck.html> <out.pptx> --style <id> …` THE deck path (self-checks; `--preview-dir` for fast previews) ·
220
+ `bin/deck-qa.cjs <deck.pptx> [--json | --final]` lint, and the one-call delivery sign-off.
221
+ - Edit: `bin/doc-fill.cjs` fill `{{tokens}}` · `bin/doc-xml.cjs unpack|pack|replace|merge-runs` raw XML ·
222
+ `bin/pptx-slide.cjs add|dup|remove|reorder` · `bin/pptx-clone.cjs` clone a template slide per item ·
223
+ `bin/pptx-tokenize.cjs` uploaded deck → template · `bin/pptx-set-image.cjs` · `bin/pptx-theme.cjs` palette from a deck ·
224
+ `bin/pptx-embed-fonts.cjs` · `bin/docx-annotate.cjs comment|suggest|accept|redline-check` ·
225
+ `bin/xlsx-recalc.cjs` recompute formulas.
226
+ - Validate: `bin/doc-validate.cjs <file.docx|pptx|xlsx|pdf> [--strict]` — for anything a generator did not already check.
227
+ - PDF: `bin/pdf-form.cjs list|fill|overlay` · `bin/pdf-ops.cjs merge|split|rotate|watermark|stamp|encrypt|decrypt`.
240
228
 
241
229
  All scripts are Node and print a machine-readable result plus a human note. On a
242
230
  missing dependency or unreadable file they exit non-zero with a clear message —