@topmindspace/tms-skills 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +145 -134
- package/README.md +1 -1
- package/bin/tms-skills.js +160 -160
- package/package.json +48 -48
- package/top-ppt-html/README.md +254 -254
- package/top-ppt-html/SKILL.md +111 -111
- package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.html +3926 -3926
- package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.model.json +167 -167
- package/top-ppt-html/assets/examples/2026-09-09-architecture-spectrum.html +3926 -3926
- package/top-ppt-html/assets/examples/2026-09-09-architecture-spectrum.model.json +167 -167
- package/top-ppt-html/assets/examples/2026-09-09-presentation-apple-mono.html +4325 -4325
- package/top-ppt-html/assets/examples/2026-09-09-presentation-apple-mono.model.json +320 -320
- package/top-ppt-html/assets/examples/2026-09-09-presentation-brand-red.html +4325 -4325
- package/top-ppt-html/assets/examples/2026-09-09-presentation-brand-red.model.json +320 -320
- package/top-ppt-html/assets/examples/2026-09-09-presentation-business-blue.html +4325 -4325
- package/top-ppt-html/assets/examples/2026-09-09-presentation-business-blue.model.json +320 -320
- package/top-ppt-html/assets/examples/2026-09-09-research-deep-teal.html +5527 -5527
- package/top-ppt-html/assets/examples/2026-09-09-research-deep-teal.model.json +913 -913
- package/top-ppt-html/assets/examples/2026-09-09-research-indigo-violet.html +5527 -5527
- package/top-ppt-html/assets/examples/2026-09-09-research-indigo-violet.model.json +913 -913
- package/top-ppt-html/assets/examples/2026-09-09-research-mckinsey.html +5527 -5527
- package/top-ppt-html/assets/examples/2026-09-09-research-mckinsey.model.json +913 -913
- package/top-ppt-html/assets/examples/2026-09-09-research-warm-sand.html +5527 -5527
- package/top-ppt-html/assets/examples/2026-09-09-research-warm-sand.model.json +913 -913
- package/top-ppt-html/assets/pptx-export.js +1944 -1944
- package/top-ppt-html/assets/style-gallery.html +589 -589
- package/top-ppt-html/assets/templates/architecture.html +3728 -3728
- package/top-ppt-html/assets/templates/engine.css +840 -840
- package/top-ppt-html/assets/templates/presentation.html +3738 -3738
- package/top-ppt-html/assets/templates/research.html +4017 -4017
- package/top-ppt-html/assets/templates/ui.js +520 -520
- package/top-ppt-html/evals/prompts.csv +15 -15
- package/top-ppt-html/evals/rubric.schema.json +25 -25
- package/top-ppt-html/evals/run_evals.py +220 -220
- package/top-ppt-html/evals/trace.example.json +16 -16
- package/top-ppt-html/package-lock.json +207 -0
- package/top-ppt-html/package.json +35 -35
- package/top-ppt-html/references/charts-basic.md +624 -624
- package/top-ppt-html/references/charts-discipline.md +108 -108
- package/top-ppt-html/references/charts-extended.md +482 -482
- package/top-ppt-html/references/charts.md +28 -28
- package/top-ppt-html/references/components-atoms.md +624 -624
- package/top-ppt-html/references/components.md +30 -30
- package/top-ppt-html/references/content-rules.md +490 -490
- package/top-ppt-html/references/design-system-engine.md +235 -235
- package/top-ppt-html/references/design-system.md +478 -478
- package/top-ppt-html/references/failure-modes.md +214 -214
- package/top-ppt-html/references/high-fidelity.md +127 -127
- package/top-ppt-html/references/icons.md +397 -397
- package/top-ppt-html/references/industry-benchmark.md +105 -105
- package/top-ppt-html/references/infographics-stats.md +308 -308
- package/top-ppt-html/references/infographics-structure.md +226 -226
- package/top-ppt-html/references/infographics.md +43 -43
- package/top-ppt-html/references/layout-grammar.md +315 -315
- package/top-ppt-html/references/layouts-architecture.md +108 -108
- package/top-ppt-html/references/layouts-combo.md +600 -600
- package/top-ppt-html/references/layouts-research.md +160 -160
- package/top-ppt-html/references/modes.md +254 -254
- package/top-ppt-html/references/outline-design.md +275 -275
- package/top-ppt-html/references/playbook.md +266 -266
- package/top-ppt-html/references/pptx-export.md +209 -209
- package/top-ppt-html/references/reform-plan.md +252 -252
- package/top-ppt-html/references/styles.md +370 -370
- package/top-ppt-html/references/tech-design.md +138 -138
- package/top-ppt-html/scripts/audit_css.py +109 -109
- package/top-ppt-html/scripts/audit_docs.py +176 -176
- package/top-ppt-html/scripts/audit_skill.py +220 -220
- package/top-ppt-html/scripts/audit_styles.py +351 -351
- package/top-ppt-html/scripts/build_examples.py +2276 -2276
- package/top-ppt-html/scripts/build_pptx.js +2380 -2380
- package/top-ppt-html/scripts/capture_theme_overview.js +78 -78
- package/top-ppt-html/scripts/checks_html.py +127 -127
- package/top-ppt-html/scripts/cross_verify.py +294 -294
- package/top-ppt-html/scripts/env_probe.py +158 -158
- package/top-ppt-html/scripts/extract_model.py +210 -210
- package/top-ppt-html/scripts/extract_snippet.py +374 -374
- package/top-ppt-html/scripts/gen_channel_a.js +214 -214
- package/top-ppt-html/scripts/layout-constants.json +3377 -3377
- package/top-ppt-html/scripts/layout_slots.json +830 -830
- package/top-ppt-html/scripts/lib_layout_regions.js +412 -412
- package/top-ppt-html/scripts/measure_height.py +178 -178
- package/top-ppt-html/scripts/model-schema.json +547 -547
- package/top-ppt-html/scripts/negative_tests.py +307 -307
- package/top-ppt-html/scripts/package_skill.py +291 -291
- package/top-ppt-html/scripts/prepare_images.py +341 -341
- package/top-ppt-html/scripts/probe_image_export.py +188 -188
- package/top-ppt-html/scripts/quality_gate.py +301 -301
- package/top-ppt-html/scripts/regression.py +308 -308
- package/top-ppt-html/scripts/render_compare.py +275 -275
- package/top-ppt-html/scripts/render_from_model.py +698 -698
- package/top-ppt-html/scripts/scaffold_report.py +1054 -1054
- package/top-ppt-html/scripts/section-file-map.json +104 -104
- package/top-ppt-html/scripts/sync_runtime.py +662 -662
- package/top-ppt-html/scripts/validate_pptx.py +1510 -1510
- package/top-ppt-html/scripts/validate_report.py +1456 -1456
|
@@ -1,158 +1,158 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
"""TopPPT HTML · 运行环境探测(公共模块 · 被 regression / probe_image_export 等复用)
|
|
4
|
-
|
|
5
|
-
统一 Node / pptxgenjs / python-pptx 的探测口径,避免各脚本各写一份导致漂移。
|
|
6
|
-
**不绑定任何机器的固定路径**:环境变量 > PATH > 常见托管目录(存在才用,不存在不影响)。
|
|
7
|
-
|
|
8
|
-
解析优先级:
|
|
9
|
-
Node 可执行文件
|
|
10
|
-
TOP_PPT_NODE_EXE > NODE_EXE > NODE
|
|
11
|
-
> PATH 上的 node > 常见托管目录最新版
|
|
12
|
-
含 pptxgenjs 的 node_modules
|
|
13
|
-
TOP_PPT_NODE_PATH > NODE_PATH
|
|
14
|
-
> 仓库内 node_modules > 逐级父目录 node_modules > 常见托管目录 > 全局 npm root
|
|
15
|
-
python-pptx
|
|
16
|
-
子进程 `import pptx` 是否成功(可选第三方裁判;未装则调用方跳过)
|
|
17
|
-
|
|
18
|
-
自检:
|
|
19
|
-
python scripts/env_probe.py
|
|
20
|
-
"""
|
|
21
|
-
from __future__ import annotations
|
|
22
|
-
|
|
23
|
-
import os
|
|
24
|
-
import shutil
|
|
25
|
-
import subprocess
|
|
26
|
-
import sys
|
|
27
|
-
from pathlib import Path
|
|
28
|
-
|
|
29
|
-
try:
|
|
30
|
-
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
31
|
-
except Exception:
|
|
32
|
-
pass
|
|
33
|
-
|
|
34
|
-
ROOT = Path(__file__).resolve().parent.parent
|
|
35
|
-
|
|
36
|
-
NODE_ENV_VARS = ('TOP_PPT_NODE_EXE', 'NODE_EXE', 'NODE')
|
|
37
|
-
NODE_PATH_ENV_VARS = ('TOP_PPT_NODE_PATH', 'NODE_PATH')
|
|
38
|
-
NODE_BASES = (
|
|
39
|
-
Path.home() / '.local' / 'share' / 'node',
|
|
40
|
-
Path.home() / 'AppData' / 'Local' / 'nvs',
|
|
41
|
-
)
|
|
42
|
-
MODULE_BASES = (
|
|
43
|
-
Path.home() / 'node_modules',
|
|
44
|
-
)
|
|
45
|
-
NODE_EXES = ('node.exe', 'bin/node', 'bin/node.exe')
|
|
46
|
-
DEFAULT_PKG = 'pptxgenjs'
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
# ── Node 可执行文件 ───────────────────────────────────────────────────────────
|
|
50
|
-
|
|
51
|
-
def node_candidates() -> list[str]:
|
|
52
|
-
"""按优先级返回候选 node 可执行文件(去重、保序)。"""
|
|
53
|
-
cands: list[str] = []
|
|
54
|
-
for var in NODE_ENV_VARS:
|
|
55
|
-
val = os.environ.get(var)
|
|
56
|
-
if val and Path(val).is_file():
|
|
57
|
-
cands.append(val)
|
|
58
|
-
found = shutil.which('node')
|
|
59
|
-
if found:
|
|
60
|
-
cands.append(found)
|
|
61
|
-
for base in NODE_BASES:
|
|
62
|
-
if not base.is_dir():
|
|
63
|
-
continue
|
|
64
|
-
try:
|
|
65
|
-
for ver in sorted((p for p in base.iterdir() if p.is_dir()), reverse=True):
|
|
66
|
-
for exe in NODE_EXES:
|
|
67
|
-
p = ver / exe
|
|
68
|
-
if p.is_file():
|
|
69
|
-
cands.append(str(p))
|
|
70
|
-
break
|
|
71
|
-
except OSError:
|
|
72
|
-
pass
|
|
73
|
-
seen, out = set(), []
|
|
74
|
-
for c in cands:
|
|
75
|
-
if c not in seen:
|
|
76
|
-
seen.add(c)
|
|
77
|
-
out.append(c)
|
|
78
|
-
return out
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def find_node() -> str | None:
|
|
82
|
-
for c in node_candidates():
|
|
83
|
-
if Path(c).is_file():
|
|
84
|
-
return c
|
|
85
|
-
return None
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
# ── 含 pptxgenjs 的 node_modules ─────────────────────────────────────────────
|
|
89
|
-
|
|
90
|
-
def node_modules_candidates() -> list[str]:
|
|
91
|
-
"""按优先级返回可能含目标包的 node_modules 目录(去重、保序)。"""
|
|
92
|
-
cands: list[str] = []
|
|
93
|
-
for var in NODE_PATH_ENV_VARS:
|
|
94
|
-
val = os.environ.get(var)
|
|
95
|
-
if val:
|
|
96
|
-
cands.extend(p for p in val.split(os.pathsep) if p)
|
|
97
|
-
cands.append(str(ROOT / 'node_modules'))
|
|
98
|
-
cands.extend(str(p / 'node_modules') for p in list(ROOT.parents)[:3]) # monorepo 场景
|
|
99
|
-
cands.extend(str(b) for b in MODULE_BASES)
|
|
100
|
-
try:
|
|
101
|
-
npm_root = subprocess.run(['npm', 'root', '-g'], capture_output=True, text=True,
|
|
102
|
-
encoding='utf-8', timeout=30).stdout.strip()
|
|
103
|
-
if npm_root:
|
|
104
|
-
cands.append(npm_root)
|
|
105
|
-
except Exception:
|
|
106
|
-
pass
|
|
107
|
-
seen, out = set(), []
|
|
108
|
-
for c in cands:
|
|
109
|
-
if c and c not in seen:
|
|
110
|
-
seen.add(c)
|
|
111
|
-
out.append(c)
|
|
112
|
-
return out
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def find_node_modules(pkg: str = DEFAULT_PKG) -> str | None:
|
|
116
|
-
for c in node_modules_candidates():
|
|
117
|
-
if (Path(c) / pkg).is_dir():
|
|
118
|
-
return c
|
|
119
|
-
return None
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
# ── 组合与提示 ───────────────────────────────────────────────────────────────
|
|
123
|
-
|
|
124
|
-
def node_env(node_modules: str | None = None) -> dict[str, str]:
|
|
125
|
-
"""返回可直接传给 subprocess 的环境(把 NODE_PATH 指到解析出的 node_modules)。"""
|
|
126
|
-
env = dict(os.environ)
|
|
127
|
-
if node_modules:
|
|
128
|
-
env['NODE_PATH'] = node_modules
|
|
129
|
-
return env
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def has_python_pptx(python_exe: str | None = None) -> bool:
|
|
133
|
-
exe = python_exe or sys.executable
|
|
134
|
-
try:
|
|
135
|
-
return subprocess.run([exe, '-c', 'import pptx'], capture_output=True).returncode == 0
|
|
136
|
-
except OSError:
|
|
137
|
-
return False
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
NODE_HINT = ('未找到 Node 可执行文件(PPTX 精导与回归必需)。安装 Node 后重试,'
|
|
141
|
-
'或用环境变量 TOP_PPT_NODE_EXE 指定路径。')
|
|
142
|
-
NODE_MODULES_HINT = ('未找到含 pptxgenjs 的 node_modules(PPTX 生成必需)。'
|
|
143
|
-
'在技能目录执行 `npm install`(依 package.json),'
|
|
144
|
-
'或用环境变量 TOP_PPT_NODE_PATH 指定 node_modules 路径。')
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
def resolve(pkg: str = DEFAULT_PKG) -> tuple[str | None, str | None]:
|
|
148
|
-
"""一次性返回 (node, node_modules)。"""
|
|
149
|
-
return find_node(), find_node_modules(pkg)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if __name__ == '__main__':
|
|
153
|
-
node, mods = resolve()
|
|
154
|
-
print('环境探测(TopPPT HTML)')
|
|
155
|
-
print(f' node : {node or "未找到"}')
|
|
156
|
-
print(f' node_modules : {mods or "未找到(缺 pptxgenjs)"}')
|
|
157
|
-
print(f' python-pptx : {"可用" if has_python_pptx() else "未安装(可选,交叉裁判会跳过)"}')
|
|
158
|
-
sys.exit(0 if (node and mods) else 1)
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""TopPPT HTML · 运行环境探测(公共模块 · 被 regression / probe_image_export 等复用)
|
|
4
|
+
|
|
5
|
+
统一 Node / pptxgenjs / python-pptx 的探测口径,避免各脚本各写一份导致漂移。
|
|
6
|
+
**不绑定任何机器的固定路径**:环境变量 > PATH > 常见托管目录(存在才用,不存在不影响)。
|
|
7
|
+
|
|
8
|
+
解析优先级:
|
|
9
|
+
Node 可执行文件
|
|
10
|
+
TOP_PPT_NODE_EXE > NODE_EXE > NODE
|
|
11
|
+
> PATH 上的 node > 常见托管目录最新版
|
|
12
|
+
含 pptxgenjs 的 node_modules
|
|
13
|
+
TOP_PPT_NODE_PATH > NODE_PATH
|
|
14
|
+
> 仓库内 node_modules > 逐级父目录 node_modules > 常见托管目录 > 全局 npm root
|
|
15
|
+
python-pptx
|
|
16
|
+
子进程 `import pptx` 是否成功(可选第三方裁判;未装则调用方跳过)
|
|
17
|
+
|
|
18
|
+
自检:
|
|
19
|
+
python scripts/env_probe.py
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import os
|
|
24
|
+
import shutil
|
|
25
|
+
import subprocess
|
|
26
|
+
import sys
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
31
|
+
except Exception:
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
35
|
+
|
|
36
|
+
NODE_ENV_VARS = ('TOP_PPT_NODE_EXE', 'NODE_EXE', 'NODE')
|
|
37
|
+
NODE_PATH_ENV_VARS = ('TOP_PPT_NODE_PATH', 'NODE_PATH')
|
|
38
|
+
NODE_BASES = (
|
|
39
|
+
Path.home() / '.local' / 'share' / 'node',
|
|
40
|
+
Path.home() / 'AppData' / 'Local' / 'nvs',
|
|
41
|
+
)
|
|
42
|
+
MODULE_BASES = (
|
|
43
|
+
Path.home() / 'node_modules',
|
|
44
|
+
)
|
|
45
|
+
NODE_EXES = ('node.exe', 'bin/node', 'bin/node.exe')
|
|
46
|
+
DEFAULT_PKG = 'pptxgenjs'
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# ── Node 可执行文件 ───────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
def node_candidates() -> list[str]:
|
|
52
|
+
"""按优先级返回候选 node 可执行文件(去重、保序)。"""
|
|
53
|
+
cands: list[str] = []
|
|
54
|
+
for var in NODE_ENV_VARS:
|
|
55
|
+
val = os.environ.get(var)
|
|
56
|
+
if val and Path(val).is_file():
|
|
57
|
+
cands.append(val)
|
|
58
|
+
found = shutil.which('node')
|
|
59
|
+
if found:
|
|
60
|
+
cands.append(found)
|
|
61
|
+
for base in NODE_BASES:
|
|
62
|
+
if not base.is_dir():
|
|
63
|
+
continue
|
|
64
|
+
try:
|
|
65
|
+
for ver in sorted((p for p in base.iterdir() if p.is_dir()), reverse=True):
|
|
66
|
+
for exe in NODE_EXES:
|
|
67
|
+
p = ver / exe
|
|
68
|
+
if p.is_file():
|
|
69
|
+
cands.append(str(p))
|
|
70
|
+
break
|
|
71
|
+
except OSError:
|
|
72
|
+
pass
|
|
73
|
+
seen, out = set(), []
|
|
74
|
+
for c in cands:
|
|
75
|
+
if c not in seen:
|
|
76
|
+
seen.add(c)
|
|
77
|
+
out.append(c)
|
|
78
|
+
return out
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def find_node() -> str | None:
|
|
82
|
+
for c in node_candidates():
|
|
83
|
+
if Path(c).is_file():
|
|
84
|
+
return c
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# ── 含 pptxgenjs 的 node_modules ─────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
def node_modules_candidates() -> list[str]:
|
|
91
|
+
"""按优先级返回可能含目标包的 node_modules 目录(去重、保序)。"""
|
|
92
|
+
cands: list[str] = []
|
|
93
|
+
for var in NODE_PATH_ENV_VARS:
|
|
94
|
+
val = os.environ.get(var)
|
|
95
|
+
if val:
|
|
96
|
+
cands.extend(p for p in val.split(os.pathsep) if p)
|
|
97
|
+
cands.append(str(ROOT / 'node_modules'))
|
|
98
|
+
cands.extend(str(p / 'node_modules') for p in list(ROOT.parents)[:3]) # monorepo 场景
|
|
99
|
+
cands.extend(str(b) for b in MODULE_BASES)
|
|
100
|
+
try:
|
|
101
|
+
npm_root = subprocess.run(['npm', 'root', '-g'], capture_output=True, text=True,
|
|
102
|
+
encoding='utf-8', timeout=30).stdout.strip()
|
|
103
|
+
if npm_root:
|
|
104
|
+
cands.append(npm_root)
|
|
105
|
+
except Exception:
|
|
106
|
+
pass
|
|
107
|
+
seen, out = set(), []
|
|
108
|
+
for c in cands:
|
|
109
|
+
if c and c not in seen:
|
|
110
|
+
seen.add(c)
|
|
111
|
+
out.append(c)
|
|
112
|
+
return out
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def find_node_modules(pkg: str = DEFAULT_PKG) -> str | None:
|
|
116
|
+
for c in node_modules_candidates():
|
|
117
|
+
if (Path(c) / pkg).is_dir():
|
|
118
|
+
return c
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# ── 组合与提示 ───────────────────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
def node_env(node_modules: str | None = None) -> dict[str, str]:
|
|
125
|
+
"""返回可直接传给 subprocess 的环境(把 NODE_PATH 指到解析出的 node_modules)。"""
|
|
126
|
+
env = dict(os.environ)
|
|
127
|
+
if node_modules:
|
|
128
|
+
env['NODE_PATH'] = node_modules
|
|
129
|
+
return env
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def has_python_pptx(python_exe: str | None = None) -> bool:
|
|
133
|
+
exe = python_exe or sys.executable
|
|
134
|
+
try:
|
|
135
|
+
return subprocess.run([exe, '-c', 'import pptx'], capture_output=True).returncode == 0
|
|
136
|
+
except OSError:
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
NODE_HINT = ('未找到 Node 可执行文件(PPTX 精导与回归必需)。安装 Node 后重试,'
|
|
141
|
+
'或用环境变量 TOP_PPT_NODE_EXE 指定路径。')
|
|
142
|
+
NODE_MODULES_HINT = ('未找到含 pptxgenjs 的 node_modules(PPTX 生成必需)。'
|
|
143
|
+
'在技能目录执行 `npm install`(依 package.json),'
|
|
144
|
+
'或用环境变量 TOP_PPT_NODE_PATH 指定 node_modules 路径。')
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def resolve(pkg: str = DEFAULT_PKG) -> tuple[str | None, str | None]:
|
|
148
|
+
"""一次性返回 (node, node_modules)。"""
|
|
149
|
+
return find_node(), find_node_modules(pkg)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
if __name__ == '__main__':
|
|
153
|
+
node, mods = resolve()
|
|
154
|
+
print('环境探测(TopPPT HTML)')
|
|
155
|
+
print(f' node : {node or "未找到"}')
|
|
156
|
+
print(f' node_modules : {mods or "未找到(缺 pptxgenjs)"}')
|
|
157
|
+
print(f' python-pptx : {"可用" if has_python_pptx() else "未安装(可选,交叉裁判会跳过)"}')
|
|
158
|
+
sys.exit(0 if (node and mods) else 1)
|