dsh-courseware 0.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.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +109 -0
  3. package/cordis.patch.yml +9 -0
  4. package/engine/pptxgen/__init__.py +8 -0
  5. package/engine/pptxgen/__main__.py +4 -0
  6. package/engine/pptxgen/builtin_template.py +218 -0
  7. package/engine/pptxgen/cli.py +575 -0
  8. package/engine/pptxgen/examples.py +290 -0
  9. package/engine/pptxgen/imagegen.py +564 -0
  10. package/engine/pptxgen/layoutmap.py +211 -0
  11. package/engine/pptxgen/patterns.py +106 -0
  12. package/engine/pptxgen/preview.py +126 -0
  13. package/engine/pptxgen/profile.py +456 -0
  14. package/engine/pptxgen/render.py +1673 -0
  15. package/engine/pptxgen/review.py +184 -0
  16. package/engine/pptxgen/schemacheck.py +122 -0
  17. package/engine/pptxgen/schemas/XAdES.xsd +466 -0
  18. package/engine/pptxgen/schemas/XAdESv141.xsd +15 -0
  19. package/engine/pptxgen/schemas/chartEx.xsd +838 -0
  20. package/engine/pptxgen/schemas/dml-chart.xsd +1499 -0
  21. package/engine/pptxgen/schemas/dml-chartDrawing.xsd +146 -0
  22. package/engine/pptxgen/schemas/dml-diagram.xsd +1085 -0
  23. package/engine/pptxgen/schemas/dml-drawing.xsd +63 -0
  24. package/engine/pptxgen/schemas/dml-lockedCanvas.xsd +11 -0
  25. package/engine/pptxgen/schemas/dml-main.xsd +3081 -0
  26. package/engine/pptxgen/schemas/dml-picture.xsd +23 -0
  27. package/engine/pptxgen/schemas/dml-spreadsheetDrawing.xsd +185 -0
  28. package/engine/pptxgen/schemas/dml-wordprocessingDrawing.xsd +287 -0
  29. package/engine/pptxgen/schemas/drawing-chart2012.xsd +129 -0
  30. package/engine/pptxgen/schemas/markup-compatibility.xsd +95 -0
  31. package/engine/pptxgen/schemas/opc-digSig.xsd +49 -0
  32. package/engine/pptxgen/schemas/opc-relationships.xsd +33 -0
  33. package/engine/pptxgen/schemas/pml.xsd +1676 -0
  34. package/engine/pptxgen/schemas/shared-additionalCharacteristics.xsd +28 -0
  35. package/engine/pptxgen/schemas/shared-bibliography.xsd +144 -0
  36. package/engine/pptxgen/schemas/shared-commonSimpleTypes.xsd +172 -0
  37. package/engine/pptxgen/schemas/shared-customXmlDataProperties.xsd +25 -0
  38. package/engine/pptxgen/schemas/shared-customXmlSchemaProperties.xsd +18 -0
  39. package/engine/pptxgen/schemas/shared-documentPropertiesCustom.xsd +59 -0
  40. package/engine/pptxgen/schemas/shared-documentPropertiesExtended.xsd +56 -0
  41. package/engine/pptxgen/schemas/shared-documentPropertiesVariantTypes.xsd +195 -0
  42. package/engine/pptxgen/schemas/shared-math.xsd +582 -0
  43. package/engine/pptxgen/schemas/shared-relationshipReference.xsd +25 -0
  44. package/engine/pptxgen/schemas/signatureInfo.xsd +103 -0
  45. package/engine/pptxgen/schemas/sml.xsd +4439 -0
  46. package/engine/pptxgen/schemas/visio.xsd +829 -0
  47. package/engine/pptxgen/schemas/vml-main.xsd +570 -0
  48. package/engine/pptxgen/schemas/vml-officeDrawing.xsd +509 -0
  49. package/engine/pptxgen/schemas/vml-presentationDrawing.xsd +12 -0
  50. package/engine/pptxgen/schemas/vml-spreadsheetDrawing.xsd +108 -0
  51. package/engine/pptxgen/schemas/vml-wordprocessingDrawing.xsd +96 -0
  52. package/engine/pptxgen/schemas/vmlDrawing.xsd +36 -0
  53. package/engine/pptxgen/schemas/wml.xsd +3643 -0
  54. package/engine/pptxgen/schemas/word12.xsd +66 -0
  55. package/engine/pptxgen/schemas/xlThreadedComments.xsd +59 -0
  56. package/engine/pptxgen/schemas/xlThreadedComments2.xsd +22 -0
  57. package/engine/pptxgen/schemas/xmldsig-core-schema.xsd +318 -0
  58. package/engine/pptxgen/shapes.py +417 -0
  59. package/engine/pptxgen/spec.py +529 -0
  60. package/engine/pptxgen/template.py +834 -0
  61. package/engine/pptxgen/util.py +218 -0
  62. package/engine/pptxgen/xmlutil.py +298 -0
  63. package/engine/pptxgen.sh +7 -0
  64. package/engine/requirements.txt +9 -0
  65. package/lib/index.js +186 -0
  66. package/package.json +34 -0
  67. package/skills/courseware/SKILL.md +130 -0
  68. package/skills/courseware/references/content-polish.md +121 -0
  69. package/skills/courseware/references/deck-spec.md +155 -0
  70. package/skills/courseware/references/design-review.md +121 -0
  71. package/skills/courseware/references/image-backends.md +94 -0
  72. package/skills/courseware/references/quality-gates.md +71 -0
  73. package/skills/courseware/references/roadmap.md +55 -0
  74. package/skills/courseware/references/template-fidelity.md +100 -0
  75. package/skills/courseware/workflows/generate.md +172 -0
  76. package/skills/courseware/workflows/quick.md +41 -0
  77. package/skills/courseware/workflows/revise.md +40 -0
  78. package/skills/courseware/workflows/routing.md +49 -0
  79. package/skills/courseware/workflows/stages/generate-images.md +74 -0
  80. package/skills/courseware/workflows/template-intake.md +112 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Liang Yu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # dsh-courseware
2
+
3
+ 把 DeepSeek Harness 的 **courseware 技能**(中小学课件生成)及其 **pptxgen 引擎**
4
+ 打包成官方可安装的 DSH 插件包:`dsh plugin --profile <名字> add <本包>`。
5
+
6
+ 安装后,profile 里每个 agent 的技能目录都会出现 `courseware` 技能:
7
+ 拿用户自己的 `.pptx` 模板 + 一份课程大纲(教案/文档/纯文本),产出套用该模板
8
+ 背景、配色、字体、页面骨架的课件 `.pptx`。
9
+
10
+ ## 包内结构
11
+
12
+ ```
13
+ dsh-courseware/
14
+ ├── package.json type:module + main:lib/index.js + dsh.bundle.patch(命门)
15
+ ├── cordis.patch.yml 把本包插成一行,loader 才会加载它
16
+ ├── lib/index.js 零依赖提供器:扫描 skills/ 注册进 ctx.skills
17
+ ├── skills/courseware/ SKILL.md + workflows/ + references/
18
+ └── engine/ pptxgen 引擎源码 + pptxgen.sh + requirements.txt
19
+ ```
20
+
21
+ ## 安装
22
+
23
+ 前置:已装 DeepSeek Harness + pnpm(`dsh plugin` 内部调用 pnpm)。
24
+
25
+ ```sh
26
+ # 本地文件夹(pnpm 链接安装,改完重启 profile 即生效,适合开发)
27
+ dsh plugin --profile web add /路径/到/dsh-courseware
28
+
29
+ # 发布到 npm 后(推荐)
30
+ dsh plugin --profile web add dsh-courseware
31
+
32
+ # 或 GitHub / tarball
33
+ dsh plugin --profile web add https://github.com/你的账号/dsh-courseware.git
34
+ dsh plugin --profile web add /路径/to/dsh-courseware-0.1.0.tgz
35
+ ```
36
+
37
+ 安装后**重启 profile**(停掉重跑 `dsh web`,刷新浏览器),然后验证:
38
+
39
+ ```sh
40
+ dsh --profile web --dump-config # 输出里应出现 dsh-courseware 行
41
+ ```
42
+
43
+ agent 的技能目录里应出现 `courseware`,可用 `skill` 工具加载。
44
+
45
+ 卸载:
46
+
47
+ ```sh
48
+ dsh plugin --profile web remove dsh-courseware
49
+ ```
50
+
51
+ > 必须用 `dsh plugin` 形式安装。直接 `npm install` 只会把包当普通库装进当前
52
+ > 目录,不会注册进任何 profile,技能永远不会被加载。
53
+
54
+ ## 首次使用:初始化引擎(一次性)
55
+
56
+ 引擎需要 Python 3.10+,首次跑课件前初始化虚拟环境:
57
+
58
+ ```sh
59
+ python3 -m venv "<引擎目录>/.venv"
60
+ "<引擎目录>/.venv/bin/pip" install -r "<引擎目录>/requirements.txt"
61
+ ```
62
+
63
+ 引擎目录 = 技能目录的上两级(agent 会自行按此规则定位);也可用环境变量
64
+ `PPTXGEN_ENGINE_HOME` 指向自备引擎。
65
+
66
+ 另外,`check --render` 与 `preview` 需要本机装有 LibreOffice(macOS:
67
+ `/Applications/LibreOffice.app`);仅 `build` 不依赖它。模板用到的字体
68
+ (如 华文琥珀/微软雅黑)在目标机器上缺失时,字号会退化,建议装齐常用中文字体。
69
+
70
+ ## 图片后端配置 —— 本包不含任何密钥
71
+
72
+ **包里没有任何 API key、也没有任何密钥配置**(引擎源码只有占位符;`~/.pptxgen/config.yaml`
73
+ 在用户侧,不在包内)。每个使用者装好后自己配:
74
+
75
+ 方式一:环境变量
76
+
77
+ ```sh
78
+ export PPTXGEN_IMAGE_BASE_URL="https://你的服务商/v1"
79
+ export PPTXGEN_IMAGE_MODEL="你的模型名"
80
+ export PPTXGEN_IMAGE_API_KEY="你自己的 key"
81
+ ```
82
+
83
+ 方式二:用户配置文件 `~/.pptxgen/config.yaml`
84
+
85
+ ```yaml
86
+ images:
87
+ base_url: https://你的服务商/v1
88
+ model: 你的模型名
89
+ api_key: 你自己的 key
90
+ ```
91
+
92
+ 引擎的图片配置优先级:环境变量 > 课件 `options.images` > 用户配置文件。
93
+ 生成配图时 agent 会先检查后端是否配好;没配就停下来提示你去配(不静默跳过)。
94
+
95
+ ## 发布到 npm 的注意
96
+
97
+ - `package.json` 的 `files` 白名单已排除 `.venv/`、`__pycache__/`、`.git/` 等;
98
+ 发布前再跑一遍 `npm pack --dry-run` 确认包内**没有**任何密钥文件。
99
+ - 发布前先在干净的 profile 上本地安装验证一遍(上面"安装"节)。
100
+ - 包名 `dsh-courseware` 如与 npm 已有包冲突,可改成带 scope 的
101
+ `@你的账号/dsh-courseware`(改 package.json 的 `name` 与 cordis.patch.yml 的
102
+ `name` 两处即可)。
103
+ - 技能内容更新:改 `skills/courseware/` 后重新发布即可,无需改代码
104
+ (提供器自动发现新技能/新文件)。
105
+ - 引擎更新:替换 `engine/pptxgen/` 内容后重新发布。
106
+
107
+ ## License
108
+
109
+ MIT。技能内容与引擎源码版权归原作者。
@@ -0,0 +1,9 @@
1
+ # dsh-courseware bundle patch: 把 courseware 技能提供器挂进 host 层技能注册表,
2
+ # 使 profile 里每个 agent preset 的作用域链都能合并它注册的技能。
3
+ #
4
+ # 本补丁叠加在 dsh-base 层之上;后面的层(profile 自己的 cordis.patch.yml、
5
+ # --patch 叠加)仍可按 id 定位这一行。技能正文在包内 skills/ 目录随包分发,
6
+ # 提供器用 import.meta.url 定位,无需任何用户配置。
7
+ - insert:
8
+ - id: dsh-courseware
9
+ name: 'dsh-courseware'
@@ -0,0 +1,8 @@
1
+ """pptxgen —— 基于自有模板的中小学课件 PPTX 生成器。"""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ from .spec import SpecError, load_deck, normalize_deck # noqa: F401
6
+ from .template import load_template # noqa: F401
7
+
8
+ __all__ = ["load_deck", "load_template", "normalize_deck", "SpecError", "__version__"]
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())
@@ -0,0 +1,218 @@
1
+ """内置兜底模板:没有提供模板文件时,生成一份干净的 16:9 课件模板。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import copy
6
+ from typing import Dict, Optional
7
+
8
+ from pptx import Presentation
9
+ from pptx.enum.shapes import MSO_SHAPE
10
+ from pptx.oxml.ns import qn
11
+ from pptx.util import Emu, Inches, Pt
12
+
13
+ from .util import emu_from_inch, parse_color
14
+
15
+ DEFAULT_PALETTE: Dict[str, str] = {
16
+ "dk1": "1F2937",
17
+ "lt1": "FFFFFF",
18
+ "dk2": "5B6675",
19
+ "lt2": "EEF2F7",
20
+ "accent1": "2F6FED",
21
+ "accent2": "F08C00",
22
+ "accent3": "2F9E44",
23
+ "accent4": "7048E8",
24
+ "accent5": "0C8599",
25
+ "accent6": "E8590C",
26
+ "hlink": "2F6FED",
27
+ "folHlink": "7048E8",
28
+ }
29
+
30
+ DEFAULT_MAJOR_FONT = "微软雅黑"
31
+ DEFAULT_MINOR_FONT = "微软雅黑"
32
+
33
+
34
+ def build_builtin_template(path: str, *, palette: Optional[Dict[str, str]] = None,
35
+ font: Optional[str] = None, theme_name: str = "DSH 课件模板",
36
+ size: str = "16:9") -> str:
37
+ prs = Presentation()
38
+ base_w = prs.slide_width
39
+ if size == "4:3":
40
+ # python-pptx 默认模板本身就是 4:3,无需缩放
41
+ pass
42
+ else:
43
+ prs.slide_width = Emu(12192000) # 13.333in
44
+ prs.slide_height = Emu(6858000) # 7.5in
45
+
46
+ pal = dict(DEFAULT_PALETTE)
47
+ if palette:
48
+ pal.update({k: parse_color(v) or v for k, v in palette.items()})
49
+ font = font or DEFAULT_MINOR_FONT
50
+
51
+ _patch_theme(prs, pal, font, theme_name)
52
+ if size != "4:3":
53
+ # 默认模板是 4:3 的版式,把占位符横向拉伸到目标比例
54
+ _rescale_horizontally(prs, prs.slide_width / float(base_w))
55
+ _patch_master(prs, pal)
56
+ title_pt = 36 if size != "4:3" else 32
57
+ body_pt = (20, 17, 15, 14, 13) if size != "4:3" else (24, 20, 18, 16, 14)
58
+ _set_txstyles(prs, title_pt=title_pt, body_pt=body_pt)
59
+ _rename_layouts(prs)
60
+ prs.save(path)
61
+ return path
62
+
63
+
64
+ # 把 Office 默认版式改成中文语义名,让页型->版式映射天然精确
65
+ LAYOUT_NAMES = [
66
+ "封面", "标题和内容", "节标题", "两栏内容", "比较",
67
+ "仅标题", "空白", "内容与标题", "图片与标题", "标题和竖排文字", "竖排标题与文本",
68
+ ]
69
+
70
+
71
+ def _rename_layouts(prs) -> None:
72
+ for i, lay in enumerate(prs.slide_layouts):
73
+ if i < len(LAYOUT_NAMES):
74
+ try:
75
+ lay.name = LAYOUT_NAMES[i]
76
+ except Exception:
77
+ pass
78
+
79
+
80
+ def _set_txstyles(prs, title_pt: float, body_pt) -> None:
81
+ """重设母版文字样式的字号,使其符合 16:9 课件的可读性。"""
82
+ txStyles = prs.slide_master._element.find(qn("p:txStyles"))
83
+ if txStyles is None:
84
+ return
85
+
86
+ def set_levels(style_tag: str, sizes) -> None:
87
+ node = txStyles.find(qn(style_tag))
88
+ if node is None:
89
+ return
90
+ for i, size in enumerate(sizes, start=1):
91
+ lvl = node.find(qn(f"a:lvl{i}pPr"))
92
+ if lvl is None:
93
+ continue
94
+ d = lvl.find(qn("a:defRPr"))
95
+ if d is None:
96
+ d = lvl.makeelement(qn("a:defRPr"), {})
97
+ lvl.append(d)
98
+ d.set("sz", str(int(size * 100)))
99
+
100
+ set_levels("p:titleStyle", [title_pt])
101
+ set_levels("p:bodyStyle", list(body_pt))
102
+ set_levels("p:otherStyle", [title_pt if False else body_pt[0]])
103
+
104
+
105
+ def _rescale_horizontally(prs, factor: float) -> None:
106
+ """把母版与所有版式里形状的 x / cx 按比例缩放(保持 y / cy 不变)。"""
107
+ from pptx.oxml.ns import qn as _qn
108
+
109
+ roots = [prs.slide_master._element]
110
+ roots += [lay._element for lay in prs.slide_layouts]
111
+ for root in roots:
112
+ for xfrm in root.iter(_qn("a:xfrm")):
113
+ off = xfrm.find(_qn("a:off"))
114
+ ext = xfrm.find(_qn("a:ext"))
115
+ if off is not None and off.get("x"):
116
+ off.set("x", str(int(int(off.get("x")) * factor)))
117
+ if ext is not None and ext.get("cx"):
118
+ ext.set("cx", str(int(int(ext.get("cx")) * factor)))
119
+
120
+
121
+ # --------------------------------------------------------------------------
122
+ def _theme_root(prs):
123
+ """python-pptx 把 theme 当作不透明 Part,需要用 blob 读写。"""
124
+ from lxml import etree
125
+ from pptx.opc.constants import RELATIONSHIP_TYPE as RT
126
+
127
+ part = prs.slide_master.part.part_related_by(RT.THEME)
128
+ root = getattr(part, "_element", None)
129
+ if root is None:
130
+ root = etree.fromstring(part.blob)
131
+ return part, root
132
+
133
+
134
+ def _save_theme(part, root) -> None:
135
+ from lxml import etree
136
+
137
+ part._blob = etree.tostring(root, xml_declaration=True, encoding="UTF-8", standalone=True)
138
+
139
+
140
+ def _patch_theme(prs, pal: Dict[str, str], font: str, theme_name: str) -> None:
141
+ part, root = _theme_root(prs)
142
+ clrScheme = root.find(f".//{qn('a:clrScheme')}")
143
+ if clrScheme is not None:
144
+ clrScheme.set("name", theme_name)
145
+ for child in list(clrScheme):
146
+ key = child.tag.split("}")[-1]
147
+ color = pal.get(key)
148
+ if not color:
149
+ continue
150
+ for sub in list(child):
151
+ child.remove(sub)
152
+ srgb = child.makeelement(qn("a:srgbClr"), {"val": color})
153
+ child.append(srgb)
154
+
155
+ fontScheme = root.find(f".//{qn('a:fontScheme')}")
156
+ if fontScheme is not None:
157
+ fontScheme.set("name", theme_name)
158
+ for which in ("majorFont", "minorFont"):
159
+ node = fontScheme.find(qn(f"a:{which}"))
160
+ if node is None:
161
+ continue
162
+ for tag in ("a:latin", "a:ea", "a:cs"):
163
+ el = node.find(qn(tag))
164
+ if el is None:
165
+ el = node.makeelement(qn(tag), {})
166
+ node.append(el)
167
+ el.set("typeface", font)
168
+
169
+ _save_theme(part, root)
170
+
171
+
172
+ def _patch_master(prs, pal: Dict[str, str]) -> None:
173
+ master = prs.slide_master
174
+ W, H = prs.slide_width, prs.slide_height
175
+
176
+ # 纯白背景,保证任何版式下文字都清晰
177
+ cSld = master._element.find(qn("p:cSld"))
178
+ if cSld is not None:
179
+ old = cSld.find(qn("p:bg"))
180
+ if old is not None:
181
+ cSld.remove(old)
182
+ bg = cSld.makeelement(qn("p:bg"), {})
183
+ bgPr = bg.makeelement(qn("p:bgPr"), {})
184
+ solid = bgPr.makeelement(qn("a:solidFill"), {})
185
+ solid.append(solid.makeelement(qn("a:srgbClr"), {"val": "FFFFFF"}))
186
+ bgPr.append(solid)
187
+ bgPr.append(bgPr.makeelement(qn("a:effectLst"), {}))
188
+ bg.append(bgPr)
189
+ cSld.insert(0, bg)
190
+
191
+ spTree = master.shapes._spTree
192
+ accent = pal.get("accent1", "2F6FED")
193
+ accent2 = pal.get("accent2", "F08C00")
194
+
195
+ # 底部细色条(母版装饰,所有版式/幻灯片自动继承)
196
+ bar_h = max(int(H * 0.012), 9144)
197
+ spTree.append(_rect_sp(0, int(H) - bar_h, int(W * 0.62), bar_h, accent, 900, "__builtin_bar1"))
198
+ spTree.append(_rect_sp(int(W * 0.62), int(H) - bar_h, int(W * 0.38), bar_h, accent2, 901, "__builtin_bar2"))
199
+
200
+
201
+ def _rect_sp(x: int, y: int, cx: int, cy: int, color: str, shape_id: int, name: str):
202
+ """构造一个无描边的纯色矩形 <p:sp>(用于母版装饰)。"""
203
+ from pptx.oxml import parse_xml
204
+ from pptx.oxml.ns import nsdecls
205
+
206
+ xml = (
207
+ f'<p:sp {nsdecls("p", "a")}>'
208
+ f'<p:nvSpPr><p:cNvPr id="{shape_id}" name="{name}"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr>'
209
+ f'<p:spPr>'
210
+ f'<a:xfrm><a:off x="{int(x)}" y="{int(y)}"/><a:ext cx="{int(cx)}" cy="{int(cy)}"/></a:xfrm>'
211
+ f'<a:prstGeom prst="rect"><a:avLst/></a:prstGeom>'
212
+ f'<a:solidFill><a:srgbClr val="{parse_color(color)}"/></a:solidFill>'
213
+ f'<a:ln><a:noFill/></a:ln>'
214
+ f'</p:spPr>'
215
+ f'<p:txBody><a:bodyPr/><a:lstStyle/><a:p/></p:txBody>'
216
+ f'</p:sp>'
217
+ )
218
+ return parse_xml(xml)