dsh-ppt 0.6.0 → 0.8.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 +40 -14
- package/LICENSE +21 -21
- package/README.en.md +22 -191
- package/README.md +22 -206
- package/cordis.patch.yml +19 -19
- package/docs/CHANGELOG.en.md +29 -0
- package/docs/FEATURE-PLAN-2026-10-01.md +50 -50
- package/docs/LIBREOFFICE-INTEGRATION-2026-10-01.md +69 -69
- package/docs/USAGE.en.md +194 -0
- package/docs/USAGE.md +209 -0
- package/docs/VALIDATION.md +15 -0
- package/docs/validation/0.8.0.md +10 -0
- package/lib/client.js +209 -0
- package/lib/execution.d.ts +1 -0
- package/lib/execution.js +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +15 -1
- package/lib/tools.js +6 -5
- package/lib/types.d.ts +1 -0
- package/lib/workbench.d.ts +32 -0
- package/lib/workbench.js +266 -0
- package/package.json +10 -3
- package/skills/dsh-ppt/SKILL.md +1 -1
- package/skills/dsh-ppt/package.json +11 -11
- package/skills/dsh-ppt/references/advanced.md +1 -1
- package/skills/dsh-ppt/references/copywriting.md +59 -59
- package/skills/dsh-ppt/references/syntax.md +1 -1
- package/skills/dsh-ppt/references/themes.md +61 -61
- package/skills/dsh-ppt/references/troubleshooting.md +1 -1
- package/skills/dsh-ppt/scripts/build-deck.mjs +184 -184
- package/skills/dsh-ppt/scripts/deck-advanced.mjs +17 -4
- package/skills/dsh-ppt/scripts/deck-core.mjs +715 -703
- package/skills/dsh-ppt/scripts/deck-delivery.mjs +104 -0
- package/skills/dsh-ppt/scripts/deck-media.mjs +160 -160
- package/skills/dsh-ppt/scripts/deck-render.mjs +41 -7
|
@@ -1,184 +1,184 @@
|
|
|
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 { existsSync, readFileSync } from 'node:fs'
|
|
15
|
-
import { resolve as resolvePath } from 'node:path'
|
|
16
|
-
import { pathToFileURL } from 'node:url'
|
|
17
|
-
import { listThemes, THEME_IDS, DEFAULT_THEME } from './deck-core.mjs'
|
|
18
|
-
import { buildDeckAsync, listTemplates, editDeck, undoDeck, checkDeckAsync, renderDeck } from './deck-advanced.mjs'
|
|
19
|
-
|
|
20
|
-
const HELP = `dsh-ppt —— 一句话 / 一篇文档 → HTML 放映 + PPTX 导出
|
|
21
|
-
|
|
22
|
-
用法:
|
|
23
|
-
node build-deck.mjs --title <标题> --content <Markdown 或文件路径> [选项]
|
|
24
|
-
|
|
25
|
-
选项:
|
|
26
|
-
--title <text> 演示文稿标题(必填)
|
|
27
|
-
--content <text|@path> Markdown 正文;或文件路径(自动读取);或 @- 从 stdin 读
|
|
28
|
-
--slides <json> 结构化 slides JSON(可选,与 --content 二选一)
|
|
29
|
-
--template <id> weekly / defense / project / pitch;仅模板会生成草稿
|
|
30
|
-
--brand <json|@path> 品牌颜色、字体、Logo、页脚
|
|
31
|
-
--edit <deck.json> 修改指定页,配合 --edits <json|@path>
|
|
32
|
-
--undo <deck.json> 撤销最近一次修改
|
|
33
|
-
--check <deck.json> 查看质量报告和页码 / ID
|
|
34
|
-
--render <deck.pptx> 用可选官方引擎生成逐页 PNG / PDF
|
|
35
|
-
--format <png|pdf|both> 渲染格式,默认 png
|
|
36
|
-
--width <n> PNG 目标宽度,默认 1440
|
|
37
|
-
--timeout <ms> 渲染整体期限,默认 120000 毫秒
|
|
38
|
-
--render-receipt <path> 配合 --check 核验当前渲染收据
|
|
39
|
-
--revision <n> 编辑 / 撤销预期修订号,防止覆盖他人修改
|
|
40
|
-
--list-templates 列出场景结构参考
|
|
41
|
-
--theme <id> 视觉主题:${THEME_IDS.join(' / ')}(默认 ${DEFAULT_THEME})
|
|
42
|
-
--lang <id> 界面语言:zh / en / bilingual(默认 zh)
|
|
43
|
-
--motion <on|off> 页间转场与要点入场动画(默认 on;off 产出纯静态)
|
|
44
|
-
--out <dir> 输出目录(默认当前目录)
|
|
45
|
-
--file <name> 文件名前缀(默认取标题)
|
|
46
|
-
--overwrite 显式覆盖同名三件套(默认自动追加 -1/-2…)
|
|
47
|
-
--list-themes 列出内置主题
|
|
48
|
-
--help 显示本帮助
|
|
49
|
-
|
|
50
|
-
输出:
|
|
51
|
-
<file>.html 独立网页放映(无外链,可直接双击打开)
|
|
52
|
-
<file>.pptx 可编辑 PPTX(16:9)
|
|
53
|
-
<file>.json deck manifest
|
|
54
|
-
`
|
|
55
|
-
|
|
56
|
-
export function parseArgv(argv) {
|
|
57
|
-
const args = { content: '', slides: null, lang: 'zh', theme: undefined, motion: undefined, out: '.', file: '', title: '', overwrite: false }
|
|
58
|
-
const positional = []
|
|
59
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
60
|
-
const raw = String(argv[i])
|
|
61
|
-
const eq = raw.indexOf('=')
|
|
62
|
-
const key = eq >= 0 ? raw.slice(0, eq) : raw
|
|
63
|
-
const inline = eq >= 0 ? raw.slice(eq + 1) : null
|
|
64
|
-
const nextValue = () => {
|
|
65
|
-
if (inline !== null) return inline
|
|
66
|
-
if (i + 1 >= argv.length) throw new Error('dsh-ppt CLI:' + key + ' 需要一个值')
|
|
67
|
-
i += 1
|
|
68
|
-
return String(argv[i])
|
|
69
|
-
}
|
|
70
|
-
if (key === '--help' || key === '-h') args.help = true
|
|
71
|
-
else if (key === '--list-themes') args.listThemes = true
|
|
72
|
-
else if (key === '--list-templates') args.listTemplates = true
|
|
73
|
-
else if (key === '--overwrite') args.overwrite = true
|
|
74
|
-
else if (key === '--title') args.title = nextValue()
|
|
75
|
-
else if (key === '--content') args.content = nextValue()
|
|
76
|
-
else if (key === '--slides') args.slides = readJson(nextValue())
|
|
77
|
-
else if (key === '--template') args.template = nextValue()
|
|
78
|
-
else if (key === '--brand') args.brand = readJson(nextValue())
|
|
79
|
-
else if (key === '--edit') args.edit = nextValue()
|
|
80
|
-
else if (key === '--edits') args.edits = readJson(nextValue())
|
|
81
|
-
else if (key === '--undo') args.undo = nextValue()
|
|
82
|
-
else if (key === '--check') args.check = nextValue()
|
|
83
|
-
else if (key === '--render') args.render = nextValue()
|
|
84
|
-
else if (key === '--format') args.format = nextValue()
|
|
85
|
-
else if (key === '--width') args.width = Number(nextValue())
|
|
86
|
-
else if (key === '--timeout') args.timeoutMs = Number(nextValue())
|
|
87
|
-
else if (key === '--render-receipt') args.renderReceipt = nextValue()
|
|
88
|
-
else if (key === '--revision') args.revision = Number(nextValue())
|
|
89
|
-
else if (key === '--theme') args.theme = nextValue()
|
|
90
|
-
else if (key === '--lang') args.lang = nextValue()
|
|
91
|
-
else if (key === '--motion') args.motion = nextValue()
|
|
92
|
-
else if (key === '--out') { args.out = nextValue(); args.explicitOut = true }
|
|
93
|
-
else if (key === '--file') args.file = nextValue()
|
|
94
|
-
else positional.push(raw)
|
|
95
|
-
}
|
|
96
|
-
args.positional = positional
|
|
97
|
-
return args
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function readJson(value) { return JSON.parse(value.startsWith('@') ? readFileSync(resolvePath(value.slice(1)), 'utf8') : value) }
|
|
101
|
-
|
|
102
|
-
async function readStdin(stream) {
|
|
103
|
-
let text = ''
|
|
104
|
-
stream.setEncoding('utf8')
|
|
105
|
-
for await (const chunk of stream) text += chunk
|
|
106
|
-
return text
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export async function main(argv = process.argv.slice(2), io = {}) {
|
|
110
|
-
const log = io.log ?? ((message) => console.log(message))
|
|
111
|
-
const args = parseArgv(argv)
|
|
112
|
-
|
|
113
|
-
if (args.help) {
|
|
114
|
-
log(HELP.trim())
|
|
115
|
-
return 0
|
|
116
|
-
}
|
|
117
|
-
if (args.listThemes) {
|
|
118
|
-
for (const theme of listThemes('zh')) {
|
|
119
|
-
log('- ' + theme.id.padEnd(8) + theme.name + '|' + theme.mood + '|适合:' + theme.bestFor)
|
|
120
|
-
}
|
|
121
|
-
return 0
|
|
122
|
-
}
|
|
123
|
-
if (args.listTemplates) { log(JSON.stringify({ templates: listTemplates(args.lang) }, null, 2)); return 0 }
|
|
124
|
-
if ([args.edit, args.undo, args.check, args.render].filter(Boolean).length > 1) throw new Error('dsh-ppt CLI:--edit / --undo / --check / --render 只能选择一项')
|
|
125
|
-
if (args.renderReceipt !== undefined && !args.check) throw new Error('dsh-ppt CLI:--render-receipt 需配合 --check')
|
|
126
|
-
if (!args.render && [args.format, args.width, args.timeoutMs].some(v => v !== undefined)) throw new Error('dsh-ppt CLI:--format / --width / --timeout 需配合 --render')
|
|
127
|
-
if (args.check) {
|
|
128
|
-
const report = await checkDeckAsync({ deckPath: args.check, renderReceipt: args.renderReceipt })
|
|
129
|
-
log(JSON.stringify(report, null, 2)); return report.ok && (args.renderReceipt === undefined || report.render?.status === 'rendered') ? 0 : 1
|
|
130
|
-
}
|
|
131
|
-
if (args.render) {
|
|
132
|
-
const rendered = await (io.renderDeck ?? renderDeck)({ pptxPath: args.render, ...(args.explicitOut ? { outputDir: args.out } : {}), format: args.format, width: args.width, timeoutMs: args.timeoutMs })
|
|
133
|
-
log(JSON.stringify(rendered, null, 2)); return rendered.ok ? 0 : 1
|
|
134
|
-
}
|
|
135
|
-
if (args.edit || args.undo) {
|
|
136
|
-
const result = args.edit ? await editDeck({ deckPath: args.edit, edits: args.edits, brand: args.brand, expectedRevision: args.revision })
|
|
137
|
-
: await undoDeck({ deckPath: args.undo, expectedRevision: args.revision })
|
|
138
|
-
log(JSON.stringify(result, null, 2)); return 0
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
let content = String(args.content ?? '')
|
|
142
|
-
let slides = args.slides
|
|
143
|
-
if (content === '@-') {
|
|
144
|
-
content = await readStdin(io.stdin ?? process.stdin)
|
|
145
|
-
} else if (content.startsWith('@')) {
|
|
146
|
-
content = readFileSync(resolvePath(content.slice(1)), 'utf8')
|
|
147
|
-
} else if (content.trim() !== '' && existsSync(resolvePath(content.trim()))) {
|
|
148
|
-
// README / SKILL.md 的示例直接传文件路径(不带 @)时也按文件读取
|
|
149
|
-
content = readFileSync(resolvePath(content.trim()), 'utf8')
|
|
150
|
-
} else if (content.trim() === '' && slides === null && !args.template) {
|
|
151
|
-
throw new Error('dsh-ppt CLI:--content 不能为空(或用 --slides 传结构化幻灯片)')
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const options = {
|
|
155
|
-
title: args.title,
|
|
156
|
-
content,
|
|
157
|
-
slides,
|
|
158
|
-
theme: args.theme,
|
|
159
|
-
lang: args.lang,
|
|
160
|
-
motion: args.motion,
|
|
161
|
-
outputDir: resolvePath(args.out || '.'),
|
|
162
|
-
fileName: args.file,
|
|
163
|
-
overwrite: args.overwrite,
|
|
164
|
-
template: args.template,
|
|
165
|
-
brand: args.brand,
|
|
166
|
-
}
|
|
167
|
-
const result = await buildDeckAsync(options)
|
|
168
|
-
log('dsh-ppt 已生成 ' + result.slideCount + ' 页演示文稿:')
|
|
169
|
-
log(' HTML 放映:' + result.htmlPath)
|
|
170
|
-
log(' PPTX 导出:' + result.pptxPath)
|
|
171
|
-
log(' Manifest :' + result.jsonPath)
|
|
172
|
-
if (result.deliveryStatus === 'draft') log(' 草稿:还有待填写内容,不可作为成品交付。')
|
|
173
|
-
log(' 质量检查:' + result.quality.errorCount + ' 个错误 / ' + result.quality.warningCount + ' 个提醒;修订号 ' + result.revision)
|
|
174
|
-
return 0
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
|
|
178
|
-
main().then((code) => {
|
|
179
|
-
process.exitCode = code ?? 0
|
|
180
|
-
}).catch((err) => {
|
|
181
|
-
console.error('[dsh-ppt] ' + (err instanceof Error ? err.message : String(err)))
|
|
182
|
-
process.exitCode = 1
|
|
183
|
-
})
|
|
184
|
-
}
|
|
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 { existsSync, readFileSync } from 'node:fs'
|
|
15
|
+
import { resolve as resolvePath } from 'node:path'
|
|
16
|
+
import { pathToFileURL } from 'node:url'
|
|
17
|
+
import { listThemes, THEME_IDS, DEFAULT_THEME } from './deck-core.mjs'
|
|
18
|
+
import { buildDeckAsync, listTemplates, editDeck, undoDeck, checkDeckAsync, renderDeck } from './deck-advanced.mjs'
|
|
19
|
+
|
|
20
|
+
const HELP = `dsh-ppt —— 一句话 / 一篇文档 → HTML 放映 + PPTX 导出
|
|
21
|
+
|
|
22
|
+
用法:
|
|
23
|
+
node build-deck.mjs --title <标题> --content <Markdown 或文件路径> [选项]
|
|
24
|
+
|
|
25
|
+
选项:
|
|
26
|
+
--title <text> 演示文稿标题(必填)
|
|
27
|
+
--content <text|@path> Markdown 正文;或文件路径(自动读取);或 @- 从 stdin 读
|
|
28
|
+
--slides <json> 结构化 slides JSON(可选,与 --content 二选一)
|
|
29
|
+
--template <id> weekly / defense / project / pitch;仅模板会生成草稿
|
|
30
|
+
--brand <json|@path> 品牌颜色、字体、Logo、页脚
|
|
31
|
+
--edit <deck.json> 修改指定页,配合 --edits <json|@path>
|
|
32
|
+
--undo <deck.json> 撤销最近一次修改
|
|
33
|
+
--check <deck.json> 查看质量报告和页码 / ID
|
|
34
|
+
--render <deck.pptx> 用可选官方引擎生成逐页 PNG / PDF
|
|
35
|
+
--format <png|pdf|both> 渲染格式,默认 png
|
|
36
|
+
--width <n> PNG 目标宽度,默认 1440
|
|
37
|
+
--timeout <ms> 渲染整体期限,默认 120000 毫秒
|
|
38
|
+
--render-receipt <path> 配合 --check 核验当前渲染收据
|
|
39
|
+
--revision <n> 编辑 / 撤销预期修订号,防止覆盖他人修改
|
|
40
|
+
--list-templates 列出场景结构参考
|
|
41
|
+
--theme <id> 视觉主题:${THEME_IDS.join(' / ')}(默认 ${DEFAULT_THEME})
|
|
42
|
+
--lang <id> 界面语言:zh / en / bilingual(默认 zh)
|
|
43
|
+
--motion <on|off> 页间转场与要点入场动画(默认 on;off 产出纯静态)
|
|
44
|
+
--out <dir> 输出目录(默认当前目录)
|
|
45
|
+
--file <name> 文件名前缀(默认取标题)
|
|
46
|
+
--overwrite 显式覆盖同名三件套(默认自动追加 -1/-2…)
|
|
47
|
+
--list-themes 列出内置主题
|
|
48
|
+
--help 显示本帮助
|
|
49
|
+
|
|
50
|
+
输出:
|
|
51
|
+
<file>.html 独立网页放映(无外链,可直接双击打开)
|
|
52
|
+
<file>.pptx 可编辑 PPTX(16:9)
|
|
53
|
+
<file>.json deck manifest
|
|
54
|
+
`
|
|
55
|
+
|
|
56
|
+
export function parseArgv(argv) {
|
|
57
|
+
const args = { content: '', slides: null, lang: 'zh', theme: undefined, motion: undefined, out: '.', file: '', title: '', overwrite: false }
|
|
58
|
+
const positional = []
|
|
59
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
60
|
+
const raw = String(argv[i])
|
|
61
|
+
const eq = raw.indexOf('=')
|
|
62
|
+
const key = eq >= 0 ? raw.slice(0, eq) : raw
|
|
63
|
+
const inline = eq >= 0 ? raw.slice(eq + 1) : null
|
|
64
|
+
const nextValue = () => {
|
|
65
|
+
if (inline !== null) return inline
|
|
66
|
+
if (i + 1 >= argv.length) throw new Error('dsh-ppt CLI:' + key + ' 需要一个值')
|
|
67
|
+
i += 1
|
|
68
|
+
return String(argv[i])
|
|
69
|
+
}
|
|
70
|
+
if (key === '--help' || key === '-h') args.help = true
|
|
71
|
+
else if (key === '--list-themes') args.listThemes = true
|
|
72
|
+
else if (key === '--list-templates') args.listTemplates = true
|
|
73
|
+
else if (key === '--overwrite') args.overwrite = true
|
|
74
|
+
else if (key === '--title') args.title = nextValue()
|
|
75
|
+
else if (key === '--content') args.content = nextValue()
|
|
76
|
+
else if (key === '--slides') args.slides = readJson(nextValue())
|
|
77
|
+
else if (key === '--template') args.template = nextValue()
|
|
78
|
+
else if (key === '--brand') args.brand = readJson(nextValue())
|
|
79
|
+
else if (key === '--edit') args.edit = nextValue()
|
|
80
|
+
else if (key === '--edits') args.edits = readJson(nextValue())
|
|
81
|
+
else if (key === '--undo') args.undo = nextValue()
|
|
82
|
+
else if (key === '--check') args.check = nextValue()
|
|
83
|
+
else if (key === '--render') args.render = nextValue()
|
|
84
|
+
else if (key === '--format') args.format = nextValue()
|
|
85
|
+
else if (key === '--width') args.width = Number(nextValue())
|
|
86
|
+
else if (key === '--timeout') args.timeoutMs = Number(nextValue())
|
|
87
|
+
else if (key === '--render-receipt') args.renderReceipt = nextValue()
|
|
88
|
+
else if (key === '--revision') args.revision = Number(nextValue())
|
|
89
|
+
else if (key === '--theme') args.theme = nextValue()
|
|
90
|
+
else if (key === '--lang') args.lang = nextValue()
|
|
91
|
+
else if (key === '--motion') args.motion = nextValue()
|
|
92
|
+
else if (key === '--out') { args.out = nextValue(); args.explicitOut = true }
|
|
93
|
+
else if (key === '--file') args.file = nextValue()
|
|
94
|
+
else positional.push(raw)
|
|
95
|
+
}
|
|
96
|
+
args.positional = positional
|
|
97
|
+
return args
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function readJson(value) { return JSON.parse(value.startsWith('@') ? readFileSync(resolvePath(value.slice(1)), 'utf8') : value) }
|
|
101
|
+
|
|
102
|
+
async function readStdin(stream) {
|
|
103
|
+
let text = ''
|
|
104
|
+
stream.setEncoding('utf8')
|
|
105
|
+
for await (const chunk of stream) text += chunk
|
|
106
|
+
return text
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function main(argv = process.argv.slice(2), io = {}) {
|
|
110
|
+
const log = io.log ?? ((message) => console.log(message))
|
|
111
|
+
const args = parseArgv(argv)
|
|
112
|
+
|
|
113
|
+
if (args.help) {
|
|
114
|
+
log(HELP.trim())
|
|
115
|
+
return 0
|
|
116
|
+
}
|
|
117
|
+
if (args.listThemes) {
|
|
118
|
+
for (const theme of listThemes('zh')) {
|
|
119
|
+
log('- ' + theme.id.padEnd(8) + theme.name + '|' + theme.mood + '|适合:' + theme.bestFor)
|
|
120
|
+
}
|
|
121
|
+
return 0
|
|
122
|
+
}
|
|
123
|
+
if (args.listTemplates) { log(JSON.stringify({ templates: listTemplates(args.lang) }, null, 2)); return 0 }
|
|
124
|
+
if ([args.edit, args.undo, args.check, args.render].filter(Boolean).length > 1) throw new Error('dsh-ppt CLI:--edit / --undo / --check / --render 只能选择一项')
|
|
125
|
+
if (args.renderReceipt !== undefined && !args.check) throw new Error('dsh-ppt CLI:--render-receipt 需配合 --check')
|
|
126
|
+
if (!args.render && [args.format, args.width, args.timeoutMs].some(v => v !== undefined)) throw new Error('dsh-ppt CLI:--format / --width / --timeout 需配合 --render')
|
|
127
|
+
if (args.check) {
|
|
128
|
+
const report = await checkDeckAsync({ deckPath: args.check, renderReceipt: args.renderReceipt })
|
|
129
|
+
log(JSON.stringify(report, null, 2)); return report.ok && (args.renderReceipt === undefined || report.render?.status === 'rendered') ? 0 : 1
|
|
130
|
+
}
|
|
131
|
+
if (args.render) {
|
|
132
|
+
const rendered = await (io.renderDeck ?? renderDeck)({ pptxPath: args.render, ...(args.explicitOut ? { outputDir: args.out } : {}), format: args.format, width: args.width, timeoutMs: args.timeoutMs })
|
|
133
|
+
log(JSON.stringify(rendered, null, 2)); return rendered.ok ? 0 : 1
|
|
134
|
+
}
|
|
135
|
+
if (args.edit || args.undo) {
|
|
136
|
+
const result = args.edit ? await editDeck({ deckPath: args.edit, edits: args.edits, brand: args.brand, expectedRevision: args.revision })
|
|
137
|
+
: await undoDeck({ deckPath: args.undo, expectedRevision: args.revision })
|
|
138
|
+
log(JSON.stringify(result, null, 2)); return 0
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let content = String(args.content ?? '')
|
|
142
|
+
let slides = args.slides
|
|
143
|
+
if (content === '@-') {
|
|
144
|
+
content = await readStdin(io.stdin ?? process.stdin)
|
|
145
|
+
} else if (content.startsWith('@')) {
|
|
146
|
+
content = readFileSync(resolvePath(content.slice(1)), 'utf8')
|
|
147
|
+
} else if (content.trim() !== '' && existsSync(resolvePath(content.trim()))) {
|
|
148
|
+
// README / SKILL.md 的示例直接传文件路径(不带 @)时也按文件读取
|
|
149
|
+
content = readFileSync(resolvePath(content.trim()), 'utf8')
|
|
150
|
+
} else if (content.trim() === '' && slides === null && !args.template) {
|
|
151
|
+
throw new Error('dsh-ppt CLI:--content 不能为空(或用 --slides 传结构化幻灯片)')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const options = {
|
|
155
|
+
title: args.title,
|
|
156
|
+
content,
|
|
157
|
+
slides,
|
|
158
|
+
theme: args.theme,
|
|
159
|
+
lang: args.lang,
|
|
160
|
+
motion: args.motion,
|
|
161
|
+
outputDir: resolvePath(args.out || '.'),
|
|
162
|
+
fileName: args.file,
|
|
163
|
+
overwrite: args.overwrite,
|
|
164
|
+
template: args.template,
|
|
165
|
+
brand: args.brand,
|
|
166
|
+
}
|
|
167
|
+
const result = await buildDeckAsync(options)
|
|
168
|
+
log('dsh-ppt 已生成 ' + result.slideCount + ' 页演示文稿:')
|
|
169
|
+
log(' HTML 放映:' + result.htmlPath)
|
|
170
|
+
log(' PPTX 导出:' + result.pptxPath)
|
|
171
|
+
log(' Manifest :' + result.jsonPath)
|
|
172
|
+
if (result.deliveryStatus === 'draft') log(' 草稿:还有待填写内容,不可作为成品交付。')
|
|
173
|
+
log(' 质量检查:' + result.quality.errorCount + ' 个错误 / ' + result.quality.warningCount + ' 个提醒;修订号 ' + result.revision)
|
|
174
|
+
return 0
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
|
|
178
|
+
main().then((code) => {
|
|
179
|
+
process.exitCode = code ?? 0
|
|
180
|
+
}).catch((err) => {
|
|
181
|
+
console.error('[dsh-ppt] ' + (err instanceof Error ? err.message : String(err)))
|
|
182
|
+
process.exitCode = 1
|
|
183
|
+
})
|
|
184
|
+
}
|
|
@@ -312,13 +312,14 @@ async function generate(manifest, signal) {
|
|
|
312
312
|
manifest.quality = checkManifest(manifest)
|
|
313
313
|
if (manifest.quality.errorCount) fail(manifest.quality.issues.filter(i => i.severity === 'error').map(i => `第 ${i.slide} 页:${i.message}`).join(';'))
|
|
314
314
|
const theme = brandedTheme(manifest), language = resolveLanguage(manifest.language)
|
|
315
|
-
const json = documentBytes(manifest)
|
|
315
|
+
const json = documentBytes(manifest)
|
|
316
316
|
const pptx = await exportPptx(manifest, theme, language, signal)
|
|
317
317
|
abort(signal)
|
|
318
|
-
return { json,
|
|
318
|
+
return { json, pptx, manifest, theme, language }
|
|
319
319
|
}
|
|
320
320
|
function commit(paths, data) {
|
|
321
|
-
|
|
321
|
+
const html = renderHtml(data.manifest, data.theme, data.language, paths)
|
|
322
|
+
commitDeckArtifacts([{ path: paths.json, data: data.json, encoding: 'utf8' }, { path: paths.html, data: html, encoding: 'utf8' }, { path: paths.pptx, data: data.pptx }])
|
|
322
323
|
}
|
|
323
324
|
|
|
324
325
|
export async function buildDeckAsync(options = {}) {
|
|
@@ -369,6 +370,12 @@ function readDocument(pathInput, cwd = '.') {
|
|
|
369
370
|
}
|
|
370
371
|
return { manifest: document, path, fingerprint: hash(bytes) }
|
|
371
372
|
}
|
|
373
|
+
/** Validated project snapshot for the operator workbench; no file is modified. */
|
|
374
|
+
export function readDeckProject(options = {}) {
|
|
375
|
+
abort(options.signal)
|
|
376
|
+
const { manifest, path } = readDocument(options.deckPath, options.cwd)
|
|
377
|
+
return { manifest: clone(manifest), path }
|
|
378
|
+
}
|
|
372
379
|
function expected(manifest, revision) {
|
|
373
380
|
if (revision !== undefined && (!Number.isInteger(revision) || revision !== manifest.revision)) fail(`修订号冲突:当前 revision=${manifest.revision},请重新读取工程后修改`)
|
|
374
381
|
}
|
|
@@ -389,7 +396,7 @@ export async function editDeck(options = {}) {
|
|
|
389
396
|
expected(current, options.expectedRevision)
|
|
390
397
|
const next = clone(current)
|
|
391
398
|
if (!Array.isArray(options.edits) || !options.edits.length) {
|
|
392
|
-
if (options.brand === undefined) fail('请提供 edits 或 brand')
|
|
399
|
+
if (options.brand === undefined && options.order === undefined) fail('请提供 edits、order 或 brand')
|
|
393
400
|
}
|
|
394
401
|
const used = new Set(), allowed = new Set(['layout', 'title', 'subtitle', 'kicker', 'text', 'bullets', 'rows', 'notes', 'image', 'chart'])
|
|
395
402
|
for (const edit of options.edits ?? []) {
|
|
@@ -412,6 +419,12 @@ export async function editDeck(options = {}) {
|
|
|
412
419
|
const embedded = embedSlides(slides, next.assets, resolve(options.cwd || '.'))[0]
|
|
413
420
|
next.slides[index] = { ...embedded, id: current.slides[index].id }
|
|
414
421
|
}
|
|
422
|
+
if (options.order !== undefined) {
|
|
423
|
+
if (!Array.isArray(options.order) || options.order.length !== next.slides.length || options.order.some(id => typeof id !== 'string') || new Set(options.order).size !== next.slides.length) fail('order 必须包含每个页面 ID,且各出现一次')
|
|
424
|
+
const byId = new Map(next.slides.map(slide => [slide.id, slide]))
|
|
425
|
+
if (options.order.some(id => !byId.has(id))) fail('order 含有不属于本工程的页面 ID')
|
|
426
|
+
next.slides = options.order.map(id => byId.get(id))
|
|
427
|
+
}
|
|
415
428
|
if (options.brand !== undefined) next.brand = normalizeBrand(options.brand === null ? null : { ...next.brand, ...options.brand,
|
|
416
429
|
...(options.brand?.logo === null ? { logo: undefined, logoAssetId: undefined } : {}) }, next.assets, resolve(options.cwd || '.'))
|
|
417
430
|
if (JSON.stringify(snapshot(next)) === JSON.stringify(snapshot(current))) return { ...result(current, pathsFor(state.path)), changed: false }
|