@capyclaw/ppt-skill-editing 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-editing(目录 dsh-ppt-skill-editing) bundle patch:只注册 pptist-editing 一个 skill provider。
2
+ - insert:
3
+ - id: ppt-skill-editing
4
+ name: @capyclaw/ppt-skill-editing
package/lib/index.mjs ADDED
@@ -0,0 +1,23 @@
1
+ /** @capyclaw/ppt-skill-editing(目录 dsh-ppt-skill-editing):单 skill provider。 */
2
+ import { fileURLToPath } from 'node:url'
3
+ import { createSkillProvider } from './provider.mjs'
4
+
5
+ export const name = 'ppt-skill-editing'
6
+ export const inject = ['skills']
7
+ const SKILL = "pptist-editing"
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-editing",
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-editing",
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:指哪打哪的修改执行器:每步落盘、不审美、不审计、不回头验证。",
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": "指哪打哪的修改执行器:每步落盘、不审美、不审计、不回头验证。"
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,64 @@
1
+ ---
2
+ name: pptist-editing
3
+ description: 纯执行型 PPT 修改技能:按用户点名的页/元素改文字、背景、配色、元素、页、图片;指哪打哪,不审美、不审计、不回头验证。
4
+ whenToUse: 用户要求修改现有演示文稿的任意内容或样式,例如换标题、改背景、整体换色、加一页、插入图片、删页、移动页时。
5
+ ---
6
+
7
+ # PPTist 纯执行器
8
+
9
+ 本技能只做一件事:**执行用户明确要求的修改**。不做设计判断,不做审美优化,不做审计验收,不替用户扩大范围。
10
+
11
+ ## 硬规则
12
+
13
+ 1. **指哪打哪**:只改用户点名的页/元素/属性;没点名的页不动,没要求改的样式不改,不顺手优化,不补装饰。
14
+ 2. **不思考审美**:不判断配色是否好看、版式是否更优、字号是否更协调;用户怎么说就怎么执行。
15
+ 3. **一步一落盘**:每完成一个独立步骤(一次用户诉求或一个明确的修改动作),立即调 `ppt_task action=checkpoint`,name 用稳定步名(如 `edit-bg-1`、`edit-title-2`);不要攒到最后一起存。
16
+ 4. **不回头看**:写完不读回 deck 复核,不调用审计/截图/验收类工具,不读回 deck 验证;工具返回成功即结束。
17
+ 5. **只读一次**:默认不读整个 deck;只有必须拿元素 id / 几何 / 页数时才调一次 `ppt_get_deck {detailed:true}`,用完即走。
18
+ 6. **一次做完**:单个修改能一次工具调用完成,就不拆成两次;多个独立诉求按顺序逐步执行、逐步 checkpoint。
19
+ 7. **token 是输入不是判断**:仅当本轮加载了设计技能并按用户要求落地其 token 时,把给定的 `accent`/`ink`/`paper`/`font_sans` 原样传给 `ppt_style`;不自行挑色、改字号或补 token。
20
+
21
+ ## 工具映射
22
+
23
+ | 用户要求 | 用什么 |
24
+ |---|---|
25
+ | 整体背景/配色/主题色 | `ppt_recolor` 一次完成 |
26
+ | 只改某页背景 | `ppt_style`(`background` + `slides`) |
27
+ | 改某页/某元素文字 | `ppt_text`(`slide` 或 `element_id`) |
28
+ | 加一页(标题 + 要点) | `ppt_slide`(`action:"add"`) |
29
+ | 删页/移动/复制/选中 | `ppt_slide` |
30
+ | 插入图片 | `ppt_image`(`attachment_id` / `path` / `src`) |
31
+ | 改位置/尺寸/旋转/填充 | `ppt_element`(`action:"update"`) |
32
+ | 加文本/形状 | `ppt_element`(`action:"add"`) |
33
+ | 填文字槽位 | `ppt_fill` |
34
+ | 每步落盘 | `ppt_task`(`action:"checkpoint"`) |
35
+ | 撤销/重做 | `ppt_history` |
36
+ | 专用工具覆盖不了 | `ppt_apply` 逃生口 |
37
+
38
+ ## 执行顺序
39
+
40
+ 1. 拆出用户明确要求的动作;只保留这些动作。
41
+ 2. 需要定位时读一次 deck;不需要就直接调专用工具。
42
+ 3. 执行;成功后立即 checkpoint。
43
+ 4. 所有动作完成后一句话汇报:改了哪几页/哪些元素。不要贴 JSON,不要列调用日志。
44
+
45
+ ## 图片
46
+
47
+ - 用户贴图说“插入这张图片”:直接 `ppt_image {attachment_id, slide}`,一次完成。
48
+ - `attachment_id` 从用户消息的 image 附件取(`sha256:…`)。
49
+ - 用户没指定页时默认第 0 页(封面),不要追问。
50
+ - 不要用 shell/glob 找附件,不要用 `ppt_element` 传本机路径。
51
+ - 按工具返回值判断成功/失败;失败就如实说明,不要重复插入,不要额外读 deck 复核。
52
+
53
+ ## 错误处理
54
+
55
+ - 编辑器未连接:提示用户打开 `editorUrl`,页面加载后自动连接;不要循环重试。
56
+ - 工具报“未知指令”:说明编辑器版本旧;如实告知用户刷新/升级,不要换工具硬试。
57
+ - 页号越界:只有在报错时才读一次 deck 确认真实页数,不要提前猜。
58
+
59
+ ## 禁止
60
+
61
+ - 不调用审计、截图、设计/审美类技能或工具。
62
+ - 不做全局换色,除非用户明确要求整体换色。
63
+ - 不因为“看起来不够丰富”自行加形状、装饰、图标或额外页面。
64
+ - 不把用户的单点修改扩展成整套 deck 重排。