@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.
- package/mcp/gen/mcp.json +2 -2
- package/package.json +3 -3
- package/skills/cozy-static-svg/SKILL.md +76 -0
- package/skills/cozy-static-svg/references/approved-preview.png +0 -0
- package/skills/cozy-static-svg/references/cross-platform.md +73 -0
- package/skills/cozy-static-svg/references/moon_keeps_watch.svg +229 -0
- package/skills/cozy-static-svg/references/style-anchor.md +271 -0
- package/skills/cozy-static-svg/scripts/artkit.py +321 -0
- package/skills/cozy-static-svg/scripts/to_png.py +333 -0
- package/skills/diagram-design/SKILL.md +568 -0
- package/skills/diagram-design/assets/example-architecture-dark.html +182 -0
- package/skills/diagram-design/assets/example-architecture-full.html +194 -0
- package/skills/diagram-design/assets/example-architecture.html +183 -0
- package/skills/diagram-design/assets/example-bar-dark.html +129 -0
- package/skills/diagram-design/assets/example-bar-full.html +105 -0
- package/skills/diagram-design/assets/example-bar.html +129 -0
- package/skills/diagram-design/assets/example-data-flow-dark.html +143 -0
- package/skills/diagram-design/assets/example-data-flow-full.html +185 -0
- package/skills/diagram-design/assets/example-data-flow.html +143 -0
- package/skills/diagram-design/assets/example-datalake-dark.html +243 -0
- package/skills/diagram-design/assets/example-datalake-full.html +248 -0
- package/skills/diagram-design/assets/example-datalake.html +269 -0
- package/skills/diagram-design/assets/example-dp-integration-dark.html +80 -0
- package/skills/diagram-design/assets/example-dp-integration-full.html +90 -0
- package/skills/diagram-design/assets/example-dp-integration.html +80 -0
- package/skills/diagram-design/assets/example-dp-security-matrix-dark.html +31 -0
- package/skills/diagram-design/assets/example-dp-security-matrix-full.html +32 -0
- package/skills/diagram-design/assets/example-dp-security-matrix.html +88 -0
- package/skills/diagram-design/assets/example-er-dark.html +202 -0
- package/skills/diagram-design/assets/example-er-full.html +205 -0
- package/skills/diagram-design/assets/example-er.html +202 -0
- package/skills/diagram-design/assets/example-flowchart-dark.html +156 -0
- package/skills/diagram-design/assets/example-flowchart-full.html +159 -0
- package/skills/diagram-design/assets/example-flowchart.html +156 -0
- package/skills/diagram-design/assets/example-gantt-dark.html +137 -0
- package/skills/diagram-design/assets/example-gantt-full.html +127 -0
- package/skills/diagram-design/assets/example-gantt.html +137 -0
- package/skills/diagram-design/assets/example-high-level-dark.html +251 -0
- package/skills/diagram-design/assets/example-high-level-full.html +247 -0
- package/skills/diagram-design/assets/example-high-level-vertical-dark.html +274 -0
- package/skills/diagram-design/assets/example-high-level-vertical-full.html +252 -0
- package/skills/diagram-design/assets/example-high-level-vertical.html +325 -0
- package/skills/diagram-design/assets/example-high-level.html +301 -0
- package/skills/diagram-design/assets/example-import-drawio.html +185 -0
- package/skills/diagram-design/assets/example-import-mermaid.html +70 -0
- package/skills/diagram-design/assets/example-it-state-dark.html +90 -0
- package/skills/diagram-design/assets/example-it-state-full.html +69 -0
- package/skills/diagram-design/assets/example-it-state.html +128 -0
- package/skills/diagram-design/assets/example-layers-dark.html +123 -0
- package/skills/diagram-design/assets/example-layers-full.html +126 -0
- package/skills/diagram-design/assets/example-layers.html +123 -0
- package/skills/diagram-design/assets/example-line-dark.html +111 -0
- package/skills/diagram-design/assets/example-line-full.html +105 -0
- package/skills/diagram-design/assets/example-line.html +111 -0
- package/skills/diagram-design/assets/example-loop-dark.html +100 -0
- package/skills/diagram-design/assets/example-loop-full.html +137 -0
- package/skills/diagram-design/assets/example-loop-terminal.html +347 -0
- package/skills/diagram-design/assets/example-loop.html +100 -0
- package/skills/diagram-design/assets/example-medallion-dark.html +159 -0
- package/skills/diagram-design/assets/example-medallion-full.html +201 -0
- package/skills/diagram-design/assets/example-medallion.html +159 -0
- package/skills/diagram-design/assets/example-nested-dark.html +132 -0
- package/skills/diagram-design/assets/example-nested-full.html +135 -0
- package/skills/diagram-design/assets/example-nested.html +136 -0
- package/skills/diagram-design/assets/example-org-chart-dark.html +45 -0
- package/skills/diagram-design/assets/example-org-chart-full.html +45 -0
- package/skills/diagram-design/assets/example-org-chart.html +45 -0
- package/skills/diagram-design/assets/example-paved-road-animated.html +546 -0
- package/skills/diagram-design/assets/example-policy-trace-animated.html +490 -0
- package/skills/diagram-design/assets/example-process-dark.html +64 -0
- package/skills/diagram-design/assets/example-process-full.html +74 -0
- package/skills/diagram-design/assets/example-process.html +64 -0
- package/skills/diagram-design/assets/example-pyramid-dark.html +119 -0
- package/skills/diagram-design/assets/example-pyramid-full.html +122 -0
- package/skills/diagram-design/assets/example-pyramid.html +119 -0
- package/skills/diagram-design/assets/example-quadrant-consultant.html +166 -0
- package/skills/diagram-design/assets/example-quadrant-dark.html +135 -0
- package/skills/diagram-design/assets/example-quadrant-full.html +138 -0
- package/skills/diagram-design/assets/example-quadrant.html +135 -0
- package/skills/diagram-design/assets/example-queue-animated.html +577 -0
- package/skills/diagram-design/assets/example-radar-dark.html +139 -0
- package/skills/diagram-design/assets/example-radar-full.html +135 -0
- package/skills/diagram-design/assets/example-radar.html +139 -0
- package/skills/diagram-design/assets/example-scatter-dark.html +141 -0
- package/skills/diagram-design/assets/example-scatter-full.html +116 -0
- package/skills/diagram-design/assets/example-scatter.html +141 -0
- package/skills/diagram-design/assets/example-sequence-dark.html +222 -0
- package/skills/diagram-design/assets/example-sequence-full.html +388 -0
- package/skills/diagram-design/assets/example-sequence-oauth-dark.html +188 -0
- package/skills/diagram-design/assets/example-sequence-oauth-full.html +350 -0
- package/skills/diagram-design/assets/example-sequence-oauth.html +227 -0
- package/skills/diagram-design/assets/example-sequence.html +222 -0
- package/skills/diagram-design/assets/example-state-dark.html +147 -0
- package/skills/diagram-design/assets/example-state-full.html +150 -0
- package/skills/diagram-design/assets/example-state.html +147 -0
- package/skills/diagram-design/assets/example-swimlane-dark.html +172 -0
- package/skills/diagram-design/assets/example-swimlane-full.html +175 -0
- package/skills/diagram-design/assets/example-swimlane.html +172 -0
- package/skills/diagram-design/assets/example-timeline-dark.html +135 -0
- package/skills/diagram-design/assets/example-timeline-full.html +138 -0
- package/skills/diagram-design/assets/example-timeline.html +135 -0
- package/skills/diagram-design/assets/example-tree-dark.html +173 -0
- package/skills/diagram-design/assets/example-tree-full.html +176 -0
- package/skills/diagram-design/assets/example-tree.html +173 -0
- package/skills/diagram-design/assets/example-venn-dark.html +132 -0
- package/skills/diagram-design/assets/example-venn-full.html +135 -0
- package/skills/diagram-design/assets/example-venn.html +110 -0
- package/skills/diagram-design/assets/icons.html +230 -0
- package/skills/diagram-design/assets/index.html +369 -0
- package/skills/diagram-design/assets/template-dark.html +85 -0
- package/skills/diagram-design/assets/template-full.html +413 -0
- package/skills/diagram-design/assets/template-motion.html +434 -0
- package/skills/diagram-design/assets/template-terminal.html +173 -0
- package/skills/diagram-design/assets/template.html +86 -0
- package/skills/diagram-design/references/animation.md +141 -0
- package/skills/diagram-design/references/export.md +136 -0
- package/skills/diagram-design/references/import-drawio.md +171 -0
- package/skills/diagram-design/references/import-mermaid.md +126 -0
- package/skills/diagram-design/references/onboarding.md +297 -0
- package/skills/diagram-design/references/output-spec.md +181 -0
- package/skills/diagram-design/references/primitive-annotation.md +36 -0
- package/skills/diagram-design/references/primitive-icons.md +827 -0
- package/skills/diagram-design/references/primitive-sketchy.md +43 -0
- package/skills/diagram-design/references/primitive-terminal.md +76 -0
- package/skills/diagram-design/references/semantic-patterns.md +122 -0
- package/skills/diagram-design/references/style-guide.md +139 -0
- package/skills/diagram-design/references/type-architecture.md +78 -0
- package/skills/diagram-design/references/type-bar.md +48 -0
- package/skills/diagram-design/references/type-data-flow.md +374 -0
- package/skills/diagram-design/references/type-dp-integration.md +410 -0
- package/skills/diagram-design/references/type-dp-security-matrix.md +379 -0
- package/skills/diagram-design/references/type-er.md +23 -0
- package/skills/diagram-design/references/type-flowchart.md +23 -0
- package/skills/diagram-design/references/type-gantt.md +45 -0
- package/skills/diagram-design/references/type-high-level.md +458 -0
- package/skills/diagram-design/references/type-it-state.md +470 -0
- package/skills/diagram-design/references/type-layers.md +26 -0
- package/skills/diagram-design/references/type-line.md +44 -0
- package/skills/diagram-design/references/type-loop.md +223 -0
- package/skills/diagram-design/references/type-medallion.md +356 -0
- package/skills/diagram-design/references/type-nested.md +22 -0
- package/skills/diagram-design/references/type-org-chart.md +44 -0
- package/skills/diagram-design/references/type-process.md +495 -0
- package/skills/diagram-design/references/type-pyramid.md +33 -0
- package/skills/diagram-design/references/type-quadrant.md +81 -0
- package/skills/diagram-design/references/type-radar.md +80 -0
- package/skills/diagram-design/references/type-scatter.md +39 -0
- package/skills/diagram-design/references/type-sequence.md +130 -0
- package/skills/diagram-design/references/type-state.md +21 -0
- package/skills/diagram-design/references/type-swimlane.md +20 -0
- package/skills/diagram-design/references/type-timeline.md +20 -0
- package/skills/diagram-design/references/type-tree.md +24 -0
- package/skills/diagram-design/references/type-venn.md +26 -0
- package/skills/diagram-design/scripts/drawio_extract.py +856 -0
- package/skills/diagram-design/scripts/mermaid_extract.py +1285 -0
- package/skills/diagram-design/scripts/self_check.py +389 -0
- package/skills/document/SKILL.md +63 -75
- package/skills/document/bin/deck-build.cjs +51 -1
- package/skills/document/bin/deck-qa.cjs +18745 -279
- package/skills/document/bin/doc-convert.cjs +9280 -76489
- package/skills/document/bin/doc-fill.cjs +0 -0
- package/skills/document/bin/doc-gen.cjs +3043 -46825
- package/skills/document/bin/doc-run.cjs +65095 -1967
- package/skills/document/bin/doc-scan.cjs +0 -0
- package/skills/document/bin/doc-text.cjs +0 -0
- package/skills/document/bin/doc-validate.cjs +18067 -226
- package/skills/document/bin/doc-xml.cjs +1 -1
- package/skills/document/bin/docx-annotate.cjs +0 -0
- package/skills/document/bin/html2pptx.cjs +32498 -145280
- package/skills/document/bin/ocr-worker.cjs +0 -0
- package/skills/document/bin/ocr.cjs +0 -0
- package/skills/document/bin/pdf-form.cjs +0 -0
- package/skills/document/bin/pdf-images.cjs +0 -0
- package/skills/document/bin/pdf-ops.cjs +0 -0
- package/skills/document/bin/pdf-render.cjs +11 -9
- package/skills/document/bin/pdf-tables.cjs +0 -0
- package/skills/document/bin/pptx-clone.cjs +21 -25
- package/skills/document/bin/pptx-embed-fonts.cjs +8 -6
- package/skills/document/bin/pptx-set-image.cjs +0 -0
- package/skills/document/bin/pptx-slide.cjs +22 -32
- package/skills/document/bin/pptx-theme.cjs +1 -20
- package/skills/document/bin/pptx-tokenize.cjs +1 -1
- package/skills/document/bin/xlsx-query.cjs +0 -0
- package/skills/document/bin/xlsx-recalc.cjs +0 -0
- package/skills/document/bin-reference.md +35 -0
- package/skills/document/content-pipeline.md +19 -18
- package/skills/document/deck-design.md +110 -211
- package/skills/document/layouts/README.md +4 -33
- package/skills/document/motion/README.md +4 -2
- package/skills/document/office.md +5 -1
- package/skills/document/rewrite.md +43 -99
- package/skills/document/styles/README.md +0 -7
- package/skills/document/theme-system.md +1 -0
- package/skills/document/themes/README.md +5 -5
- package/skills/document/themes/anthropic-clay.json +1 -7
- package/skills/document/themes/apple-keynote.json +1 -7
- package/skills/document/themes/black-gold.json +1 -2
- package/skills/document/themes/cinereel.json +1 -2
- package/skills/document/themes/cool-tech.json +1 -2
- package/skills/document/themes/hand-drawn.json +1 -2
- package/skills/document/themes/ivory-gold.json +1 -2
- package/skills/document/themes/magazine-ink.json +1 -2
- package/skills/document/themes/mckinsey-consulting.json +1 -7
- package/skills/document/themes/mono.json +1 -2
- package/skills/document/themes/swiss-grid.json +1 -2
- package/skills/document/themes/warm-editorial.json +1 -2
- package/skills/image-layout/SKILL.md +1 -1
- 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())
|
package/skills/document/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: document
|
|
3
|
-
description: "Use
|
|
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
|
-
**
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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.
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
- `bin/
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
- `bin/doc-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
- `bin/
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
- `bin/
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
- `bin/
|
|
234
|
-
- `bin/pdf-form.cjs list
|
|
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 —
|