dsh-ppt 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.
- package/CHANGELOG.md +10 -0
- package/LICENSE +21 -0
- package/README.en.md +139 -0
- package/README.md +143 -0
- package/cordis.patch.yml +17 -0
- package/lib/config.d.ts +10 -0
- package/lib/config.js +18 -0
- package/lib/index.d.ts +33 -0
- package/lib/index.js +200 -0
- package/lib/skill.d.ts +29 -0
- package/lib/skill.js +60 -0
- package/lib/types.d.ts +65 -0
- package/lib/types.js +2 -0
- package/package.json +71 -0
- package/skills/dsh-ppt/SKILL.md +103 -0
- package/skills/dsh-ppt/references/copywriting.md +68 -0
- package/skills/dsh-ppt/references/themes.md +55 -0
- package/skills/dsh-ppt/scripts/build-deck.mjs +126 -0
- package/skills/dsh-ppt/scripts/deck-core.mjs +1222 -0
package/lib/skill.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
export const SKILL_NAMES = ['dsh-ppt'];
|
|
5
|
+
/** 随包分发的技能目录绝对路径。 */
|
|
6
|
+
export function bundledSkillsDir() {
|
|
7
|
+
return fileURLToPath(new URL('../skills/', import.meta.url));
|
|
8
|
+
}
|
|
9
|
+
/** 解析 SKILL.md 的 frontmatter(v0.1 只读 name/description 两个单行字段,零依赖)。 */
|
|
10
|
+
function unquoteYamlScalar(value) {
|
|
11
|
+
const trimmed = value.trim();
|
|
12
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
13
|
+
return trimmed.slice(1, -1).replace(/\\"/g, '"').replace(/\\n/g, '\n').trim();
|
|
14
|
+
}
|
|
15
|
+
return trimmed;
|
|
16
|
+
}
|
|
17
|
+
export function parseSkillFile(text) {
|
|
18
|
+
const normalized = text.replace(/^\uFEFF/, '').replace(/\r\n?/g, '\n');
|
|
19
|
+
const match = /^---[ \t]*\n([\s\S]*?)\n---[ \t]*\n?/.exec(normalized);
|
|
20
|
+
if (!match)
|
|
21
|
+
return { name: '', description: '', content: normalized };
|
|
22
|
+
const frontmatter = match[1];
|
|
23
|
+
let name = '';
|
|
24
|
+
let description = '';
|
|
25
|
+
for (const rawLine of frontmatter.split('\n')) {
|
|
26
|
+
const line = rawLine.trim();
|
|
27
|
+
const keyMatch = /^([A-Za-z][A-Za-z0-9_-]*):[ \t]*(.*)$/.exec(line);
|
|
28
|
+
if (keyMatch === null)
|
|
29
|
+
continue;
|
|
30
|
+
if (keyMatch[1] === 'name')
|
|
31
|
+
name = unquoteYamlScalar(keyMatch[2]);
|
|
32
|
+
else if (keyMatch[1] === 'description')
|
|
33
|
+
description = unquoteYamlScalar(keyMatch[2]);
|
|
34
|
+
}
|
|
35
|
+
// legacy regex 解析已由逐行 YAML 扫描替代
|
|
36
|
+
// legacy description regex 已由逐行 YAML 扫描替代
|
|
37
|
+
// name 已在逐行扫描中解析
|
|
38
|
+
// description 已在逐行扫描中解析
|
|
39
|
+
return { name, description, content: normalized.slice(match[0].length).trimStart() };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 把技能注册进 DSH 技能注册表。技能文件缺失或 frontmatter 不完整只告警,
|
|
43
|
+
* 绝不弄崩宿主启动。
|
|
44
|
+
*/
|
|
45
|
+
export function registerPptSkill(ctx) {
|
|
46
|
+
const skillName = SKILL_NAMES[0];
|
|
47
|
+
const dir = join(bundledSkillsDir(), skillName);
|
|
48
|
+
const text = readFileSync(join(dir, 'SKILL.md'), 'utf8');
|
|
49
|
+
const parsed = parseSkillFile(text);
|
|
50
|
+
if (parsed.name === '' || parsed.description === '' || parsed.content === '') {
|
|
51
|
+
throw new Error('技能 ' + skillName + ' 的 frontmatter 不完整(name/description/content 均不能为空)');
|
|
52
|
+
}
|
|
53
|
+
return ctx.skills.register({
|
|
54
|
+
name: parsed.name,
|
|
55
|
+
description: parsed.description,
|
|
56
|
+
content: parsed.content,
|
|
57
|
+
resourceBase: { kind: 'directory', path: dir },
|
|
58
|
+
source: 'runtime',
|
|
59
|
+
});
|
|
60
|
+
}
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** dsh-ppt 的公共类型。 */
|
|
2
|
+
export type PptThemeId = 'swiss' | 'velvet' | 'data' | 'soft' | 'bold';
|
|
3
|
+
export type PptLanguage = 'zh' | 'en' | 'bilingual';
|
|
4
|
+
export type PptSlideLayout = 'cover' | 'section' | 'bullets' | 'statement' | 'closing';
|
|
5
|
+
export interface PptSlideSpec {
|
|
6
|
+
layout?: PptSlideLayout;
|
|
7
|
+
title?: string;
|
|
8
|
+
subtitle?: string;
|
|
9
|
+
kicker?: string;
|
|
10
|
+
text?: string;
|
|
11
|
+
bullets?: string[];
|
|
12
|
+
}
|
|
13
|
+
export interface PptConfig {
|
|
14
|
+
/** 默认输出目录;调用 ppt_create 时可用 outputDir 覆盖。默认当前工作目录。 */
|
|
15
|
+
outputDir?: string;
|
|
16
|
+
/** 单次生成幻灯片上限,默认 60(3–120)。 */
|
|
17
|
+
maxSlides?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface PptCreateArgs {
|
|
20
|
+
title: string;
|
|
21
|
+
/** Markdown 正文:一句话、一段文字或整篇文档(与 slides 二选一,推荐)。 */
|
|
22
|
+
content?: string;
|
|
23
|
+
/** 结构化幻灯片(高级用法,与 content 二选一)。 */
|
|
24
|
+
slides?: PptSlideSpec[];
|
|
25
|
+
theme?: string;
|
|
26
|
+
lang?: string;
|
|
27
|
+
outputDir?: string;
|
|
28
|
+
fileName?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface PptThemeInfo {
|
|
31
|
+
id: string;
|
|
32
|
+
name: string;
|
|
33
|
+
mood: string;
|
|
34
|
+
bestFor: string;
|
|
35
|
+
dark: boolean;
|
|
36
|
+
palette: Record<string, string>;
|
|
37
|
+
fonts: Record<string, string>;
|
|
38
|
+
}
|
|
39
|
+
export interface PptThemesResult {
|
|
40
|
+
ok: boolean;
|
|
41
|
+
themes: PptThemeInfo[];
|
|
42
|
+
}
|
|
43
|
+
export interface PptCreateResult {
|
|
44
|
+
ok: boolean;
|
|
45
|
+
title: string;
|
|
46
|
+
theme: string;
|
|
47
|
+
language: string;
|
|
48
|
+
slideCount: number;
|
|
49
|
+
outputDir: string;
|
|
50
|
+
files: {
|
|
51
|
+
html: string;
|
|
52
|
+
pptx: string;
|
|
53
|
+
json: string;
|
|
54
|
+
};
|
|
55
|
+
htmlPath: string;
|
|
56
|
+
pptxPath: string;
|
|
57
|
+
jsonPath: string;
|
|
58
|
+
}
|
|
59
|
+
/** deck-core.mjs 暴露给插件的最小面。 */
|
|
60
|
+
export interface DeckEngine {
|
|
61
|
+
buildDeck(options: Record<string, unknown>): PptCreateResult;
|
|
62
|
+
listThemes(lang?: string): PptThemeInfo[];
|
|
63
|
+
resolveTheme(input: unknown): Record<string, unknown>;
|
|
64
|
+
resolveLanguage(input: unknown): Record<string, unknown>;
|
|
65
|
+
}
|
package/lib/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-ppt",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "DSH 演示文稿技能 + 工具插件:一句话或一篇 Markdown 文档 → 完整演示文稿(独立 HTML 网页放映 + 可编辑 PPTX 导出),内置 5 套视觉主题,中英双语,SKILL.md 双格式跨 harness。",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "lib/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/index.d.ts",
|
|
11
|
+
"default": "./lib/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./skills/*": "./skills/*",
|
|
14
|
+
"./deck-core": "./skills/dsh-ppt/scripts/deck-core.mjs",
|
|
15
|
+
"./build-deck": "./skills/dsh-ppt/scripts/build-deck.mjs",
|
|
16
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"lib",
|
|
21
|
+
"skills",
|
|
22
|
+
"cordis.patch.yml",
|
|
23
|
+
"README.md",
|
|
24
|
+
"README.en.md",
|
|
25
|
+
"CHANGELOG.md",
|
|
26
|
+
"LICENSE"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc -p tsconfig.json",
|
|
30
|
+
"prepare": "tsc -p tsconfig.json",
|
|
31
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
32
|
+
"test": "pnpm run build && node --test \"test/*.test.mjs\"",
|
|
33
|
+
"test:core": "node --test \"test/*.test.mjs\"",
|
|
34
|
+
"smoke:cli": "node skills/dsh-ppt/scripts/build-deck.mjs --title \"DSH PPT Smoke\" --content @smoke/sample-deck.md --theme data --lang zh --out .smoke-deck"
|
|
35
|
+
},
|
|
36
|
+
"dsh": {
|
|
37
|
+
"bundle": {
|
|
38
|
+
"patch": "./cordis.patch.yml"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"dsh",
|
|
43
|
+
"deepseek-harness",
|
|
44
|
+
"dsh-plugin",
|
|
45
|
+
"plugin",
|
|
46
|
+
"skills",
|
|
47
|
+
"ppt",
|
|
48
|
+
"pptx",
|
|
49
|
+
"presentation",
|
|
50
|
+
"slides",
|
|
51
|
+
"html"
|
|
52
|
+
],
|
|
53
|
+
"license": "MIT",
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=20"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/node": "^24.0.0",
|
|
59
|
+
"typescript": "^5.6.0"
|
|
60
|
+
},
|
|
61
|
+
"repository": {
|
|
62
|
+
"type": "git",
|
|
63
|
+
"url": "git+https://github.com/STARDUSTLC666/dsh-ppt.git"
|
|
64
|
+
},
|
|
65
|
+
"bugs": {
|
|
66
|
+
"url": "https://github.com/STARDUSTLC666/dsh-ppt/issues"
|
|
67
|
+
},
|
|
68
|
+
"homepage": "https://github.com/STARDUSTLC666/dsh-ppt#readme",
|
|
69
|
+
"author": "stardustlc",
|
|
70
|
+
"packageManager": "pnpm@11.7.0"
|
|
71
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dsh-ppt
|
|
3
|
+
description: "Turn one sentence or a Markdown document into a complete, presentation-ready deck: a standalone HTML web slideshow plus an editable PPTX export, with 5 built-in visual themes (Swiss Pulse / Velvet Standard / Data Drift / Soft Signal / Maximalist Type) and bilingual Chinese/English support. Use when the user asks for a PPT, slides, deck, keynote, 演示文稿, 幻灯片, 汇报, 提案, 路演, 培训材料, or wants to turn a document into a presentation. 中文:把一句话或一篇文档变成完整演示文稿(HTML 网页放映 + PPTX 导出),内置 5 套视觉主题,中英双语。"
|
|
4
|
+
compatibility: "DSH plugin runs in-process with zero runtime dependencies. The standalone CLI needs Node.js >= 20. Cross-harness: copy this skill directory into any Agent Skills directory."
|
|
5
|
+
allowed-tools: "Bash, Read, Write, Edit"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# dsh-ppt
|
|
9
|
+
|
|
10
|
+
一句话或一篇文档 → 完整演示文稿:**独立 HTML 网页放映 + 可编辑 PPTX**。这是从内容到成品的完整 SOP;DSH 内安装插件后调用 `ppt_create` / `ppt_themes` 工具,其他 harness 复制本目录并运行 `scripts/build-deck.mjs`。
|
|
11
|
+
|
|
12
|
+
## 触发条件
|
|
13
|
+
|
|
14
|
+
- 用户要求做 PPT、幻灯片、演示文稿、deck、keynote、汇报、提案、路演、培训材料。
|
|
15
|
+
- 用户给了一句话、一段文字或一篇 Markdown 文档,希望变成可放映/可分享的演示。
|
|
16
|
+
- 用户提到导出 PPTX 或网页版幻灯片。
|
|
17
|
+
|
|
18
|
+
## 核心流水线(每次执行)
|
|
19
|
+
|
|
20
|
+
1. **Probe 需求**:读入用户的句子/文档。确定听众、目标(说服/汇报/教学/发布)、语气与情绪基调。一句话输入不要反问,直接按发布场景推断并产出完整内容。
|
|
21
|
+
2. **Outline 大纲**:产出 5–12 页大纲。每页只讲一个观点;页型组合固定为:封面 → 问题/背景 → 方案/论点(3–6 页)→ 证据/案例 → 行动号召 → 结束页。每页要点不超过 6 条,每条不超过 20 字(中文)或 12 词(英文)。
|
|
22
|
+
3. **Theme 主题**:调用 `ppt_themes`(DSH)或 `node scripts/build-deck.mjs --list-themes`(跨 harness)选择视觉主题。情绪 → 主题映射见 `references/themes.md`;默认 `data`(技术/AI),高管汇报用 `velvet`,数据报表用 `swiss`,温暖叙事用 `soft`,大声量发布用 `bold`。
|
|
23
|
+
4. **Build 生成**:
|
|
24
|
+
- DSH 内:调用 `ppt_create`,把大纲写成 Markdown 传 `content`(推荐),或直接传结构化 `slides`。
|
|
25
|
+
- 跨 harness:`node <skill-dir>/scripts/build-deck.mjs --title "标题" --content deck.md --theme data --lang zh --out dist`
|
|
26
|
+
- 输出三件套:`*.html`(双击即放映)、`*.pptx`(可编辑)、`*.json`(manifest)。
|
|
27
|
+
5. **Verify 校验**:确认 HTML 页数与大纲一致、标题/要点无截断;PPTX 文件头为 `PK`(zip)。DSH 内用文件工具读取确认;跨 harness 用 `unzip -l deck.pptx | grep presentation.xml` 或 PowerShell `Expand-Archive` 抽查。
|
|
28
|
+
6. **Deliver 交付**:给用户三个绝对路径,并说明:HTML 浏览器直接打开(方向键翻页、F 全屏、G 总览、P 打印/另存 PDF);PPTX 用 PowerPoint / WPS / Keynote 打开。
|
|
29
|
+
|
|
30
|
+
## 内容写作规则
|
|
31
|
+
|
|
32
|
+
- 一句话输入也要「完整」:封面 = 标题 + 那句话;核心观点页 = 原句放大;结束页 = 谢谢/行动号召。若用户要求更丰富,先自行扩写成 6–10 页大纲再构建。
|
|
33
|
+
- 一页一个观点;标题是判断句,不是名词短语。
|
|
34
|
+
- 正文用短句;先结论后理由;数字和对比比形容词更有说服力。
|
|
35
|
+
- 中英双语:`lang` 只决定界面文字(页码/主题标签/结束页),**内容语言由你撰写**。要求双语时,优先每页中文标题 + 英文副标题,或直接生成两份 deck(`--lang zh` 与 `--lang en`)。
|
|
36
|
+
- 更多规则见 `references/copywriting.md`。
|
|
37
|
+
|
|
38
|
+
## 主题选择
|
|
39
|
+
|
|
40
|
+
内置 5 套主题(源自 hyperframes 视觉风格库):
|
|
41
|
+
|
|
42
|
+
| 主题 ID | 名称 | 情绪 | 适用 |
|
|
43
|
+
| --- | --- | --- | --- |
|
|
44
|
+
| `data` | 数据漂移 | 未来/沉浸 | AI、技术发布、研究(默认) |
|
|
45
|
+
| `swiss` | 瑞士脉冲 | 精准/理性 | 数据、SaaS、开发者工具 |
|
|
46
|
+
| `velvet` | 天鹅绒标准 | 高级/克制 | 高管汇报、品牌、融资路演 |
|
|
47
|
+
| `soft` | 柔和信号 | 温暖/人本 | 品牌故事、培训、个人分享 |
|
|
48
|
+
| `bold` | 极繁大字 | 大声/动能 | 产品发布、活动、大事件 |
|
|
49
|
+
|
|
50
|
+
完整色板与反模式见 `references/themes.md`。
|
|
51
|
+
|
|
52
|
+
## 视觉引擎复用(hyperframes)
|
|
53
|
+
|
|
54
|
+
如果 `hyperframes` 技能已安装,生成前先读它的 `visual-styles.md` / `house-style.md` 作为视觉判断依据;本技能的 5 套主题就是把其中 Swiss Pulse / Velvet Standard / Data Drift / Soft Signal / Maximalist Type 五套风格落成确定性 HTML+PPTX 实现。需要高级动画视频版时,才转用 hyperframes;普通演示不要上视频引擎。
|
|
55
|
+
|
|
56
|
+
## 结构化 slides(高级用法)
|
|
57
|
+
|
|
58
|
+
`ppt_create` 的 `slides` 与 CLI `--slides` 接受 JSON 数组:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
[
|
|
62
|
+
{ "layout": "cover", "title": "标题", "subtitle": "副标题", "kicker": "开场" },
|
|
63
|
+
{ "layout": "section", "kicker": "01", "title": "背景" },
|
|
64
|
+
{ "layout": "bullets", "title": "三个论点", "bullets": ["论点一", "论点二", "论点三"] },
|
|
65
|
+
{ "layout": "statement", "title": "核心观点一句话", "subtitle": "支撑说明" },
|
|
66
|
+
{ "layout": "closing", "title": "谢谢", "subtitle": "行动号召" }
|
|
67
|
+
]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`layout` 仅限 `cover | section | bullets | statement | closing`。Markdown 输入不够精确时,用结构化 slides 重写。
|
|
71
|
+
|
|
72
|
+
## 质量门禁(交付前逐项确认)
|
|
73
|
+
|
|
74
|
+
- [ ] 页数 3–60,首屏是封面,末屏是结束页。
|
|
75
|
+
- [ ] 每页有明确标题;bullets 页 1–8 条。
|
|
76
|
+
- [ ] HTML 与 PPTX 使用同一主题,配色一致。
|
|
77
|
+
- [ ] 主题色对比度足够(深底浅字或浅底深字)。
|
|
78
|
+
- [ ] 中文内容无错别字;英文标题用 Title Case。
|
|
79
|
+
- [ ] 文件已写到用户工作区,路径为绝对路径。
|
|
80
|
+
|
|
81
|
+
## 故障排查
|
|
82
|
+
|
|
83
|
+
| 症状 | 处理 |
|
|
84
|
+
| --- | --- |
|
|
85
|
+
| `ppt_create` 不存在 | 本插件未安装;用 `node <skill-dir>/scripts/build-deck.mjs` 裸 CLI 生成同样三件套 |
|
|
86
|
+
| 未知主题 | `ppt_themes` 或 `--list-themes` 看可用 ID;不要猜 |
|
|
87
|
+
| 内容超过 60 页 | 合并论点,或拆成多个 deck;`maxSlides` 默认 60 |
|
|
88
|
+
| PPTX 打不开 | 确认文件完整(zip 头 `PK`);Office 首次打开空白版式属正常,编辑视图可用 |
|
|
89
|
+
| 双语界面 | `lang: bilingual` 只双语化界面;内容双语由 agent 在写作阶段完成 |
|
|
90
|
+
|
|
91
|
+
## 跨 harness 安装
|
|
92
|
+
|
|
93
|
+
把整个 `dsh-ppt` 技能目录复制到目标 agent 的技能目录即可(只依赖 Node 20+):
|
|
94
|
+
|
|
95
|
+
| Agent | 技能目录 |
|
|
96
|
+
| --- | --- |
|
|
97
|
+
| DeepSeek Harness | `dsh plugin --profile web add dsh-ppt` |
|
|
98
|
+
| Claude Code | `~/.claude/skills/` |
|
|
99
|
+
| Cursor | `.cursor/skills/` |
|
|
100
|
+
| Gemini CLI | `~/.gemini/skills/` |
|
|
101
|
+
| OpenAI Codex | `~/.codex/skills/` |
|
|
102
|
+
|
|
103
|
+
一次编写,处处可用。
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# dsh-ppt 幻灯片写作规则(中英双语)
|
|
2
|
+
|
|
3
|
+
## 结构模板
|
|
4
|
+
|
|
5
|
+
任何 deck 都遵循同一叙事骨架:
|
|
6
|
+
|
|
7
|
+
1. **封面**:标题(判断句或名词短语 + 价值承诺)+ 副标题(一句话定位)。
|
|
8
|
+
2. **背景/问题**:现状 → 痛点 → 为什么现在。
|
|
9
|
+
3. **方案/论点**:每个论点一页,先结论后理由。
|
|
10
|
+
4. **证据/案例**:数字、对比、客户/数据点。
|
|
11
|
+
5. **行动号召**:希望听众下一步做什么。
|
|
12
|
+
6. **结束页**:谢谢 + 联系方式或 CTA 重复。
|
|
13
|
+
|
|
14
|
+
一句话输入的最小完整结构:封面 → 核心观点页 → 结束页。
|
|
15
|
+
|
|
16
|
+
## 单页规则
|
|
17
|
+
|
|
18
|
+
- 一个页面只讲一个观点。
|
|
19
|
+
- 标题是完整的判断句,例如「AI 客服把首响时间压缩到 8 秒」,而不是「关于客服」。
|
|
20
|
+
- bullets 1–8 条,中文每条 ≤ 20 字,英文每条 ≤ 12 词。
|
|
21
|
+
- 优先使用数字、对比、动词;避免「赋能、抓手、闭环」等空词。
|
|
22
|
+
- 最后一页要给出可执行的 CTA,不是只写「谢谢」。
|
|
23
|
+
|
|
24
|
+
## 双语规则
|
|
25
|
+
|
|
26
|
+
- 插件 `lang` 参数只控制界面(页码/标签/结束页默认文案);**内容双语由写作阶段完成**。
|
|
27
|
+
- 双语 deck 推荐两种做法:
|
|
28
|
+
1. 同一页内:中文标题 + 英文副标题;bullets 中文为主,关键词括号英文。
|
|
29
|
+
2. 两份 deck:同一大纲分别以 `zh` 与 `en` 生成,主题保持一致。
|
|
30
|
+
- 英文标题用 Title Case;术语首现给中文+英文,如「检索增强生成(RAG)」。
|
|
31
|
+
- 双语时不要逐句直译,按目标语言习惯重写。
|
|
32
|
+
|
|
33
|
+
## Markdown 输入规范
|
|
34
|
+
|
|
35
|
+
`ppt_create` 与 `build-deck.mjs` 按以下规则解析 Markdown:
|
|
36
|
+
|
|
37
|
+
- 第一个 `# 标题` 会成为封面标题;其后的段落/列表成为封面副标题来源。
|
|
38
|
+
- 每个 `## 小节`(或更深标题)生成一页:
|
|
39
|
+
- 有 `- ` / `* ` / `1.` 列表 → `bullets` 页;
|
|
40
|
+
- 无列表且无正文 → `section` 过渡页;
|
|
41
|
+
- 有正文段落 → 正文拆句后作为 bullets。
|
|
42
|
+
- 没有标题的纯文本:第一段第一句为封面副标题,后续段落按 5 句一组拆成要点页。
|
|
43
|
+
- 只有一句话:自动生成「封面 → 核心观点 → 结束页」三页。
|
|
44
|
+
- 需要更精细控制时使用结构化 `slides` JSON,不要和 Markdown 混用。
|
|
45
|
+
|
|
46
|
+
## 示例
|
|
47
|
+
|
|
48
|
+
输入:
|
|
49
|
+
|
|
50
|
+
```markdown
|
|
51
|
+
# 让会议少一半
|
|
52
|
+
|
|
53
|
+
用异步决策替代同步例会。
|
|
54
|
+
|
|
55
|
+
## 问题
|
|
56
|
+
- 工程师每周 6 小时在例会上
|
|
57
|
+
- 决策没有记录,反复讨论
|
|
58
|
+
|
|
59
|
+
## 方案
|
|
60
|
+
- 会前异步文档 + 24h 评论期
|
|
61
|
+
- 只有分歧项才开会
|
|
62
|
+
|
|
63
|
+
## 效果
|
|
64
|
+
- 周会从 4 次降到 1 次
|
|
65
|
+
- 决策记录 100% 沉淀
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
产出:封面 → 问题 → 方案 → 效果 → 结束页,共 5 页,主题可选 `swiss`(数据/效率)或 `soft`(团队协作)。
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# dsh-ppt 内置主题参考
|
|
2
|
+
|
|
3
|
+
主题系统移植自 hyperframes 技能的 `visual-styles.md` 视觉风格库。每套主题在 HTML 与 PPTX 中使用同一色板、同一字体族、同一情绪规则。
|
|
4
|
+
|
|
5
|
+
## 速查
|
|
6
|
+
|
|
7
|
+
| ID | 中文名 | English | 明暗 | 底色 | 前景 | 主强调 | 次强调 | 情绪 | 适用 |
|
|
8
|
+
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
|
9
|
+
| `data` | 数据漂移 | Data Drift | 暗 | `#070B14` | `#E8F1FF` | `#7C3AED` | `#06B6D4` | 未来/沉浸 | AI、数据、研究、技术发布 |
|
|
10
|
+
| `swiss` | 瑞士脉冲 | Swiss Pulse | 暗 | `#10151B` | `#F5F7FA` | `#2F6BFF` | `#FFB300` | 精准/理性 | SaaS、数据、开发者工具 |
|
|
11
|
+
| `velvet` | 天鹅绒标准 | Velvet Standard | 暗 | `#111316` | `#F4EFE6` | `#C9A84C` | `#3D4A63` | 高级/克制 | 高管汇报、品牌、融资路演 |
|
|
12
|
+
| `soft` | 柔和信号 | Soft Signal | 亮 | `#FFF8EC` | `#3B2F2A` | `#E58A2F` | `#8FAF8C` | 温暖/人本 | 品牌故事、培训、个人分享 |
|
|
13
|
+
| `bold` | 极繁大字 | Maximalist Type | 暗 | `#0D0D0D` | `#FFFFFF` | `#E63946` | `#FFD60A` | 大声/动能 | 产品发布、活动、大事件 |
|
|
14
|
+
|
|
15
|
+
## 逐主题说明
|
|
16
|
+
|
|
17
|
+
### data — 数据漂移(默认)
|
|
18
|
+
|
|
19
|
+
- 深黑底 + 电光紫 + 青色,细体未来感。
|
|
20
|
+
- 适合 AI / ML / 数据 / 前沿技术;大标题少字,用对比数字。
|
|
21
|
+
- 反模式:不要加多余暖色;不要堆长段落。
|
|
22
|
+
|
|
23
|
+
### swiss — 瑞士脉冲
|
|
24
|
+
|
|
25
|
+
- 黑白灰 + 一个电蓝(或琥珀),栅格感、大数字。
|
|
26
|
+
- 适合 SaaS / 指标 / 开发者工具;标题短、要点像 spec。
|
|
27
|
+
- 反模式:不要装饰性渐变文字;不要圆角卡片堆叠。
|
|
28
|
+
|
|
29
|
+
### velvet — 天鹅绒标准
|
|
30
|
+
|
|
31
|
+
- 黑、米白、香槟金,衬线标题 + 无衬线正文,宽字距,留白多。
|
|
32
|
+
- 适合高管汇报 / 品牌 / 融资;节奏慢,页数少而精。
|
|
33
|
+
- 反模式:不要动画感强烈的颜色;不要超过一个强调色。
|
|
34
|
+
|
|
35
|
+
### soft — 柔和信号
|
|
36
|
+
|
|
37
|
+
- 暖奶油底、深棕字、琥珀 + 鼠尾草绿,亮色唯一浅底主题。
|
|
38
|
+
- 适合品牌故事 / 培训 / 个人分享;文字可以更口语。
|
|
39
|
+
- 反模式:不要用纯黑正文;不要高饱和撞色。
|
|
40
|
+
|
|
41
|
+
### bold — 极繁大字
|
|
42
|
+
|
|
43
|
+
- 黑底、正红 + 明黄,超粗标题,字就是视觉主体。
|
|
44
|
+
- 适合产品发布 / 活动 / 大事件;短标题、单页单一冲击。
|
|
45
|
+
- 反模式:不要把页面塞满要点;不要让红黄之外再出现第三种强调色。
|
|
46
|
+
|
|
47
|
+
## 选主题决策树
|
|
48
|
+
|
|
49
|
+
1. 情绪是未来/科技 → `data`
|
|
50
|
+
2. 情绪是理性/数据/精准 → `swiss`
|
|
51
|
+
3. 情绪是高级/信任/正式 → `velvet`
|
|
52
|
+
4. 情绪是温暖/人本/故事 → `soft`
|
|
53
|
+
5. 情绪是大音量/发布会 → `bold`
|
|
54
|
+
|
|
55
|
+
无法判断时用 `data`,不要自造主题 ID。
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#! /usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* build-deck.mjs —— dsh-ppt 的跨 harness 裸 CLI。
|
|
4
|
+
*
|
|
5
|
+
* DSH 插件内请优先使用 ppt_create 工具;把 skills/dsh-ppt 目录复制到
|
|
6
|
+
* Claude Code / Cursor / Gemini CLI / Codex 等 agent 时,用本脚本生成三件套。
|
|
7
|
+
*
|
|
8
|
+
* 示例:
|
|
9
|
+
* node build-deck.mjs --title "产品发布" --content "deck.md" --theme data --lang zh --out dist/deck
|
|
10
|
+
* node build-deck.mjs --title "Pitch" --content "一句话介绍我们的产品。" --theme velvet --lang bilingual
|
|
11
|
+
* node build-deck.mjs --list-themes
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFileSync } from 'node:fs'
|
|
15
|
+
import { resolve as resolvePath } from 'node:path'
|
|
16
|
+
import { pathToFileURL } from 'node:url'
|
|
17
|
+
import { buildDeck, listThemes, THEME_IDS, DEFAULT_THEME } from './deck-core.mjs'
|
|
18
|
+
|
|
19
|
+
const HELP = `dsh-ppt v0.1.0 —— 一句话 / 一篇文档 → HTML 放映 + PPTX 导出
|
|
20
|
+
|
|
21
|
+
用法:
|
|
22
|
+
node build-deck.mjs --title <标题> --content <Markdown 或文件路径> [选项]
|
|
23
|
+
|
|
24
|
+
选项:
|
|
25
|
+
--title <text> 演示文稿标题(必填)
|
|
26
|
+
--content <text|@path> Markdown 正文;或文件路径(自动读取);或 @- 从 stdin 读
|
|
27
|
+
--slides <json> 结构化 slides JSON(可选,与 --content 二选一)
|
|
28
|
+
--theme <id> 视觉主题:${THEME_IDS.join(' / ')}(默认 ${DEFAULT_THEME})
|
|
29
|
+
--lang <id> 界面语言:zh / en / bilingual(默认 zh)
|
|
30
|
+
--out <dir> 输出目录(默认当前目录)
|
|
31
|
+
--file <name> 文件名前缀(默认取标题)
|
|
32
|
+
--list-themes 列出内置主题
|
|
33
|
+
--help 显示本帮助
|
|
34
|
+
|
|
35
|
+
输出:
|
|
36
|
+
<file>.html 独立网页放映(无外链,可直接双击打开)
|
|
37
|
+
<file>.pptx 可编辑 PPTX(16:9)
|
|
38
|
+
<file>.json deck manifest
|
|
39
|
+
`
|
|
40
|
+
|
|
41
|
+
export function parseArgv(argv) {
|
|
42
|
+
const args = { content: '', slides: null, lang: 'zh', theme: DEFAULT_THEME, out: '.', file: '', title: '' }
|
|
43
|
+
const positional = []
|
|
44
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
45
|
+
const raw = String(argv[i])
|
|
46
|
+
const eq = raw.indexOf('=')
|
|
47
|
+
const key = eq >= 0 ? raw.slice(0, eq) : raw
|
|
48
|
+
const inline = eq >= 0 ? raw.slice(eq + 1) : null
|
|
49
|
+
const nextValue = () => {
|
|
50
|
+
if (inline !== null) return inline
|
|
51
|
+
if (i + 1 >= argv.length) throw new Error('dsh-ppt CLI:' + key + ' 需要一个值')
|
|
52
|
+
i += 1
|
|
53
|
+
return String(argv[i])
|
|
54
|
+
}
|
|
55
|
+
if (key === '--help' || key === '-h') args.help = true
|
|
56
|
+
else if (key === '--list-themes') args.listThemes = true
|
|
57
|
+
else if (key === '--title') args.title = nextValue()
|
|
58
|
+
else if (key === '--content') args.content = nextValue()
|
|
59
|
+
else if (key === '--slides') args.slides = JSON.parse(nextValue())
|
|
60
|
+
else if (key === '--theme') args.theme = nextValue()
|
|
61
|
+
else if (key === '--lang') args.lang = nextValue()
|
|
62
|
+
else if (key === '--out') args.out = nextValue()
|
|
63
|
+
else if (key === '--file') args.file = nextValue()
|
|
64
|
+
else positional.push(raw)
|
|
65
|
+
}
|
|
66
|
+
args.positional = positional
|
|
67
|
+
return args
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function readStdin(stream) {
|
|
71
|
+
let text = ''
|
|
72
|
+
stream.setEncoding('utf8')
|
|
73
|
+
for await (const chunk of stream) text += chunk
|
|
74
|
+
return text
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function main(argv = process.argv.slice(2), io = {}) {
|
|
78
|
+
const log = io.log ?? ((message) => console.log(message))
|
|
79
|
+
const args = parseArgv(argv)
|
|
80
|
+
|
|
81
|
+
if (args.help) {
|
|
82
|
+
log(HELP.trim())
|
|
83
|
+
return 0
|
|
84
|
+
}
|
|
85
|
+
if (args.listThemes) {
|
|
86
|
+
for (const theme of listThemes('zh')) {
|
|
87
|
+
log('- ' + theme.id.padEnd(8) + theme.name + '|' + theme.mood + '|适合:' + theme.bestFor)
|
|
88
|
+
}
|
|
89
|
+
return 0
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let content = String(args.content ?? '')
|
|
93
|
+
let slides = args.slides
|
|
94
|
+
if (content === '@-') {
|
|
95
|
+
content = await readStdin(io.stdin ?? process.stdin)
|
|
96
|
+
} else if (content.startsWith('@')) {
|
|
97
|
+
content = readFileSync(resolvePath(content.slice(1)), 'utf8')
|
|
98
|
+
} else if (content.trim() === '' && slides === null) {
|
|
99
|
+
throw new Error('dsh-ppt CLI:--content 不能为空(或用 --slides 传结构化幻灯片)')
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const options = {
|
|
103
|
+
title: args.title,
|
|
104
|
+
content,
|
|
105
|
+
slides,
|
|
106
|
+
theme: args.theme,
|
|
107
|
+
lang: args.lang,
|
|
108
|
+
outputDir: resolvePath(args.out || '.'),
|
|
109
|
+
fileName: args.file,
|
|
110
|
+
}
|
|
111
|
+
const result = buildDeck(options)
|
|
112
|
+
log('dsh-ppt 已生成 ' + result.slideCount + ' 页演示文稿:')
|
|
113
|
+
log(' HTML 放映:' + result.htmlPath)
|
|
114
|
+
log(' PPTX 导出:' + result.pptxPath)
|
|
115
|
+
log(' Manifest :' + result.jsonPath)
|
|
116
|
+
return 0
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
|
|
120
|
+
main().then((code) => {
|
|
121
|
+
process.exitCode = code ?? 0
|
|
122
|
+
}).catch((err) => {
|
|
123
|
+
console.error('[dsh-ppt] ' + (err instanceof Error ? err.message : String(err)))
|
|
124
|
+
process.exitCode = 1
|
|
125
|
+
})
|
|
126
|
+
}
|