dsh-ppt 0.7.0 → 0.8.1

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 (37) hide show
  1. package/CHANGELOG.md +114 -91
  2. package/LICENSE +21 -21
  3. package/README.en.md +42 -219
  4. package/README.md +42 -234
  5. package/cordis.patch.yml +19 -19
  6. package/docs/CHANGELOG.en.md +29 -0
  7. package/docs/FEATURE-PLAN-2026-10-01.md +50 -50
  8. package/docs/LIBREOFFICE-INTEGRATION-2026-10-01.md +69 -69
  9. package/docs/USAGE.en.md +194 -0
  10. package/docs/USAGE.md +209 -0
  11. package/docs/VALIDATION.md +17 -0
  12. package/docs/validation/0.8.0.md +10 -0
  13. package/docs/validation-language-2026-10-03.md +16 -0
  14. package/lib/client.js +304 -0
  15. package/lib/execution.d.ts +1 -0
  16. package/lib/execution.js +1 -1
  17. package/lib/index.d.ts +1 -0
  18. package/lib/index.js +15 -1
  19. package/lib/tools.js +2 -1
  20. package/lib/types.d.ts +1 -0
  21. package/lib/workbench-language.d.ts +4 -0
  22. package/lib/workbench-language.js +65 -0
  23. package/lib/workbench.d.ts +32 -0
  24. package/lib/workbench.js +270 -0
  25. package/package.json +91 -84
  26. package/skills/dsh-ppt/SKILL.md +74 -74
  27. package/skills/dsh-ppt/package.json +10 -10
  28. package/skills/dsh-ppt/references/advanced.md +39 -39
  29. package/skills/dsh-ppt/references/copywriting.md +59 -59
  30. package/skills/dsh-ppt/references/syntax.md +44 -44
  31. package/skills/dsh-ppt/references/themes.md +61 -61
  32. package/skills/dsh-ppt/references/troubleshooting.md +19 -19
  33. package/skills/dsh-ppt/scripts/build-deck.mjs +184 -184
  34. package/skills/dsh-ppt/scripts/deck-advanced.mjs +468 -456
  35. package/skills/dsh-ppt/scripts/deck-core.mjs +2347 -2347
  36. package/skills/dsh-ppt/scripts/deck-media.mjs +160 -160
  37. package/skills/dsh-ppt/scripts/deck-render.mjs +270 -236
@@ -0,0 +1,270 @@
1
+ /** Operator-only workbench. Paths come from successful tools, never browser input. */
2
+ import { createHash } from 'node:crypto';
3
+ import { appendFileSync, existsSync, lstatSync, mkdirSync, readFileSync, realpathSync } from 'node:fs';
4
+ import { homedir } from 'node:os';
5
+ import { basename, dirname, join, resolve } from 'node:path';
6
+ import { getEngine } from './execution.js';
7
+ import { bilingualQuality, englishWorkbenchMessage } from './workbench-language.js';
8
+ export const PPT_WORKBENCH_ROUTE = '/api/dsh-ppt/workbench';
9
+ export const PPT_DOWNLOAD_ROUTE = '/api/dsh-ppt/download';
10
+ const REQUEST_LIMIT = 12 * 1024 * 1024;
11
+ const FILE_LIMIT = 64 * 1024 * 1024;
12
+ const digest = (value) => createHash('sha256').update(value).digest('hex');
13
+ function ordinaryFile(path) {
14
+ const stat = lstatSync(path);
15
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > FILE_LIMIT)
16
+ throw new Error('文件不是普通文件或超过 64 MiB');
17
+ return readFileSync(path);
18
+ }
19
+ function pathsFor(path) { const base = path.slice(0, -5); return { json: path, html: base + '.html', pptx: base + '.pptx' }; }
20
+ function json(status, value) {
21
+ return new Response(JSON.stringify(value), { status, headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' } });
22
+ }
23
+ function fail(status, message) { return json(status, { ok: false, message, messageEn: englishWorkbenchMessage(message) }); }
24
+ function originVerdict(request) {
25
+ const site = request.headers.get('sec-fetch-site'), origin = request.headers.get('origin');
26
+ if (site && !['same-origin', 'none'].includes(site))
27
+ return fail(403, '拒绝跨站操作');
28
+ if (origin && /^https?:/i.test(origin)) {
29
+ try {
30
+ if (new URL(origin).host !== (request.headers.get('host') || new URL(request.url).host))
31
+ return fail(403, '拒绝跨源操作');
32
+ }
33
+ catch {
34
+ return fail(403, '无效来源');
35
+ }
36
+ }
37
+ }
38
+ export class PptWorkbench {
39
+ catalogPath;
40
+ loadEngine;
41
+ renders = new Map();
42
+ constructor(options = {}) {
43
+ this.catalogPath = options.catalogPath ?? join(process.env.DSH_HOME || join(homedir(), '.dsh'), 'plugins', 'dsh-ppt', 'projects.jsonl');
44
+ this.loadEngine = options.loadEngine ?? getEngine;
45
+ }
46
+ catalog() {
47
+ if (!existsSync(this.catalogPath))
48
+ return [];
49
+ if (lstatSync(this.catalogPath).size > 8 * 1024 * 1024)
50
+ throw new Error('项目索引超过 8 MiB,请备份后整理 projects.jsonl');
51
+ const rows = new Map();
52
+ for (const line of readFileSync(this.catalogPath, 'utf8').split('\n')) {
53
+ try {
54
+ const row = JSON.parse(line);
55
+ if (typeof row.path === 'string' && row.path.endsWith('.json') && row.id === digest(row.path) && typeof row.title === 'string' && typeof row.updatedAt === 'string') {
56
+ rows.delete(row.id);
57
+ rows.set(row.id, row);
58
+ }
59
+ }
60
+ catch { /* A crash may leave an incomplete last append; keep older rows. */ }
61
+ }
62
+ return [...rows.values()].reverse().slice(0, 100);
63
+ }
64
+ /** Recording a completed tool never turns a successful generation into an error. */
65
+ async remember(result, args, exec) {
66
+ const file = result?.jsonPath ?? result?.deckPath;
67
+ if (typeof file !== 'string')
68
+ return;
69
+ exec?.signal?.throwIfAborted();
70
+ const path = realpathSync(resolve(exec?.agent?.session.header.cwd ?? process.cwd(), file));
71
+ ordinaryFile(path);
72
+ const engine = await this.loadEngine();
73
+ const { manifest } = engine.readDeckProject({ deckPath: path });
74
+ // Versioned generator projects only; arbitrary JSON and externally edited PPTX cannot enter.
75
+ if (manifest.schemaVersion !== 1 || typeof manifest.deckId !== 'string' || manifest.slides.some((s) => typeof s.id !== 'string'))
76
+ return;
77
+ const row = { id: digest(path), path, title: manifest.title, updatedAt: new Date().toISOString() };
78
+ mkdirSync(dirname(this.catalogPath), { recursive: true });
79
+ if (existsSync(this.catalogPath) && lstatSync(this.catalogPath).size >= 8 * 1024 * 1024)
80
+ throw new Error('项目索引已满,请备份后整理 projects.jsonl');
81
+ appendFileSync(this.catalogPath, JSON.stringify(row) + '\n');
82
+ }
83
+ async project(id) {
84
+ if (typeof id !== 'string' || !/^[0-9a-f]{64}$/.test(id))
85
+ throw new Error('请选择已有项目');
86
+ const entry = this.catalog().find(row => row.id === id);
87
+ if (!entry)
88
+ throw new Error('项目不在本机索引中,请先在对话中创建或检查演示文稿');
89
+ if (realpathSync(entry.path) !== entry.path)
90
+ throw new Error('项目位置已改变,请在对话中重新检查');
91
+ ordinaryFile(entry.path);
92
+ const engine = await this.loadEngine();
93
+ const { manifest } = engine.readDeckProject({ deckPath: entry.path });
94
+ return { entry, engine, manifest, files: pathsFor(entry.path) };
95
+ }
96
+ async view(id) {
97
+ const { entry, engine, manifest, files } = await this.project(id);
98
+ const check = bilingualQuality(engine.checkDeck({ deckPath: entry.path }));
99
+ let html = '';
100
+ try {
101
+ html = ordinaryFile(files.html).toString('utf8');
102
+ }
103
+ catch { /* A check can register an older JSON-only project. */ }
104
+ const render = this.renders.get(entry.id);
105
+ const currentRender = render && render.revision === manifest.revision && digest(ordinaryFile(files.pptx)) === render.sourceSha256 ? render : undefined;
106
+ return { ok: true, project: { ...entry, deckId: manifest.deckId, revision: manifest.revision, slides: manifest.slides,
107
+ undoAvailable: manifest.history.length, quality: check, html,
108
+ assets: Object.fromEntries(Object.entries(manifest.assets ?? {}).map(([key, value]) => [key, { data: value.data, mime: value.mime }])),
109
+ render: currentRender ? { pageCount: currentRender.pngPaths.length, missingFonts: currentRender.missingFonts ?? [] } : null } };
110
+ }
111
+ revision(body, manifest) {
112
+ if (!Number.isSafeInteger(body.expectedRevision) || body.expectedRevision !== manifest.revision)
113
+ throw new Error('项目已被修改,请重新打开后再编辑;当前输入保留');
114
+ }
115
+ patch(value) {
116
+ if (!value || typeof value !== 'object' || Array.isArray(value))
117
+ throw new Error('修改内容无效');
118
+ const result = {};
119
+ for (const [key, item] of Object.entries(value)) {
120
+ if (['title', 'subtitle', 'kicker', 'text', 'notes'].includes(key)) {
121
+ if (typeof item !== 'string' || item.length > 8000)
122
+ throw new Error('文字字段过长或无效');
123
+ }
124
+ else if (key === 'bullets') {
125
+ if (!Array.isArray(item) || item.length > 50 || item.some(v => typeof v !== 'string' || v.length > 2000))
126
+ throw new Error('要点内容无效');
127
+ }
128
+ else if (key === 'image') {
129
+ if (!item || typeof item !== 'object' || Array.isArray(item))
130
+ throw new Error('请选择 PNG 或 JPEG 图片');
131
+ const image = item;
132
+ if (Object.keys(image).some(k => !['src', 'alt', 'fit', 'caption'].includes(k)) || typeof image.src !== 'string' || !/^data:image\/(png|jpeg);base64,[A-Za-z0-9+/=]+$/.test(image.src))
133
+ throw new Error('图片必须从面板上传,不能读取其他本地文件');
134
+ if (['alt', 'caption'].some(k => image[k] !== undefined && (typeof image[k] !== 'string' || image[k].length > 2000)) || (image.fit !== undefined && !['contain', 'cover'].includes(String(image.fit))))
135
+ throw new Error('图片说明或适应方式无效');
136
+ }
137
+ else
138
+ throw new Error('面板不支持修改此字段:' + key);
139
+ result[key] = item;
140
+ }
141
+ return result;
142
+ }
143
+ async action(body, signal) {
144
+ signal?.throwIfAborted();
145
+ if (body.operation === 'list')
146
+ return { ok: true, projects: this.catalog() };
147
+ if (body.operation === 'open')
148
+ return this.view(body.id);
149
+ const { entry, engine, manifest, files } = await this.project(body.id);
150
+ signal?.throwIfAborted();
151
+ if (body.operation === 'download') {
152
+ if (!['html', 'pptx', 'json'].includes(String(body.kind)))
153
+ throw new Error('不支持此文件类型');
154
+ const path = files[body.kind];
155
+ return { ok: true, name: basename(path), data: ordinaryFile(path).toString('base64') };
156
+ }
157
+ if (body.operation === 'page') {
158
+ const render = this.renders.get(entry.id);
159
+ if (!render || render.revision !== manifest.revision || digest(ordinaryFile(files.pptx)) !== render.sourceSha256)
160
+ throw new Error('请先渲染当前版本的 PPTX');
161
+ if (!Number.isSafeInteger(body.page) || Number(body.page) < 1 || Number(body.page) > render.pngPaths.length)
162
+ throw new Error('页码无效');
163
+ const bytes = ordinaryFile(render.pngPaths[Number(body.page) - 1]);
164
+ if (digest(bytes) !== render.pngHashes[Number(body.page) - 1])
165
+ throw new Error('渲染图片已变化,请重新渲染当前 PPTX');
166
+ return { ok: true, data: 'data:image/png;base64,' + bytes.toString('base64') };
167
+ }
168
+ this.revision(body, manifest);
169
+ if (body.operation === 'render') {
170
+ const result = await engine.renderDeck({ pptxPath: files.pptx, format: 'png', signal });
171
+ if (result.ok)
172
+ this.renders.set(entry.id, { revision: manifest.revision, pngPaths: result.pngPaths ?? [], pngHashes: (result.pngPaths ?? []).map(path => digest(ordinaryFile(path))), missingFonts: result.missingFonts, sourceSha256: result.sourceSha256 });
173
+ const view = await this.view(entry.id);
174
+ if (result.ok && !view.project.render)
175
+ throw new Error('项目已被修改,请重新渲染');
176
+ return { ...result, ...(result.ok ? {} : {
177
+ messageEn: englishWorkbenchMessage(result.message || 'PPTX rendering failed.'),
178
+ installationHintEn: 'Optional renderer: @deepseek-ai/libreoffice-kit@0.1.5. Install it in this DSH profile and restart DSH.',
179
+ }), project: view.project };
180
+ }
181
+ if (body.operation === 'edit') {
182
+ if (body.slide !== undefined && (typeof body.slide !== 'string' || !manifest.slides.some((s) => s.id === body.slide)))
183
+ throw new Error('页面不存在');
184
+ const args = { deckPath: entry.path, expectedRevision: body.expectedRevision, signal,
185
+ ...(body.slide === undefined ? {} : { edits: [{ slide: body.slide, patch: this.patch(body.patch) }] }),
186
+ ...(body.order === undefined ? {} : { order: body.order }) };
187
+ const result = await engine.editDeck(args);
188
+ await this.remember(result, args);
189
+ return { ...await this.view(entry.id), changed: result.changed };
190
+ }
191
+ if (body.operation === 'undo') {
192
+ await engine.undoDeck({ deckPath: entry.path, expectedRevision: body.expectedRevision, signal });
193
+ return this.view(entry.id);
194
+ }
195
+ throw new Error('不支持此操作');
196
+ }
197
+ async fetchDownload(request) {
198
+ if (request.method !== 'GET')
199
+ return fail(405, '请使用 GET');
200
+ const denied = originVerdict(request);
201
+ if (denied)
202
+ return denied;
203
+ try {
204
+ const params = new URL(request.url).searchParams, kind = params.get('kind');
205
+ if (!['html', 'pptx', 'json'].includes(String(kind)))
206
+ return fail(400, '不支持此文件类型');
207
+ const { entry, manifest, files } = await this.project(params.get('id'));
208
+ if (String(manifest.revision) !== params.get('revision'))
209
+ return fail(409, '项目已被修改,请重新打开后下载');
210
+ request.signal.throwIfAborted();
211
+ const path = files[kind], bytes = ordinaryFile(path);
212
+ if ((await this.project(entry.id)).manifest.revision !== manifest.revision)
213
+ return fail(409, '项目已被修改,请重新打开后下载');
214
+ const type = kind === 'pptx' ? 'application/vnd.openxmlformats-officedocument.presentationml.presentation' : kind === 'html' ? 'text/html; charset=utf-8' : 'application/json; charset=utf-8';
215
+ return new Response(new Uint8Array(bytes), { headers: {
216
+ 'content-type': type, 'cache-control': 'no-store', 'x-content-type-options': 'nosniff',
217
+ 'content-disposition': `attachment; filename="presentation.${kind}"; filename*=UTF-8''${encodeURIComponent(basename(path))}`,
218
+ } });
219
+ }
220
+ catch (error) {
221
+ return fail(400, error instanceof Error ? error.message : String(error));
222
+ }
223
+ }
224
+ async fetch(request) {
225
+ if (request.method !== 'POST')
226
+ return fail(405, '请使用 POST');
227
+ if (request.headers.get('x-dsh-ppt-action') !== '1' || request.headers.get('content-type')?.split(';')[0].trim().toLowerCase() !== 'application/json')
228
+ return fail(403, '请从演示文稿面板操作');
229
+ const denied = originVerdict(request);
230
+ if (denied)
231
+ return denied;
232
+ try {
233
+ const reader = request.body?.getReader();
234
+ if (!reader)
235
+ return fail(400, '缺少请求内容');
236
+ let size = 0;
237
+ const chunks = [];
238
+ try {
239
+ for (;;) {
240
+ const { value, done } = await reader.read();
241
+ if (done)
242
+ break;
243
+ size += value.byteLength;
244
+ if (size > REQUEST_LIMIT) {
245
+ await reader.cancel();
246
+ return fail(413, '图片或请求超过 12 MiB,请压缩后重试');
247
+ }
248
+ chunks.push(value);
249
+ }
250
+ }
251
+ finally {
252
+ reader.releaseLock();
253
+ }
254
+ const body = JSON.parse(Buffer.concat(chunks).toString('utf8'));
255
+ if (!body || typeof body !== 'object' || Array.isArray(body))
256
+ return fail(400, '请求必须是对象');
257
+ return json(200, await this.action(body, request.signal));
258
+ }
259
+ catch (error) {
260
+ const message = error instanceof Error ? error.message : String(error);
261
+ return fail(/修订号冲突|已被修改|正在被另一次|其他操作修改/.test(message) ? 409 : 400, message);
262
+ }
263
+ }
264
+ }
265
+ export function installPptWorkbench(ctx, workbench) {
266
+ ctx.inject?.(['connection'], (host) => {
267
+ host.connection?.fetch?.register({ path: PPT_WORKBENCH_ROUTE, methods: ['POST'], requestBody: 'buffered', fetch: (request) => workbench.fetch(request) });
268
+ host.connection?.fetch?.register({ path: PPT_DOWNLOAD_ROUTE, methods: ['GET'], requestBody: 'buffered', fetch: (request) => workbench.fetchDownload(request) });
269
+ });
270
+ }
package/package.json CHANGED
@@ -1,84 +1,91 @@
1
- {
2
- "name": "dsh-ppt",
3
- "version": "0.7.0",
4
- "description": "DSH 演示文稿插件:HTML 与可编辑 PPTX、图文排版、原生图表、按页编辑撤销、质量检查与可选官方 PNG/PDF 渲染。",
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
- "./deck-advanced": "./skills/dsh-ppt/scripts/deck-advanced.mjs",
16
- "./deck-render": "./skills/dsh-ppt/scripts/deck-render.mjs",
17
- "./build-deck": "./skills/dsh-ppt/scripts/build-deck.mjs",
18
- "./cordis.patch.yml": "./cordis.patch.yml",
19
- "./package.json": "./package.json"
20
- },
21
- "files": [
22
- "lib",
23
- "skills",
24
- "docs",
25
- "cordis.patch.yml",
26
- "README.md",
27
- "README.en.md",
28
- "CHANGELOG.md",
29
- "LICENSE"
30
- ],
31
- "scripts": {
32
- "build": "tsc -p tsconfig.json",
33
- "typecheck": "tsc -p tsconfig.json --noEmit",
34
- "test": "pnpm run build && node --test \"test/*.test.mjs\"",
35
- "test:core": "node --test \"test/*.test.mjs\"",
36
- "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"
37
- },
38
- "dsh": {
39
- "bundle": {
40
- "patch": "./cordis.patch.yml"
41
- }
42
- },
43
- "keywords": [
44
- "dsh",
45
- "deepseek-harness",
46
- "dsh-plugin",
47
- "plugin",
48
- "skills",
49
- "ppt",
50
- "pptx",
51
- "presentation",
52
- "slides",
53
- "html"
54
- ],
55
- "license": "MIT",
56
- "engines": {
57
- "node": "^22.19.0 || >=24.0.0"
58
- },
59
- "devDependencies": {
60
- "@types/node": "^24.0.0",
61
- "typescript": "^5.6.0"
62
- },
63
- "repository": {
64
- "type": "git",
65
- "url": "git+https://github.com/STARDUSTLC666/dsh-ppt.git"
66
- },
67
- "bugs": {
68
- "url": "https://github.com/STARDUSTLC666/dsh-ppt/issues"
69
- },
70
- "homepage": "https://github.com/STARDUSTLC666/dsh-ppt#readme",
71
- "author": "stardustlc",
72
- "packageManager": "pnpm@11.7.0",
73
- "dependencies": {
74
- "@office-kit/pptx": "0.21.0"
75
- },
76
- "peerDependencies": {
77
- "@deepseek-ai/libreoffice-kit": "0.1.3"
78
- },
79
- "peerDependenciesMeta": {
80
- "@deepseek-ai/libreoffice-kit": {
81
- "optional": true
82
- }
83
- }
84
- }
1
+ {
2
+ "name": "dsh-ppt",
3
+ "version": "0.8.1",
4
+ "description": "DSH 演示文稿插件:生成 HTML、可编辑 PPTX 和工程文件,支持图表、轻量编辑、撤销与质量检查。",
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
+ "./deck-advanced": "./skills/dsh-ppt/scripts/deck-advanced.mjs",
16
+ "./deck-render": "./skills/dsh-ppt/scripts/deck-render.mjs",
17
+ "./client": "./lib/client.js",
18
+ "./build-deck": "./skills/dsh-ppt/scripts/build-deck.mjs",
19
+ "./cordis.patch.yml": "./cordis.patch.yml",
20
+ "./package.json": "./package.json"
21
+ },
22
+ "files": [
23
+ "lib",
24
+ "skills",
25
+ "docs",
26
+ "cordis.patch.yml",
27
+ "README.md",
28
+ "README.en.md",
29
+ "CHANGELOG.md",
30
+ "LICENSE"
31
+ ],
32
+ "scripts": {
33
+ "build": "tsc -p tsconfig.json",
34
+ "typecheck": "tsc -p tsconfig.json --noEmit",
35
+ "test": "pnpm run build && node --test \"test/*.test.mjs\"",
36
+ "test:core": "node --test \"test/*.test.mjs\"",
37
+ "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"
38
+ },
39
+ "dsh": {
40
+ "bundle": {
41
+ "patch": "./cordis.patch.yml"
42
+ },
43
+ "client": {
44
+ "inject": [
45
+ "@deepseek-ai/dsh-client-ui-settings"
46
+ ],
47
+ "platform": "web"
48
+ }
49
+ },
50
+ "keywords": [
51
+ "dsh",
52
+ "deepseek-harness",
53
+ "dsh-plugin",
54
+ "plugin",
55
+ "skills",
56
+ "ppt",
57
+ "pptx",
58
+ "presentation",
59
+ "slides",
60
+ "html"
61
+ ],
62
+ "license": "MIT",
63
+ "engines": {
64
+ "node": "^22.19.0 || >=24.0.0"
65
+ },
66
+ "devDependencies": {
67
+ "@types/node": "^24.0.0",
68
+ "typescript": "^5.6.0"
69
+ },
70
+ "repository": {
71
+ "type": "git",
72
+ "url": "git+https://github.com/STARDUSTLC666/dsh-ppt.git"
73
+ },
74
+ "bugs": {
75
+ "url": "https://github.com/STARDUSTLC666/dsh-ppt/issues"
76
+ },
77
+ "homepage": "https://github.com/STARDUSTLC666/dsh-ppt#readme",
78
+ "author": "stardustlc",
79
+ "packageManager": "pnpm@11.7.0",
80
+ "dependencies": {
81
+ "@office-kit/pptx": "0.21.0"
82
+ },
83
+ "peerDependencies": {
84
+ "@deepseek-ai/libreoffice-kit": "0.1.5"
85
+ },
86
+ "peerDependenciesMeta": {
87
+ "@deepseek-ai/libreoffice-kit": {
88
+ "optional": true
89
+ }
90
+ }
91
+ }
@@ -1,74 +1,74 @@
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), slide transitions + bullet entrance animations (motion, on by default), 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: "Node ^22.19.0 or >=24.0.0. Images/charts use @office-kit/pptx@0.21.0. Optional PNG/PDF renderer: @deepseek-ai/libreoffice-kit@0.1.3. Cross-harness: copy the full directory and run npm install --ignore-scripts there."
5
- allowed-tools: "Bash, Read, Write, Edit"
6
- ---
7
-
8
- # dsh-ppt
9
-
10
- 一句话或一篇文档 → 完整演示文稿:**独立 HTML 网页放映 + 可编辑 PPTX**。本文件只做路由与纪律;具体规则按需读取 `references/`。
11
-
12
- ## 触发条件
13
-
14
- 用户要做 PPT / 幻灯片 / 演示文稿 / deck / 汇报 / 提案 / 路演 / 培训材料,或给一句话、一段文字、一篇 Markdown 想变成可放映的演示。
15
-
16
- ## 执行纪律(每次都要遵守)
17
-
18
- - 一页一个观点;标题是判断句,不是名词短语。
19
- - 内容语言由你撰写;`lang` 只决定界面文字。
20
- - 先选主题再构建;不确定就用默认 `data`。
21
- - 交付前逐项过一遍文末「质量门禁」。
22
- - 目标是可用成品:先组织真实内容,再调用生成器;未填模板必须标注草稿,不编造缺失事实。
23
- - 渲染报告缺失字体时,替换为环境和接收方机器可用字体,重新生成并逐页检查;未解决需标注字体问题,不能声称已验收。
24
-
25
- ## 核心流水线
26
-
27
- 1. **Probe 需求**:读入用户的句子/文档,确定听众、目标、语气。一句话输入不要反问,直接按发布场景推断。
28
- 2. **Outline 大纲**:产出 5–12 页大纲(封面 → 问题/背景 → 方案/论点 3–6 页 → 证据 → 行动号召 → 结束页)。
29
- → 写大纲与正文前先读 `references/copywriting.md`。
30
- 3. **Theme 主题**:调 `ppt_themes`(DSH)或 `node scripts/build-deck.mjs --list-themes`(跨 harness)。
31
- → 拿不准选哪套时读 `references/themes.md` 的情绪决策树。
32
- 4. **Build 生成**:
33
- - DSH 内:调 `ppt_create`,大纲写成 Markdown 传 `content`(推荐),或传结构化 `slides`。
34
- - 跨 harness:`node <skill-dir>/scripts/build-deck.mjs --title "标题" --content deck.md --theme data --lang zh --out dist`。
35
- - 动效默认开(页间转场 + 要点逐条入场);严肃/打印场景传 `motion: 'off'`。
36
- → 用表格 / 金句 / 备注 / 结构化 slides 前读 `references/syntax.md`。
37
- 5. **Verify 校验与修正**:调 `ppt_check` 检查具体页;在浏览器操作放映、总览和备注,查看图片/图表。若可选官方引擎可用,调 `ppt_render` 读取最终 PPTX 生成逐页 PNG/PDF,逐页看画面。修正用 `ppt_edit`,以 `expectedRevision` 防冲突;需要时 `ppt_undo`。修改后重新渲染,以收据复核当前文件。文件头/结构通过不等同于视觉验收,静态估算、渲染、视觉检查必须分开说明。
38
- 6. **Deliver 交付**:给用户三个绝对路径,说明 HTML 直接放映(← → 翻页 · F 全屏 · G 总览 · S 备注 · P 打印),PPTX 用 PowerPoint / WPS / Keynote 打开(备注在演示者视图可见,bullets 页放映时逐条点击出现)。
39
-
40
- ## 按需装载索引
41
-
42
- | 何时读 | 文件 | 内容 |
43
- | --- | --- | --- |
44
- | 写大纲 / 正文前 | `references/copywriting.md` | 叙事骨架、单页规则、双语规则 |
45
- | 选主题拿不准 | `references/themes.md` | 5 套主题色板 + 情绪决策树 |
46
- | 用表格/金句/备注/结构化 slides | `references/syntax.md` | Markdown 解析、进阶语法、slides JSON、motion 开关 |
47
- | 图片/图表/模板/品牌/编辑/渲染 | `references/advanced.md` | 参数、限制、草稿与成品、收据语义 |
48
- | 交付异常 | `references/troubleshooting.md` | 症状 → 处理对照表 |
49
-
50
- ## 质量门禁(交付前逐项确认)
51
-
52
- - [ ] 页数 3–60,首屏是封面,末屏是结束页。
53
- - [ ] 每页有明确标题;bullets 页 1–8 条,中文每条 ≤ 20 字、英文 ≤ 12 词。
54
- - [ ] HTML 与 PPTX 使用同一主题,配色一致。
55
- - [ ] 主题色对比度足够(深底浅字或浅底深字)。
56
- - [ ] 中文内容无错别字;英文标题用 Title Case。
57
- - [ ] 最后一页有可执行的行动号召,不只是「谢谢」。
58
- - [ ] 文件已写到用户工作区,路径为绝对路径。
59
- - [ ] 没有未填模板;保留真实数据来源,检查每页画面而非只检查生成成功。
60
- - [ ] 图片嵌入成功,图表与原始数据一致;渲染收据匹配最终 PPTX,说明尚未验证的部分。
61
-
62
- ## 跨 harness 安装
63
-
64
- 把整个 `dsh-ppt` 技能目录复制到目标 agent 的技能目录,使用 Node `^22.19.0 || >=24.0.0`,在该目录运行 `npm install --ignore-scripts`。纯文本可直接运行;图片/图表/品牌导出需要 Office Kit,PNG/PDF 需另外安装可选官方引擎:
65
-
66
- | Agent | 技能目录 |
67
- | --- | --- |
68
- | DeepSeek Harness | `dsh plugin --profile web add dsh-ppt` |
69
- | Claude Code | `~/.claude/skills/` |
70
- | Cursor | `.cursor/skills/` |
71
- | Gemini CLI | `~/.gemini/skills/` |
72
- | OpenAI Codex | `~/.codex/skills/` |
73
-
74
- 一次编写,处处可用。
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), slide transitions + bullet entrance animations (motion, on by default), 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: "Node ^22.19.0 or >=24.0.0. Images/charts use @office-kit/pptx@0.21.0. Optional PNG/PDF renderer: @deepseek-ai/libreoffice-kit@0.1.3. Cross-harness: copy the full directory and run npm install --ignore-scripts there."
5
+ allowed-tools: "Bash, Read, Write, Edit"
6
+ ---
7
+
8
+ # dsh-ppt
9
+
10
+ 一句话或一篇文档 → 完整演示文稿:**独立 HTML 网页放映 + 可编辑 PPTX**。本文件只做路由与纪律;具体规则按需读取 `references/`。
11
+
12
+ ## 触发条件
13
+
14
+ 用户要做 PPT / 幻灯片 / 演示文稿 / deck / 汇报 / 提案 / 路演 / 培训材料,或给一句话、一段文字、一篇 Markdown 想变成可放映的演示。
15
+
16
+ ## 执行纪律(每次都要遵守)
17
+
18
+ - 一页一个观点;标题是判断句,不是名词短语。
19
+ - 内容语言由你撰写;`lang` 只决定界面文字。
20
+ - 先选主题再构建;不确定就用默认 `data`。
21
+ - 交付前逐项过一遍文末「质量门禁」。
22
+ - 目标是可用成品:先组织真实内容,再调用生成器;未填模板必须标注草稿,不编造缺失事实。
23
+ - 渲染报告缺失字体时,替换为环境和接收方机器可用字体,重新生成并逐页检查;未解决需标注字体问题,不能声称已验收。
24
+
25
+ ## 核心流水线
26
+
27
+ 1. **Probe 需求**:读入用户的句子/文档,确定听众、目标、语气。一句话输入不要反问,直接按发布场景推断。
28
+ 2. **Outline 大纲**:产出 5–12 页大纲(封面 → 问题/背景 → 方案/论点 3–6 页 → 证据 → 行动号召 → 结束页)。
29
+ → 写大纲与正文前先读 `references/copywriting.md`。
30
+ 3. **Theme 主题**:调 `ppt_themes`(DSH)或 `node scripts/build-deck.mjs --list-themes`(跨 harness)。
31
+ → 拿不准选哪套时读 `references/themes.md` 的情绪决策树。
32
+ 4. **Build 生成**:
33
+ - DSH 内:调 `ppt_create`,大纲写成 Markdown 传 `content`(推荐),或传结构化 `slides`。
34
+ - 跨 harness:`node <skill-dir>/scripts/build-deck.mjs --title "标题" --content deck.md --theme data --lang zh --out dist`。
35
+ - 动效默认开(页间转场 + 要点逐条入场);严肃/打印场景传 `motion: 'off'`。
36
+ → 用表格 / 金句 / 备注 / 结构化 slides 前读 `references/syntax.md`。
37
+ 5. **Verify 校验与修正**:调 `ppt_check` 检查具体页;在浏览器操作放映、总览和备注,查看图片/图表。若可选官方引擎可用,调 `ppt_render` 读取最终 PPTX 生成逐页 PNG/PDF,逐页看画面。修正用 `ppt_edit`,以 `expectedRevision` 防冲突;需要时 `ppt_undo`。修改后重新渲染,以收据复核当前文件。文件头/结构通过不等同于视觉验收,静态估算、渲染、视觉检查必须分开说明。
38
+ 6. **Deliver 交付**:给用户三个绝对路径,说明 HTML 直接放映(← → 翻页 · F 全屏 · G 总览 · S 备注 · P 打印),PPTX 用 PowerPoint / WPS / Keynote 打开(备注在演示者视图可见,bullets 页放映时逐条点击出现)。DSH 用户也可在「设置 → 演示文稿」修改文字、替换 PNG/JPEG、移动页面、撤销和下载最新文件;已有 JSON 工程先 `ppt_check` 才会出现在项目列表。`ppt_edit.order` 使用每个稳定页面 ID 各一次,与正文修改合并成一次撤销。
39
+
40
+ ## 按需装载索引
41
+
42
+ | 何时读 | 文件 | 内容 |
43
+ | --- | --- | --- |
44
+ | 写大纲 / 正文前 | `references/copywriting.md` | 叙事骨架、单页规则、双语规则 |
45
+ | 选主题拿不准 | `references/themes.md` | 5 套主题色板 + 情绪决策树 |
46
+ | 用表格/金句/备注/结构化 slides | `references/syntax.md` | Markdown 解析、进阶语法、slides JSON、motion 开关 |
47
+ | 图片/图表/模板/品牌/编辑/渲染 | `references/advanced.md` | 参数、限制、草稿与成品、收据语义 |
48
+ | 交付异常 | `references/troubleshooting.md` | 症状 → 处理对照表 |
49
+
50
+ ## 质量门禁(交付前逐项确认)
51
+
52
+ - [ ] 页数 3–60,首屏是封面,末屏是结束页。
53
+ - [ ] 每页有明确标题;bullets 页 1–8 条,中文每条 ≤ 20 字、英文 ≤ 12 词。
54
+ - [ ] HTML 与 PPTX 使用同一主题,配色一致。
55
+ - [ ] 主题色对比度足够(深底浅字或浅底深字)。
56
+ - [ ] 中文内容无错别字;英文标题用 Title Case。
57
+ - [ ] 最后一页有可执行的行动号召,不只是「谢谢」。
58
+ - [ ] 文件已写到用户工作区,路径为绝对路径。
59
+ - [ ] 没有未填模板;保留真实数据来源,检查每页画面而非只检查生成成功。
60
+ - [ ] 图片嵌入成功,图表与原始数据一致;渲染收据匹配最终 PPTX,说明尚未验证的部分。
61
+
62
+ ## 跨 harness 安装
63
+
64
+ 把整个 `dsh-ppt` 技能目录复制到目标 agent 的技能目录,使用 Node `^22.19.0 || >=24.0.0`,在该目录运行 `npm install --ignore-scripts`。纯文本可直接运行;图片/图表/品牌导出需要 Office Kit,PNG/PDF 需另外安装可选官方引擎:
65
+
66
+ | Agent | 技能目录 |
67
+ | --- | --- |
68
+ | DeepSeek Harness | `dsh plugin --profile web add dsh-ppt` |
69
+ | Claude Code | `~/.claude/skills/` |
70
+ | Cursor | `.cursor/skills/` |
71
+ | Gemini CLI | `~/.gemini/skills/` |
72
+ | OpenAI Codex | `~/.codex/skills/` |
73
+
74
+ 一次编写,处处可用。
@@ -1,11 +1,11 @@
1
- {
2
- "name": "dsh-ppt-skill",
1
+ {
2
+ "name": "dsh-ppt-skill",
3
3
  "version": "0.7.0",
4
- "private": true,
5
- "type": "module",
6
- "description": "Standalone dsh-ppt skill dependencies for image/chart export",
7
- "dependencies": { "@office-kit/pptx": "0.21.0" },
8
- "peerDependencies": { "@deepseek-ai/libreoffice-kit": "0.1.3" },
9
- "peerDependenciesMeta": { "@deepseek-ai/libreoffice-kit": { "optional": true } },
10
- "engines": { "node": "^22.19.0 || >=24.0.0" }
11
- }
4
+ "private": true,
5
+ "type": "module",
6
+ "description": "Standalone dsh-ppt skill dependencies for image/chart export",
7
+ "dependencies": { "@office-kit/pptx": "0.21.0" },
8
+ "peerDependencies": { "@deepseek-ai/libreoffice-kit": "0.1.3" },
9
+ "peerDependenciesMeta": { "@deepseek-ai/libreoffice-kit": { "optional": true } },
10
+ "engines": { "node": "^22.19.0 || >=24.0.0" }
11
+ }