@capyclaw/ppt-skill-ppt 0.0.0-stage → 2.2.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.
@@ -0,0 +1,4 @@
1
+ # @capyclaw/ppt-skill-ppt(目录 dsh-ppt-skill-ppt) bundle patch:只注册 ppt 一个 skill provider。
2
+ - insert:
3
+ - id: ppt-skill-ppt
4
+ name: @capyclaw/ppt-skill-ppt
package/lib/index.mjs ADDED
@@ -0,0 +1,23 @@
1
+ /** @capyclaw/ppt-skill-ppt(目录 dsh-ppt-skill-ppt):单 skill provider。 */
2
+ import { fileURLToPath } from 'node:url'
3
+ import { createSkillProvider } from './provider.mjs'
4
+
5
+ export const name = 'ppt-skill-ppt'
6
+ export const inject = ['skills']
7
+ const SKILL = "ppt"
8
+
9
+ function defaultProvider(config = {}) {
10
+ return createSkillProvider({
11
+ assetRoot: config.assetRoot ?? fileURLToPath(new URL('../skills/', import.meta.url)),
12
+ contractRoot: config.contractRoot ?? fileURLToPath(new URL('../contract/', import.meta.url)),
13
+ contractFile: 'ppt-contract.json',
14
+ providerName: "dsh-ppt-ppt",
15
+ skillDirs: [SKILL],
16
+ contractNote: (version, skillName) => (String(skillName || '').startsWith('pptmaster-')
17
+ ? ''
18
+ : '契约:PPTist Workbench ' + version + '。动作前先调用 ppt_capabilities;工具缺失或 major 不一致时停止,并提示用户安装/升级 @capyclaw/ppt-suite。'),
19
+ })
20
+ }
21
+
22
+ export function createProvider(config = {}) { return defaultProvider(config) }
23
+ export function apply(ctx, config = {}) { ctx.skills.registerProvider(() => defaultProvider(config)) }
@@ -0,0 +1,82 @@
1
+ /** 通用 skill provider:从目录扫描 kebab-case SKILL.md,按需加载正文。 */
2
+ import { readFileSync } from 'node:fs'
3
+ import { readFile } from 'node:fs/promises'
4
+ import { isAbsolute, join, sep } from 'node:path'
5
+
6
+ const NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
7
+ const RANK = 600
8
+
9
+ function parseSkill(raw, path) {
10
+ const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/u.exec(raw)
11
+ if (!frontmatter?.[1]) throw new Error(`skill ${path} 缺少 YAML frontmatter`)
12
+ const meta = {}
13
+ for (const line of frontmatter[1].split(/\r?\n/)) {
14
+ const match = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line)
15
+ if (match) meta[match[1]] = match[2].replace(/^['"]|['"]$/g, '').trim()
16
+ }
17
+ if (!meta.name || !meta.description) throw new Error(`skill ${path} 缺少 name/description`)
18
+ if (!NAME_RE.test(meta.name)) throw new Error(`skill ${path} name 必须是 kebab-case`)
19
+ return {
20
+ name: meta.name,
21
+ description: meta.description,
22
+ whenToUse: meta.whenToUse || undefined,
23
+ content: raw.slice(frontmatter[0].length).trim(),
24
+ }
25
+ }
26
+
27
+ /**
28
+ * @param {{
29
+ * assetRoot: string,
30
+ * skillDirs: string[],
31
+ * providerName?: string,
32
+ * contractRoot?: string,
33
+ * contractNote?: (version: string) => string
34
+ * }} config
35
+ */
36
+ export function createSkillProvider(config) {
37
+ const assetRoot = config.assetRoot
38
+ if (!assetRoot || !isAbsolute(assetRoot)) throw new Error('createSkillProvider: assetRoot 必须是绝对路径')
39
+ let contract = null
40
+ if (config.contractRoot) {
41
+ try {
42
+ contract = JSON.parse(readFileSync(join(config.contractRoot, config.contractFile || 'contract.json'), 'utf8'))
43
+ } catch { /* 非技能包可以没有契约快照 */ }
44
+ }
45
+ const dirs = config.skillDirs || []
46
+ if (!dirs.length) throw new Error('createSkillProvider: skillDirs 不能为空')
47
+ const candidates = dirs.map((dir) => {
48
+ const directory = join(assetRoot, dir)
49
+ const locator = join(directory, 'SKILL.md')
50
+ const parsed = parseSkill(readFileSync(locator, 'utf8'), locator)
51
+ if (parsed.name !== dir) throw new Error(`skill ${locator} name 必须是 ${dir}`)
52
+ return {
53
+ name: parsed.name,
54
+ description: parsed.description,
55
+ whenToUse: parsed.whenToUse,
56
+ invocation: { modelInvocable: true, userInvocable: true },
57
+ provider: config.providerName || 'dsh-plugin',
58
+ source: 'bundled',
59
+ rank: RANK,
60
+ resourceBase: { kind: 'directory', path: directory.split(sep).join('/') },
61
+ locator,
62
+ }
63
+ })
64
+ const byName = new Map(candidates.map((candidate) => [candidate.name, candidate]))
65
+ const noteFor = (name) => (contract && config.contractNote
66
+ ? config.contractNote(contract.contractVersion, name)
67
+ : '')
68
+
69
+ return {
70
+ name: config.providerName || 'dsh-plugin',
71
+ list: () => Promise.resolve(candidates),
72
+ async get(candidate, options = {}) {
73
+ const hit = byName.get(candidate.name)
74
+ if (!hit) throw new Error(`未知 skill ${candidate.name}`)
75
+ const raw = await readFile(hit.locator, { encoding: 'utf8', signal: options.signal })
76
+ const parsed = parseSkill(raw, hit.locator)
77
+ const { rank: _rank, locator: _locator, ...summary } = hit
78
+ const note = noteFor(candidate.name)
79
+ return { ...summary, content: parsed.content + (note ? `\n\n---\n\n${note}` : '') }
80
+ },
81
+ }
82
+ }
package/package.json CHANGED
@@ -1,6 +1,43 @@
1
1
  {
2
2
  "name": "@capyclaw/ppt-skill-ppt",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "2.2.1",
4
+ "description": "PPT skill:PPT 总入口:按意图路由到执行、成稿、审美、审计、研究技能。",
5
+ "type": "module",
6
+ "main": "./lib/index.mjs",
7
+ "exports": {
8
+ ".": "./lib/index.mjs",
9
+ "./package.json": "./package.json"
10
+ },
11
+ "engines": {
12
+ "node": ">=18",
13
+ "dsh": "^0.2.0-rc.2"
14
+ },
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "dsh": {
19
+ "bundle": {
20
+ "patch": "./cordis.patch.yml"
21
+ },
22
+ "category": "skills",
23
+ "platform": "both"
24
+ },
25
+ "meta": {
26
+ "title": "PPT Skill · PPT 路由",
27
+ "description": "PPT 总入口:按意图路由到执行、成稿、审美、审计、研究技能。"
28
+ },
29
+ "files": [
30
+ "lib/",
31
+ "skills/",
32
+ "contract/",
33
+ "cordis.patch.yml",
34
+ "README.md"
35
+ ],
36
+ "license": "MIT",
37
+ "scripts": {
38
+ "test": "node --test test/smoke.test.mjs"
39
+ },
40
+ "dependencies": {
41
+ "@capyclaw/plugin-kit": "^0.2.0"
42
+ }
43
+ }
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: ppt
3
+ description: PPT 演示文稿总入口:默认先路由到“纯执行/成稿”,审美与审计作为独立技能按需加载,不混入日常修改。
4
+ whenToUse: 用户提到 PPT、演示文稿、幻灯片、Keynote、deck,或要求生成/修改/美化/预览演示内容时。
5
+ ---
6
+
7
+ # PPT 工作台路由
8
+
9
+ 本技能只做路由。执行、审美、审计是三条独立链路:
10
+
11
+ | 用户意图 | 加载技能 | 负责什么 |
12
+ |---|---|---|
13
+ | 方案结构、故事线、目录、章节、一页一观点 | `pptmaster-narrative` | 内容叙事,不碰工具/视觉 |
14
+ | 调工具、schema、编辑器状态、撤销、页面命令 | `pptist-api` | PPTist API 操作手册 |
15
+ | 打开/保存本地 .pptx | `pptist-api` | `ppt_import_pptx` / `ppt_export_pptx`,不手写 OOXML |
16
+ | 从叙事骨架落到干净成稿 | `pptist-authoring` | 桥接层:骨架 → 简洁版式工具 |
17
+ | 改现有 deck(文字/背景/元素/页/图片) | `pptist-editing` | **纯执行**:指哪打哪,不审美、不审计、不回头验证 |
18
+ | 配色、字体、版式、设计 token、反 AI slop | `pptmaster-design` | 独立审美技能,只在用户明确要审美判断时加载 |
19
+ | 审计、截图、交付前检查 | `pptist-visual-check` | 独立验收技能,只在用户明确要求检查/验收时加载 |
20
+ | 查资料、找数据、找配图 | `pptist-research` | 外部研究,产出分析卡 |
21
+
22
+ ## 加载顺序
23
+
24
+ - **改现有 deck**:默认只加载 `pptist-editing`。用户没说“美化/配色/版式”时不加载 `pptmaster-design`;用户没说“检查/审计/验收/截图”时不加载 `pptist-visual-check`。
25
+ - **从零做方案**:`pptmaster-narrative` → `pptist-authoring`。`pptist-authoring` 默认出 clean 简洁稿;审美定制再叠加 `pptmaster-design`,验收再叠加 `pptist-visual-check`。
26
+ - **只做审美**:`pptmaster-design`。
27
+ - **只做审计/截图**:`pptist-visual-check`。
28
+ - **带外部资料**:`pptist-research` → `pptmaster-narrative` → `pptist-authoring`(审美/审计仍按需)。
29
+
30
+ ## 交叉规则
31
+
32
+ - 叙事技能不调用工具;设计技能不调用工具;执行技能不做审美判断;审计技能不负责改稿。
33
+ - 先 `ppt_capabilities` 确认 contract 2.x 和编辑器状态;工具参数细节读 `pptist-api` 技能(安装包内随附构建生成的 `references/tool-contract.md`,源码树不保存该文件)。
34
+ - 用户说“背景/页面/幻灯片/标题”默认指右侧 PPTist 编辑器。
35
+ - 新建 deck 默认走内置简洁版式,不要另加装饰层。
36
+ - 编辑/成稿默认**不主动审计、不主动截图**;只有用户明确要求验收时,才由 `pptist-visual-check` 执行。
37
+ - 工具返回已含回执;改完一句话说明结果,不贴 JSON。
@@ -0,0 +1,61 @@
1
+ # PPTist 设计基线(按需读取)
2
+
3
+ ## 画布
4
+
5
+ - 尺寸:1000 × 562.5(16:9)。
6
+ - 安全边距:左右 ≥ 56,上 ≥ 44,下 ≥ 40。
7
+ - 标题区高度:64–96;正文区从 y=140 开始。
8
+
9
+ ## 字号
10
+
11
+ | 角色 | 字号 |
12
+ |---|---|
13
+ | 封面主标题 | 44–56 |
14
+ | 页面标题 | 36–44 |
15
+ | 章节过渡标题 | 40–48 |
16
+ | 正文 / 要点 | 18–22 |
17
+ | 注释 / 来源 | 11–13 |
18
+ | 大数字 | 56–72 |
19
+
20
+ 一页最多一个主标题;标题不超过两行。
21
+
22
+ ## 颜色
23
+
24
+ - 默认简洁路线使用内置 clean 主题色,不另造一套。
25
+ - 正文与背景对比度至少 4.5:1;深底用白字,浅底用深灰/黑。
26
+ - 强调色只占 10% 面积;不要整页高饱和。
27
+ - `ppt_recolor` 会同时处理背景、文字对比色和装饰形状,整体换色优先用它。
28
+
29
+ ## 版式
30
+
31
+ | 版式 | 结构 |
32
+ |---|---|
33
+ | 封面 | 主标题 + 副标题 + 讲者/日期;可加一条装饰线 |
34
+ | 目录 | 3–6 项,纵向排列,编号对齐 |
35
+ | 章节过渡 | 大编号 + 章节标题 + 一句引导 |
36
+ | 要点页 | 标题 + 3–4 条要点;每条不超过两行 |
37
+ | 两栏 | 左文右图或左图右文;两栏间距 ≥ 32 |
38
+ | 数据页 | 1–3 个大数字 + 一行解释;数字优先 |
39
+ | 引用页 | 一句引文 + 出处;引文不超过三行 |
40
+ | 结尾 | 一句收束 + 联系方式/致谢 |
41
+
42
+ ## 内容密度
43
+
44
+ - 每页一个信息点;不要把整段文字塞进一页。
45
+ - 每章 2–4 条要点;超过就拆章。
46
+ - 标题 ≤ 10 字,要点 ≤ 20 字,detail ≤ 30 字。
47
+ - 能用量化指标就不写形容词。
48
+
49
+ ## 图片
50
+
51
+ - 单张主图占 40–60% 画布宽度;不要与文字抢同一块区域。
52
+ - 保持原始宽高比;`ppt_image` 会自动等比缩放。
53
+ - 图片上压字时加半透明遮罩或改用纯色块。
54
+ - 外部图片先确认来源/授权;热链可能被编辑器拦截。
55
+
56
+ ## 交付检查
57
+
58
+ - 每页标题是否一眼可读?
59
+ - 是否有文字溢出/重叠/对比度不足?
60
+ - 整体版式与配色是否统一?
61
+ - 最后一页是否明确收束?