dsh-ppt 0.3.0 → 0.4.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0
4
+
5
+ - **动效(motion,默认开)**:HTML 放映页间淡入 + 要点按序逐条入场(打印自动兜底为静态);PPTX 每页原生淡入转场,`bullets` 页按段落构建、放映时点击一次出现一条要点(PowerPoint「出现,按段落」标准时序)。
6
+ - **`motion` 开关**:`ppt_create` 新增 `motion: 'on' | 'off'`,CLI 新增 `--motion on|off`;`off` 时产物不含任何动画代码,适合严肃/打印场景。manifest 记录该开关。
7
+ - **技能文件瘦身**:SKILL.md 收敛为触发条件 + 执行纪律 + 六步流水线 + 按需装载索引 + 质量门禁;新增 `references/syntax.md`(Markdown 解析、表格/金句/备注语法、结构化 slides、motion)与 `references/troubleshooting.md`;写作规则去重归口 `references/copywriting.md`,hyperframes 视觉联动并入 `references/themes.md`。
8
+ - 动效相关测试 8 例(转场/时序树/点击节点计数/静态开关/CLI/工具层),全套 42/42 通过。
9
+
3
10
  ## 0.3.0
4
11
 
5
12
  - **演讲者备注**:结构化 `slides` 的 `notes` 字段与 Markdown `<!-- 备注: ... -->` 注释都会成为该页备注;HTML 放映按 `S` 键(或右下角「备注」按钮)呼出备注面板;PPTX 生成原生 `notesSlide` + `notesMaster` 部件,PowerPoint 演示者视图直接可用。
package/README.en.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
6
6
 
7
- > **One sentence or one document → a complete presentation**: HTML web slideshow + PPTX export, 5 visual themes, bilingual Chinese/English.
7
+ > **One sentence or one document → a complete presentation**: HTML web slideshow + PPTX export, 5 visual themes, slide transitions + bullet entrance animations, bilingual Chinese/English.
8
8
 
9
9
  DeepSeek Harness (DSH) presentation skill + tool plugin: turns a sentence, a paragraph, or a Markdown document into a ready-to-present **HTML slideshow** and an editable **PPTX**. Pure Node, **zero runtime dependencies**, one codebase for Windows / macOS / Linux.
10
10
 
@@ -16,6 +16,7 @@ DeepSeek Harness (DSH) presentation skill + tool plugin: turns a sentence, a par
16
16
  | `ppt_themes` tool | Lists the 5 built-in themes and their best use cases |
17
17
  | 7 layouts | cover / section / bullets / statement / **quote** / **table** / closing |
18
18
  | Speaker notes | `<!-- note: ... -->` comments or the `notes` field: press `S` in the HTML player; native PPTX notes slides (presenter view) |
19
+ | Motion | On by default: HTML slide-in transitions + staggered bullet entrances; native PPTX fade transitions + click-to-reveal bullets. `motion: 'off'` / `--motion off` for a fully static deck |
19
20
  | Markdown extras | Tables (`\| ... \|`), blockquotes (`>`), and note comments are auto-detected into matching layouts |
20
21
  | `dsh-ppt` skill | A complete six-step SOP registered into DSH |
21
22
  | Standalone SKILL.md | Copy `skills/dsh-ppt/` into Claude Code / Cursor / Gemini CLI / Codex for cross-harness use |
@@ -29,7 +30,7 @@ Example:
29
30
 
30
31
  ## Compatibility
31
32
 
32
- Verified against `@deepseek-ai/dsh@0.1.2-alpha.3` on 2026-09-01. Built for the cordis patch-bundle plugin model (`cordis.patch.yml` + `dsh.bundle.patch`). No runtime imports of `@deepseek-ai/*` internals.
33
+ Verified against the tool and skill registration contracts in `@deepseek-ai/dsh@0.1.2-rc.1` on 2026-09-04 (no relevant API or documentation changes from alpha.5). Built for the cordis patch-bundle plugin model (`cordis.patch.yml` + `dsh.bundle.patch`). No runtime imports of `@deepseek-ai/*` internals.
33
34
 
34
35
  ## Installation
35
36
 
@@ -78,6 +79,8 @@ node <skill-dir>/scripts/build-deck.mjs \
78
79
 
79
80
  Artifacts:
80
81
 
82
+ Existing artifacts are not overwritten by default: if any member of the trio already exists, the whole set receives a shared `-1`, `-2`, ... suffix. Replacement requires explicit `overwrite: true` (or `--overwrite` in the CLI).
83
+
81
84
  | File | Purpose |
82
85
  | --- | --- |
83
86
  | `*.html` | Standalone web slideshow: arrow keys/wheel/touch navigation, F fullscreen, G overview, P print/save as PDF |
@@ -102,7 +105,7 @@ Themes are derived from [dsh-hyperframes](https://github.com/STARDUSTLC666/dsh-h
102
105
  - Each `## section` becomes one slide: lists produce `bullets` slides; empty sections produce `section` dividers.
103
106
  - Plain text without headings: the first paragraph is the cover, then every 5 sentences become one slide.
104
107
  - A single sentence automatically produces a complete 3-slide structure: cover → core idea → closing.
105
- - For precise control, use structured `slides` (`cover | section | bullets | statement | closing`).
108
+ - For precise control, use structured `slides` (`cover | section | bullets | statement | quote | table | closing`), with `rows` for tables and `notes` for speaker notes.
106
109
 
107
110
  ## Configuration
108
111
 
@@ -119,6 +122,8 @@ No required configuration. Optional:
119
122
 
120
123
  The `DSH_PPT_OUTPUT_DIR` env var can also set the default output directory; the `ppt_create` `outputDir`/`theme`/`lang` arguments have the highest priority.
121
124
 
125
+ Output paths resolve against each tool call's `exec.agent.session.header.cwd`: omitted paths use the session directory, relative paths (including plugin configuration) resolve within it, and absolute paths keep their meaning. Direct calls without a session fall back to the process working directory; concurrent sessions never change process cwd. Tools check `exec.signal` before and after loading the engine, so cancelled calls do not proceed to generation. Rendering and writing are synchronous and finish the artifact trio once started.
126
+
122
127
  ## Bilingual support
123
128
 
124
129
  - `lang` argument: `zh` (default) / `en` / `bilingual` controls the player UI, page numbers, and closing defaults.
@@ -146,7 +151,7 @@ pnpm run smoke:cli # bare CLI smoke test, generates .smoke-deck
146
151
  - The PPTX uses a blank layout plus text boxes: text is editable in PowerPoint / WPS, but no smart master placeholders yet.
147
152
  - A one-sentence input produces a minimal 3-slide structure; for richer decks, expand the content into a Markdown outline first.
148
153
  - `bilingual` only localizes the player UI; it does not translate content.
149
- - Charts, images, speaker notes, and PPT animations are planned for v0.2+.
154
+ - Charts and images are not supported yet; speaker notes and HTML/PPTX motion are already supported.
150
155
 
151
156
  ## License
152
157
 
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # dsh-ppt
4
4
 
5
- > **一句话 / 一篇文档 → 完整演示文稿**:HTML 网页放映 + PPTX 导出,5 套视觉主题,中英双语。
5
+ > **一句话 / 一篇文档 → 完整演示文稿**:HTML 网页放映 + PPTX 导出,5 套视觉主题,页间转场 + 要点入场动画,中英双语。
6
6
 
7
7
  ![npm version](https://img.shields.io/npm/v/dsh-ppt?label=npm&color=blue) ![npm downloads](https://img.shields.io/npm/dm/dsh-ppt) ![license](https://img.shields.io/npm/l/dsh-ppt) ![stars](https://img.shields.io/github/stars/STARDUSTLC666/dsh-ppt?style=social)
8
8
 
@@ -18,6 +18,7 @@ DSH(DeepSeek Harness)演示文稿技能 + 工具插件:把一句话、一
18
18
  | `ppt_themes` 工具 | 列出 5 套内置主题与适用场景 |
19
19
  | 7 种页型 | 封面 / 章节 / 要点 / 核心观点 / **金句** / **表格** / 结束页 |
20
20
  | 演讲者备注 | `<!-- 备注: ... -->` 或 `notes` 字段:HTML 按 `S` 呼出,PPTX 原生备注页(演示者视图) |
21
+ | 动效(motion) | 默认开:HTML 页间淡入 + 要点逐条入场;PPTX 原生转场 + 要点逐条点击显现;`motion: 'off'` / `--motion off` 产出纯静态 |
21
22
  | Markdown 进阶 | 表格(`\| ... \|`)、引用金句(`>`)、备注注释自动识别成对应页型 |
22
23
  | `dsh-ppt` 技能 | 完整 SOP 注册进 DSH:从一句话到成品 deck 的六步流水线 |
23
24
  | 裸 SKILL.md | 把 `skills/dsh-ppt/` 复制到 Claude Code / Cursor / Gemini CLI / Codex 即可跨 harness 使用 |
@@ -31,7 +32,7 @@ DSH(DeepSeek Harness)演示文稿技能 + 工具插件:把一句话、一
31
32
 
32
33
  ## 兼容性
33
34
 
34
- `@deepseek-ai/dsh@0.1.2-alpha.3` 上验证(2026-09-01 全量冒烟通过)。遵循 cordis 组合包补丁模型(`cordis.patch.yml` + `dsh.bundle.patch`),运行时不 import 任何 `@deepseek-ai/*` 内部模块。
35
+ 已按 `@deepseek-ai/dsh@0.1.2-rc.1` 的工具与技能注册契约验证(2026-09-04;相较 alpha.5 无相关 API / 文档变化)。遵循 cordis 组合包补丁模型(`cordis.patch.yml` + `dsh.bundle.patch`),运行时不 import 任何 `@deepseek-ai/*` 内部模块。
35
36
 
36
37
  ## 安装
37
38
 
@@ -71,6 +72,8 @@ node <skill-dir>/scripts/build-deck.mjs \
71
72
 
72
73
  输出三件套:
73
74
 
75
+ 默认不会覆盖已有同名产物:三件套中任一文件存在时会整组追加 `-1`、`-2`… 后缀;只有显式传 `overwrite: true`(CLI 为 `--overwrite`)才会覆盖。
76
+
74
77
  | 文件 | 用途 |
75
78
  | --- | --- |
76
79
  | `*.html` | 独立网页放映:方向键/滚轮/触屏翻页,F 全屏,G 总览,P 打印或另存 PDF |
@@ -95,7 +98,7 @@ node <skill-dir>/scripts/build-deck.mjs \
95
98
  - 每个 `## 小节` → 一页:有列表生成 `bullets` 页,无内容生成 `section` 过渡页。
96
99
  - 没有标题的纯文本 → 第一段作封面,后续每 5 句一页。
97
100
  - 只有一句话 → 自动生成「封面 → 核心观点 → 结束页」三页完整结构。
98
- - 需要精确控制时用结构化 `slides`(`cover | section | bullets | statement | closing`)。
101
+ - 需要精确控制时用结构化 `slides`(`cover | section | bullets | statement | quote | table | closing`),表格使用 `rows`,备注使用 `notes`。
99
102
 
100
103
  ## 配置
101
104
 
@@ -112,6 +115,8 @@ node <skill-dir>/scripts/build-deck.mjs \
112
115
 
113
116
  也可用环境变量 `DSH_PPT_OUTPUT_DIR` 指定默认输出目录;`ppt_create` 的 `outputDir`/`theme`/`lang` 参数优先级最高。
114
117
 
118
+ 输出目录按每次工具调用的 `exec.agent.session.header.cwd` 解析:不指定目录时写入当前会话工作目录,相对目录(包括插件配置)相对此目录解析,绝对目录保持原意。直接调用工具而未提供会话时,回退到进程工作目录;并行会话不会修改进程 cwd。工具在加载引擎前和加载完成后检查 `exec.signal`,取消的调用不会继续生成文件;生成和写入阶段为同步操作,开始后会完成本次三件套。
119
+
115
120
 
116
121
  ## 卸载
117
122
 
@@ -148,7 +153,7 @@ pnpm run smoke:cli # 裸 CLI 冒烟,生成 .smoke-deck
148
153
  - PPTX 采用空白版式 + 文本框实现:PowerPoint / WPS 中可正常编辑文字,但暂不生成智能母版占位符。
149
154
  - 一句话输入自动生成三页最小结构;更丰富的内容需要先扩写成 Markdown 大纲再调用 `ppt_create`。
150
155
  - `bilingual` 只双语化播放器界面,不自动翻译内容。
151
- - 暂不支持图表、图片、演讲者备注与 PPT 动画;这些在 v0.2+ 规划。
156
+ - 暂不支持图表与图片;演讲者备注及 HTML/PPTX 动效已经支持。
152
157
 
153
158
  ## 协议
154
159
 
@@ -158,4 +163,4 @@ MIT。社区插件,与 DeepSeek 官方无关;`@deepseek-ai/*` 为官方保
158
163
 
159
164
  - [dsh-hyperframes](https://github.com/STARDUSTLC666/dsh-hyperframes) — HTML 视频创作技能(本插件视觉风格来源)
160
165
  - [dsh-remotion](https://github.com/STARDUSTLC666/dsh-remotion) — React 编程式视频技能
161
- - [dsh-email](https://github.com/STARDUSTLC666/dsh-email) — 邮件六件套
166
+ - [dsh-email](https://github.com/STARDUSTLC666/dsh-email) — 邮件六件套
package/lib/config.js CHANGED
@@ -2,17 +2,39 @@ export const PPT_OUTPUT_DIR_ENV = 'DSH_PPT_OUTPUT_DIR';
2
2
  export const DEFAULT_MAX_SLIDES = 60;
3
3
  /** 解析并校验插件行配置。本插件无必填项,空配置永远可用。 */
4
4
  export function resolvePptConfig(config) {
5
+ if (config !== undefined && config !== null && (typeof config !== 'object' || Array.isArray(config))) {
6
+ throw new Error('dsh-ppt 配置必须是对象。');
7
+ }
5
8
  const raw = config ?? {};
9
+ if (raw.outputDir !== undefined && typeof raw.outputDir !== 'string') {
10
+ throw new Error('outputDir 必须是字符串。');
11
+ }
12
+ if (raw.defaultTheme !== undefined && typeof raw.defaultTheme !== 'string') {
13
+ throw new Error('defaultTheme 必须是字符串。');
14
+ }
15
+ if (raw.defaultLang !== undefined && typeof raw.defaultLang !== 'string') {
16
+ throw new Error('defaultLang 必须是字符串。');
17
+ }
6
18
  const outputDir = (typeof raw.outputDir === 'string' ? raw.outputDir : '').trim()
7
19
  || (process.env[PPT_OUTPUT_DIR_ENV] ?? '').trim();
8
20
  const defaultTheme = (typeof raw.defaultTheme === 'string' ? raw.defaultTheme : '').trim();
9
21
  const defaultLang = (typeof raw.defaultLang === 'string' ? raw.defaultLang : '').trim();
22
+ if (defaultTheme !== '' && !['swiss', 'velvet', 'data', 'soft', 'bold'].includes(defaultTheme)) {
23
+ throw new Error('defaultTheme 只支持 swiss / velvet / data / soft / bold。');
24
+ }
10
25
  if (defaultLang !== '' && defaultLang !== 'zh' && defaultLang !== 'en' && defaultLang !== 'bilingual') {
11
26
  throw new Error('defaultLang 只支持 zh / en / bilingual。');
12
27
  }
28
+ let maxSlides = DEFAULT_MAX_SLIDES;
29
+ if (raw.maxSlides !== undefined) {
30
+ if (typeof raw.maxSlides !== 'number' || !Number.isInteger(raw.maxSlides) || raw.maxSlides < 3 || raw.maxSlides > 120) {
31
+ throw new Error('maxSlides 必须是 3–120 的整数。');
32
+ }
33
+ maxSlides = raw.maxSlides;
34
+ }
13
35
  return {
14
36
  outputDir,
15
- maxSlides: clampInt(raw.maxSlides, DEFAULT_MAX_SLIDES, 3, 120),
37
+ maxSlides,
16
38
  defaultTheme,
17
39
  defaultLang,
18
40
  };
@@ -0,0 +1,18 @@
1
+ import type { ResolvedPptConfig } from './config.js';
2
+ import type { DeckEngine, PptThemesResult } from './types.js';
3
+ /** The Harness execution fields used by this plugin; optional for direct callers. */
4
+ export interface PptExecution {
5
+ readonly signal?: AbortSignal;
6
+ readonly agent?: {
7
+ readonly session: {
8
+ readonly header: {
9
+ readonly cwd?: string;
10
+ };
11
+ };
12
+ };
13
+ }
14
+ /** Build executors with a shared engine loader; resolve paths separately for each call. */
15
+ export declare function createPptExecutors(config: ResolvedPptConfig, loadEngine?: () => Promise<DeckEngine>): {
16
+ themes(rawArgs: unknown, exec?: PptExecution): Promise<PptThemesResult>;
17
+ create(rawArgs: unknown, exec?: PptExecution): Promise<import("./types.js").PptCreateResult>;
18
+ };
@@ -0,0 +1,56 @@
1
+ import { resolve } from 'node:path';
2
+ let enginePromise = null;
3
+ function getEngine() {
4
+ if (enginePromise === null) {
5
+ const engineUrl = new URL('../skills/dsh-ppt/scripts/deck-core.mjs', import.meta.url);
6
+ enginePromise = import(engineUrl.href).then((module) => module)
7
+ .catch((error) => {
8
+ enginePromise = null;
9
+ throw error;
10
+ });
11
+ }
12
+ return enginePromise;
13
+ }
14
+ function asRecord(value) {
15
+ return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : {};
16
+ }
17
+ /** Build executors with a shared engine loader; resolve paths separately for each call. */
18
+ export function createPptExecutors(config, loadEngine = getEngine) {
19
+ return {
20
+ async themes(rawArgs, exec) {
21
+ exec?.signal?.throwIfAborted();
22
+ const args = asRecord(rawArgs);
23
+ const lang = typeof args.lang === 'string' && args.lang.trim() !== '' ? args.lang.trim() : 'zh';
24
+ const engine = await loadEngine();
25
+ exec?.signal?.throwIfAborted();
26
+ return { ok: true, themes: engine.listThemes(lang) };
27
+ },
28
+ async create(rawArgs, exec) {
29
+ exec?.signal?.throwIfAborted();
30
+ const args = asRecord(rawArgs);
31
+ if (typeof args.title !== 'string' || args.title.trim() === '')
32
+ throw new Error('dsh-ppt:title 不能为空');
33
+ const hasContent = typeof args.content === 'string' && args.content.trim() !== '';
34
+ const hasSlides = Array.isArray(args.slides) && args.slides.length > 0;
35
+ if (!hasContent && !hasSlides)
36
+ throw new Error('dsh-ppt:content 不能为空(或用 slides 传结构化幻灯片)');
37
+ const engine = await loadEngine();
38
+ // Loading can yield to cancellation. Rendering/writing then runs synchronously.
39
+ exec?.signal?.throwIfAborted();
40
+ const cwd = exec?.agent?.session.header.cwd ?? process.cwd();
41
+ const outputDir = typeof args.outputDir === 'string' && args.outputDir.trim() !== '' ? args.outputDir.trim() : config.outputDir;
42
+ return engine.buildDeck({
43
+ title: args.title.trim(),
44
+ content: typeof args.content === 'string' ? args.content : '',
45
+ slides: hasSlides ? args.slides : undefined,
46
+ theme: typeof args.theme === 'string' && args.theme.trim() !== '' ? args.theme.trim() : (config.defaultTheme || undefined),
47
+ lang: typeof args.lang === 'string' && args.lang.trim() !== '' ? args.lang.trim() : (config.defaultLang || undefined),
48
+ motion: args.motion,
49
+ outputDir: resolve(cwd, outputDir || '.'),
50
+ fileName: typeof args.fileName === 'string' && args.fileName.trim() !== '' ? args.fileName.trim() : undefined,
51
+ overwrite: args.overwrite === true,
52
+ maxSlides: config.maxSlides,
53
+ });
54
+ },
55
+ };
56
+ }
package/lib/index.d.ts CHANGED
@@ -1,22 +1,12 @@
1
1
  import { type SkillsService } from './skill.js';
2
2
  import type { PptConfig } from './types.js';
3
+ import { type ToolDefinition } from './tools.js';
4
+ export type { ToolDefinition } from './tools.js';
5
+ export type { PptExecution } from './execution.js';
3
6
  /** cordis 服务注入:apply 里要使用 ctx.tools 与 ctx.skills。 */
4
7
  export declare const inject: string[];
5
8
  export declare const name = "dsh-ppt";
6
9
  export type Config = PptConfig;
7
- export interface ToolDefinition {
8
- name: string;
9
- description: string;
10
- parameters: Record<string, unknown>;
11
- output: {
12
- schema: Record<string, unknown>;
13
- render: (args: unknown, value: unknown) => Array<{
14
- type: 'text';
15
- text: string;
16
- }>;
17
- };
18
- execute: (rawArgs: unknown) => Promise<unknown>;
19
- }
20
10
  export interface PptPluginContext {
21
11
  tools: {
22
12
  register(definition: ToolDefinition): () => void;
@@ -30,4 +20,4 @@ export interface PptPluginContext {
30
20
  export declare function apply(ctx: PptPluginContext, config?: Config): void;
31
21
  export { resolvePptConfig, PPT_OUTPUT_DIR_ENV, DEFAULT_MAX_SLIDES, clampInt } from './config.js';
32
22
  export { bundledSkillsDir, parseSkillFile, registerPptSkill, SKILL_NAMES } from './skill.js';
33
- export type { PptConfig, PptCreateArgs, PptCreateResult, PptSlideSpec, PptThemeInfo, PptThemesResult } from './types.js';
23
+ export type { PptConfig, PptCreateArgs, PptCreateResult, PptLanguage, PptSlideLayout, PptSlideSpec, PptThemeId, PptThemeInfo, PptThemesResult, } from './types.js';
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * dsh-ppt —— 演示文稿技能 + 工具插件(v0.1.0)。
2
+ * dsh-ppt —— 演示文稿技能 + 工具插件。
3
3
  *
4
4
  * 一句话或一篇 Markdown 文档 → 完整演示文稿三件套:
5
5
  * deck.html 独立网页放映(无外链)
@@ -17,112 +17,10 @@
17
17
  */
18
18
  import { resolvePptConfig } from './config.js';
19
19
  import { registerPptSkill } from './skill.js';
20
+ import { buildPptTools } from './tools.js';
20
21
  /** cordis 服务注入:apply 里要使用 ctx.tools 与 ctx.skills。 */
21
22
  export const inject = ['tools', 'skills'];
22
23
  export const name = 'dsh-ppt';
23
- /**
24
- * 把作者 DSL 映射编译成原生 JSON Schema 对象,作为 defineTool 的
25
- * definition.parameters 原样下发。原生 wire 请求会逐字携带该值。
26
- */
27
- function compileParameters(spec) {
28
- const properties = {};
29
- const required = [];
30
- for (const [key, prop] of Object.entries(spec)) {
31
- if (prop?.required === true)
32
- required.push(key);
33
- const node = {};
34
- if (typeof prop?.type === 'string')
35
- node.type = prop.type;
36
- if (typeof prop?.description === 'string')
37
- node.description = prop.description;
38
- if (Array.isArray(prop?.enum))
39
- node.enum = prop.enum;
40
- if (prop?.type === 'array' && prop.items !== null && typeof prop.items === 'object') {
41
- node.items = {
42
- type: prop.items.type === 'object' ? 'object' : 'string',
43
- ...(prop.items.type === 'object' ? { additionalProperties: true } : {}),
44
- };
45
- }
46
- properties[key] = node;
47
- }
48
- return { type: 'object', properties, ...(required.length > 0 ? { required } : {}) };
49
- }
50
- const themeInfoSchema = {
51
- type: 'object',
52
- properties: {
53
- id: { type: 'string' },
54
- name: { type: 'string' },
55
- mood: { type: 'string' },
56
- bestFor: { type: 'string' },
57
- dark: { type: 'boolean' },
58
- palette: { type: 'object', additionalProperties: true },
59
- fonts: { type: 'object', additionalProperties: true },
60
- },
61
- additionalProperties: true,
62
- };
63
- const themesResultSchema = {
64
- type: 'object',
65
- properties: {
66
- ok: { type: 'boolean' },
67
- themes: { type: 'array', items: themeInfoSchema },
68
- },
69
- additionalProperties: true,
70
- };
71
- const createResultSchema = {
72
- type: 'object',
73
- properties: {
74
- ok: { type: 'boolean' },
75
- title: { type: 'string' },
76
- theme: { type: 'string' },
77
- language: { type: 'string' },
78
- slideCount: { type: 'integer' },
79
- outputDir: { type: 'string' },
80
- files: {
81
- type: 'object',
82
- properties: {
83
- html: { type: 'string' },
84
- pptx: { type: 'string' },
85
- json: { type: 'string' },
86
- },
87
- additionalProperties: true,
88
- },
89
- htmlPath: { type: 'string' },
90
- pptxPath: { type: 'string' },
91
- jsonPath: { type: 'string' },
92
- },
93
- additionalProperties: true,
94
- };
95
- function oneText(text) {
96
- return [{ type: 'text', text }];
97
- }
98
- function renderThemes(value) {
99
- if (value.themes.length === 0)
100
- return oneText('dsh-ppt 没有可用主题。');
101
- const lines = value.themes.map((theme) => '- ' + theme.id + ':' + theme.name + '(' + theme.mood + ')|适合:' + theme.bestFor + '|' + (theme.dark ? '深色' : '浅色'));
102
- return oneText('dsh-ppt 内置主题:\n\n' + lines.join('\n') + '\n\nppt_create 的 theme 参数填其中的 id(默认 data)。');
103
- }
104
- function renderCreate(value) {
105
- return oneText('dsh-ppt 已生成 ' + value.slideCount + ' 页演示文稿(主题 ' + value.theme + ',语言 ' + value.language + '):\n' +
106
- 'HTML 网页放映:' + value.htmlPath + '\n' +
107
- 'PPTX 导出:' + value.pptxPath + '\n' +
108
- 'Manifest:' + value.jsonPath + '\n' +
109
- 'HTML 双击即可放映(方向键翻页 / F 全屏 / G 总览 / P 打印);PPTX 可用 PowerPoint / WPS / Keynote 打开。');
110
- }
111
- let enginePromise = null;
112
- function getEngine() {
113
- if (enginePromise === null) {
114
- const engineUrl = new URL('../skills/dsh-ppt/scripts/deck-core.mjs', import.meta.url);
115
- enginePromise = import(engineUrl.href).then((module) => module)
116
- .catch((error) => {
117
- enginePromise = null;
118
- throw error;
119
- });
120
- }
121
- return enginePromise;
122
- }
123
- function isRecord(value) {
124
- return value !== null && typeof value === 'object';
125
- }
126
24
  export function apply(ctx, config = {}) {
127
25
  const resolved = resolvePptConfig(config);
128
26
  const warn = (message) => { ctx.logger?.warn?.(message); };
@@ -134,61 +32,9 @@ export function apply(ctx, config = {}) {
134
32
  catch (error) {
135
33
  warn('[dsh-ppt] 技能加载失败:' + (error instanceof Error ? error.message : String(error)));
136
34
  }
137
- disposers.push(ctx.tools.register({
138
- name: 'ppt_themes',
139
- description: 'List the built-in visual themes of dsh-ppt (id, name, mood, best-for, light/dark palette) before building a deck. Use a theme id as the theme argument of ppt_create. 中文:列出 dsh-ppt 内置视觉主题(id、名称、情绪、适用场景、明暗色板),用于选择 ppt_create 的 theme 参数。',
140
- parameters: compileParameters({
141
- lang: { type: 'string', description: 'Theme description language: zh (default), en, or bilingual.' },
142
- }),
143
- output: {
144
- schema: themesResultSchema,
145
- render: (_args, value) => renderThemes(value),
146
- },
147
- async execute(rawArgs) {
148
- const args = isRecord(rawArgs) ? rawArgs : {};
149
- const lang = typeof args.lang === 'string' && args.lang.trim() !== '' ? args.lang.trim() : 'zh';
150
- const engine = await getEngine();
151
- const themes = engine.listThemes(lang);
152
- return { ok: true, themes };
153
- },
154
- }));
155
- disposers.push(ctx.tools.register({
156
- name: 'ppt_create',
157
- description: 'Build a complete presentation deck from one sentence or a Markdown document and write three artifacts to outputDir: a standalone HTML web slideshow, an editable 16:9 PPTX, and a deck.json manifest. Five built-in visual themes are available (see ppt_themes). content is Markdown text (recommended); advanced callers may pass structured slides instead. 中文:把一句话或一篇 Markdown 文档生成完整演示文稿,写入 outputDir 三个文件:独立 HTML 网页放映、可编辑 16:9 PPTX、deck.json manifest;内置 5 套视觉主题。',
158
- parameters: compileParameters({
159
- title: { type: 'string', required: true, description: 'Deck title (used for the cover and file names).' },
160
- content: { type: 'string', required: true, description: 'Markdown content: one sentence, a paragraph, or a full document. First # heading becomes the cover title; ## headings become slides; -/* lists become bullets; | ... | tables become table slides; > blockquotes become quote slides; <!-- 备注: ... --> comments become speaker notes. Required unless slides is provided.' },
161
- theme: { type: 'string', description: 'Visual theme id: swiss / velvet / data / soft / bold. Default data. See ppt_themes.' },
162
- lang: { type: 'string', description: 'UI language of the generated player: zh (default), en, or bilingual. Content language is whatever you write.' },
163
- slides: { type: 'array', items: { type: 'object', additionalProperties: true }, description: 'Optional structured slides: [{ layout: cover|section|bullets|statement|quote|table|closing, title, subtitle, kicker, bullets: [], rows: [][] (for table), notes: "speaker notes" }]. Use this for precise control instead of content.' },
164
- outputDir: { type: 'string', description: 'Directory to write the files into. Default: session working directory (or the plugin outputDir config).' },
165
- fileName: { type: 'string', description: 'Base file name for the three artifacts. Default: sanitized deck title.' },
166
- }),
167
- output: {
168
- schema: createResultSchema,
169
- render: (_args, value) => renderCreate(value),
170
- },
171
- async execute(rawArgs) {
172
- const args = (isRecord(rawArgs) ? rawArgs : {});
173
- if (typeof args.title !== 'string' || args.title.trim() === '')
174
- throw new Error('dsh-ppt:title 不能为空');
175
- const hasContent = typeof args.content === 'string' && args.content.trim() !== '';
176
- const hasSlides = Array.isArray(args.slides) && args.slides.length > 0;
177
- if (!hasContent && !hasSlides)
178
- throw new Error('dsh-ppt:content 不能为空(或用 slides 传结构化幻灯片)');
179
- const engine = await getEngine();
180
- return engine.buildDeck({
181
- title: args.title.trim(),
182
- content: typeof args.content === 'string' ? args.content : '',
183
- slides: hasSlides ? args.slides : undefined,
184
- theme: typeof args.theme === 'string' && args.theme.trim() !== '' ? args.theme.trim() : (resolved.defaultTheme || undefined),
185
- lang: typeof args.lang === 'string' && args.lang.trim() !== '' ? args.lang.trim() : (resolved.defaultLang || undefined),
186
- outputDir: typeof args.outputDir === 'string' && args.outputDir.trim() !== '' ? args.outputDir.trim() : (resolved.outputDir || undefined),
187
- fileName: typeof args.fileName === 'string' && args.fileName.trim() !== '' ? args.fileName.trim() : undefined,
188
- maxSlides: resolved.maxSlides,
189
- });
190
- },
191
- }));
35
+ for (const definition of buildPptTools(resolved)) {
36
+ disposers.push(ctx.tools.register(definition));
37
+ }
192
38
  if (typeof ctx.on === 'function') {
193
39
  ctx.on('dispose', () => {
194
40
  for (const dispose of disposers)
package/lib/tools.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ import type { ResolvedPptConfig } from './config.js';
2
+ import { type PptExecution } from './execution.js';
3
+ export interface ToolDefinition {
4
+ name: string;
5
+ description: string;
6
+ parameters: Record<string, unknown>;
7
+ output: {
8
+ schema: Record<string, unknown>;
9
+ render: (args: unknown, value: unknown) => Array<{
10
+ type: 'text';
11
+ text: string;
12
+ }>;
13
+ };
14
+ execute: (rawArgs: unknown, exec?: PptExecution) => Promise<unknown>;
15
+ }
16
+ /** Assemble presentation schemas, rendering and the session-aware executors. */
17
+ export declare function buildPptTools(config: ResolvedPptConfig): ToolDefinition[];
package/lib/tools.js ADDED
@@ -0,0 +1,123 @@
1
+ import { createPptExecutors } from './execution.js';
2
+ function compileParameters(spec) {
3
+ const properties = {};
4
+ const required = [];
5
+ for (const [key, prop] of Object.entries(spec)) {
6
+ if (prop?.required === true)
7
+ required.push(key);
8
+ const node = {};
9
+ if (typeof prop?.type === 'string')
10
+ node.type = prop.type;
11
+ if (typeof prop?.description === 'string')
12
+ node.description = prop.description;
13
+ if (Array.isArray(prop?.enum))
14
+ node.enum = prop.enum;
15
+ if (prop?.type === 'array' && prop.items !== null && typeof prop.items === 'object') {
16
+ node.items = {
17
+ type: prop.items.type === 'object' ? 'object' : 'string',
18
+ ...(prop.items.type === 'object' ? { additionalProperties: true } : {}),
19
+ };
20
+ }
21
+ properties[key] = node;
22
+ }
23
+ return { type: 'object', properties, ...(required.length > 0 ? { required } : {}) };
24
+ }
25
+ const themeInfoSchema = {
26
+ type: 'object',
27
+ properties: {
28
+ id: { type: 'string' },
29
+ name: { type: 'string' },
30
+ mood: { type: 'string' },
31
+ bestFor: { type: 'string' },
32
+ dark: { type: 'boolean' },
33
+ palette: { type: 'object', additionalProperties: true },
34
+ fonts: { type: 'object', additionalProperties: true },
35
+ },
36
+ additionalProperties: true,
37
+ };
38
+ const themesResultSchema = {
39
+ type: 'object',
40
+ properties: {
41
+ ok: { type: 'boolean' },
42
+ themes: { type: 'array', items: themeInfoSchema },
43
+ },
44
+ additionalProperties: true,
45
+ };
46
+ const createResultSchema = {
47
+ type: 'object',
48
+ properties: {
49
+ ok: { type: 'boolean' },
50
+ title: { type: 'string' },
51
+ theme: { type: 'string' },
52
+ language: { type: 'string' },
53
+ slideCount: { type: 'integer' },
54
+ outputDir: { type: 'string' },
55
+ files: {
56
+ type: 'object',
57
+ properties: {
58
+ html: { type: 'string' },
59
+ pptx: { type: 'string' },
60
+ json: { type: 'string' },
61
+ },
62
+ additionalProperties: true,
63
+ },
64
+ htmlPath: { type: 'string' },
65
+ pptxPath: { type: 'string' },
66
+ jsonPath: { type: 'string' },
67
+ },
68
+ additionalProperties: true,
69
+ };
70
+ function oneText(text) {
71
+ return [{ type: 'text', text }];
72
+ }
73
+ function renderThemes(value) {
74
+ if (value.themes.length === 0)
75
+ return oneText('dsh-ppt 没有可用主题。');
76
+ const lines = value.themes.map((theme) => '- ' + theme.id + ':' + theme.name + '(' + theme.mood + ')|适合:' + theme.bestFor + '|' + (theme.dark ? '深色' : '浅色'));
77
+ return oneText('dsh-ppt 内置主题:\n\n' + lines.join('\n') + '\n\nppt_create 的 theme 参数填其中的 id(默认 data)。');
78
+ }
79
+ function renderCreate(value) {
80
+ return oneText('dsh-ppt 已生成 ' + value.slideCount + ' 页演示文稿(主题 ' + value.theme + ',语言 ' + value.language + '):\n' +
81
+ 'HTML 网页放映:' + value.htmlPath + '\n' +
82
+ 'PPTX 导出:' + value.pptxPath + '\n' +
83
+ 'Manifest:' + value.jsonPath + '\n' +
84
+ 'HTML 双击即可放映(方向键翻页 / F 全屏 / G 总览 / P 打印);PPTX 可用 PowerPoint / WPS / Keynote 打开。');
85
+ }
86
+ /** Assemble presentation schemas, rendering and the session-aware executors. */
87
+ export function buildPptTools(config) {
88
+ const executors = createPptExecutors(config);
89
+ return [
90
+ {
91
+ name: 'ppt_themes',
92
+ description: 'List the built-in visual themes of dsh-ppt (id, name, mood, best-for, light/dark palette) before building a deck. Use a theme id as the theme argument of ppt_create. 中文:列出 dsh-ppt 内置视觉主题(id、名称、情绪、适用场景、明暗色板),用于选择 ppt_create 的 theme 参数。',
93
+ parameters: compileParameters({
94
+ lang: { type: 'string', description: 'Theme description language: zh (default), en, or bilingual.' },
95
+ }),
96
+ output: {
97
+ schema: themesResultSchema,
98
+ render: (_args, value) => renderThemes(value),
99
+ },
100
+ execute: executors.themes,
101
+ },
102
+ {
103
+ name: 'ppt_create',
104
+ description: 'Build a complete presentation deck from one sentence or a Markdown document and write three artifacts to outputDir: a standalone HTML web slideshow, an editable 16:9 PPTX, and a deck.json manifest. Five built-in visual themes are available (see ppt_themes). content is Markdown text (recommended); advanced callers may pass structured slides instead. 中文:把一句话或一篇 Markdown 文档生成完整演示文稿,写入 outputDir 三个文件:独立 HTML 网页放映、可编辑 16:9 PPTX、deck.json manifest;内置 5 套视觉主题。',
105
+ parameters: compileParameters({
106
+ title: { type: 'string', required: true, description: 'Deck title (used for the cover and file names).' },
107
+ content: { type: 'string', description: 'Markdown content: one sentence, a paragraph, or a full document. First # heading becomes the cover title; ## headings become slides; -/* lists become bullets; | ... | tables become table slides; > blockquotes become quote slides; <!-- 备注: ... --> comments become speaker notes. Required unless slides is provided.' },
108
+ theme: { type: 'string', description: 'Visual theme id: swiss / velvet / data / soft / bold. Default data. See ppt_themes.' },
109
+ lang: { type: 'string', description: 'UI language of the generated player: zh (default), en, or bilingual. Content language is whatever you write.' },
110
+ motion: { type: 'string', enum: ['on', 'off'], description: 'Slide transitions plus bullet entrance animations: on (default) or off for a fully static deck.' },
111
+ slides: { type: 'array', items: { type: 'object', additionalProperties: true }, description: 'Optional structured slides: [{ layout: cover|section|bullets|statement|quote|table|closing, title, subtitle, kicker, bullets: [], rows: [][] (for table), notes: "speaker notes" }]. Use this for precise control instead of content.' },
112
+ outputDir: { type: 'string', description: 'Directory to write the files into. Default: session working directory (or the plugin outputDir config).' },
113
+ fileName: { type: 'string', description: 'Base file name for the three artifacts. Default: sanitized deck title.' },
114
+ overwrite: { type: 'boolean', description: 'Replace an existing same-name HTML/PPTX/JSON trio. Default false: choose a unique numeric suffix instead.' },
115
+ }),
116
+ output: {
117
+ schema: createResultSchema,
118
+ render: (_args, value) => renderCreate(value),
119
+ },
120
+ execute: executors.create,
121
+ },
122
+ ];
123
+ }