dsh-ppt 0.8.0 → 0.9.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.
@@ -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
+ }