@ninkch/dsh-infinite-canvas 0.19.6 → 0.19.7
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/NOTICE +35 -0
- package/README.md +4 -3
- package/README.zh-CN.md +4 -3
- package/assets/canvas-director/SKILL.md +7 -7
- package/assets/canvas-director/genres/absurd-parable.md +85 -0
- package/assets/canvas-director/genres/action-mission.md +5 -0
- package/assets/canvas-director/genres/adventure.md +5 -0
- package/assets/canvas-director/genres/broken-mirror-reunion.md +5 -0
- package/assets/canvas-director/genres/class-vertical.md +85 -0
- package/assets/canvas-director/genres/counterattack.md +5 -0
- package/assets/canvas-director/genres/county-drift.md +85 -0
- package/assets/canvas-director/genres/crime-police.md +5 -0
- package/assets/canvas-director/genres/family-melodrama.md +85 -0
- package/assets/canvas-director/genres/family-relation.md +5 -0
- package/assets/canvas-director/genres/fantasy.md +5 -0
- package/assets/canvas-director/genres/gambling-den.md +85 -0
- package/assets/canvas-director/genres/gunstandoff.md +85 -0
- package/assets/canvas-director/genres/hk-retro.md +5 -0
- package/assets/canvas-director/genres/identity-swap.md +5 -0
- package/assets/canvas-director/genres/institutional-eccentric.md +85 -0
- package/assets/canvas-director/genres/mo-lei-tau.md +85 -0
- package/assets/canvas-director/genres/modern-urban.md +5 -0
- package/assets/canvas-director/genres/multi-thread-caper.md +85 -0
- package/assets/canvas-director/genres/neon-dystopia.md +85 -0
- package/assets/canvas-director/genres/parent-child-secret.md +5 -0
- package/assets/canvas-director/genres/period-power-struggle.md +5 -0
- package/assets/canvas-director/genres/rebirth-transmigration.md +5 -0
- package/assets/canvas-director/genres/revenge-face-slap.md +5 -0
- package/assets/canvas-director/genres/slice-of-life.md +5 -0
- package/assets/canvas-director/genres/smalltown-uncanny.md +85 -0
- package/assets/canvas-director/genres/social-traverse.md +85 -0
- package/assets/canvas-director/genres/survival-fate.md +85 -0
- package/assets/canvas-director/genres/suspense-rule.md +5 -0
- package/assets/canvas-director/genres/system-flow.md +5 -0
- package/assets/canvas-director/genres/wasteland-chase.md +85 -0
- package/assets/canvas-director/genres/wealthy-romance.md +5 -0
- package/assets/canvas-director/genres/workplace-comedy.md +5 -0
- package/assets/canvas-director/genres/wuxia.md +5 -0
- package/assets/canvas-director/genres/xianxia-cultivation.md +5 -0
- package/assets/canvas-director/genres/youth-distance.md +85 -0
- package/assets/canvas-director/lenses/alejandro_inarritu.md +248 -0
- package/assets/canvas-director/lenses/alfonso_cuaron.md +241 -0
- package/assets/canvas-director/lenses/ang_lee.md +223 -0
- package/assets/canvas-director/lenses/bi_gan.md +5 -1
- package/assets/canvas-director/lenses/bong_joon_ho.md +236 -0
- package/assets/canvas-director/lenses/coen_brothers.md +9 -6
- package/assets/canvas-director/lenses/david_lynch.md +243 -0
- package/assets/canvas-director/lenses/edward_yang.md +223 -0
- package/assets/canvas-director/lenses/fincher.md +5 -1
- package/assets/canvas-director/lenses/george_miller.md +251 -0
- package/assets/canvas-director/lenses/guy_ritchie.md +252 -0
- package/assets/canvas-director/lenses/hou_hsiao_hsien.md +9 -6
- package/assets/canvas-director/lenses/jia_zhangke.md +231 -0
- package/assets/canvas-director/lenses/jiang_wen.md +232 -0
- package/assets/canvas-director/lenses/johnnie_to.md +234 -0
- package/assets/canvas-director/lenses/koreeda_hirokazu.md +242 -0
- package/assets/canvas-director/lenses/lee_chang_dong.md +247 -0
- package/assets/canvas-director/lenses/malick.md +8 -5
- package/assets/canvas-director/lenses/michael_mann.md +10 -7
- package/assets/canvas-director/lenses/miyazaki_hayao.md +247 -0
- package/assets/canvas-director/lenses/ning_hao.md +235 -0
- package/assets/canvas-director/lenses/nolan.md +4 -1
- package/assets/canvas-director/lenses/park_chan_wook.md +8 -5
- package/assets/canvas-director/lenses/pedro_almodovar.md +251 -0
- package/assets/canvas-director/lenses/refn.md +4 -1
- package/assets/canvas-director/lenses/ridley_scott.md +241 -0
- package/assets/canvas-director/lenses/shinkai_makoto.md +246 -0
- package/assets/canvas-director/lenses/stephen_chow.md +238 -0
- package/assets/canvas-director/lenses/tarkovsky.md +5 -1
- package/assets/canvas-director/lenses/villeneuve.md +6 -2
- package/assets/canvas-director/lenses/wes_anderson.md +249 -0
- package/assets/canvas-director/lenses/wong_jing.md +233 -0
- package/assets/canvas-director/lenses/wong_kar_wai.md +4 -1
- package/assets/canvas-director/lenses/zhang_yimou.md +8 -5
- package/assets/canvas-director/looks/baroque-rich.md +74 -0
- package/assets/canvas-director/looks/cold-noir.md +74 -0
- package/assets/canvas-director/looks/earth-warmth.md +74 -0
- package/assets/canvas-director/looks/minimal-grey.md +73 -0
- package/assets/canvas-director/looks/saturated-epic.md +73 -0
- package/assets/canvas-director/looks/soft-romance.md +74 -0
- package/assets/canvas-director/manifest.json +672 -130
- package/assets/canvas-director/pack.js +1 -0
- package/assets/canvas-studio/SKILL.md +1 -1
- package/client.js +64 -7
- package/cordis.patch.yml +171 -0
- package/docs/guide.en.md +39 -39
- package/docs/guide.zh-CN.md +39 -39
- package/package.json +78 -66
- package/skill-pack-loader.js +1 -0
- package/skill-pack.js +1 -1
- package/skills/director-workshop/SKILL.md +128 -0
- package/skills.js +342 -0
package/package.json
CHANGED
|
@@ -1,70 +1,82 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
2
|
+
"name": "@ninkch/dsh-infinite-canvas",
|
|
3
|
+
"version": "0.19.7",
|
|
4
|
+
"description": "无限画布(Infinite Canvas)for DeepSeek Harness:节点式创作工作室(剧本/分镜/生成/成片),内置「媒体创作」预设模式(导演第一视角 + 引导工作坊)。Version-agnostic: reads the live catalog from the installed package at load time.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"dsh",
|
|
7
|
+
"deepseek-harness",
|
|
8
|
+
"plugin",
|
|
9
|
+
"infinite-canvas",
|
|
10
|
+
"storyboard",
|
|
11
|
+
"short-drama",
|
|
12
|
+
"ai-video",
|
|
13
|
+
"image-generation",
|
|
14
|
+
"video-generation",
|
|
15
|
+
"seedance",
|
|
16
|
+
"photocraft",
|
|
17
|
+
"filmcraft"
|
|
18
|
+
],
|
|
19
|
+
"license": "Apache-2.0",
|
|
20
|
+
"author": "NINKCH",
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"registry": "https://registry.npmjs.org/"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/NINKCH/dsh-infinite_canvas.git"
|
|
27
|
+
},
|
|
28
|
+
"type": "module",
|
|
29
|
+
"icon": "./icon.svg",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": "./index.js",
|
|
32
|
+
"./client": "./client.js",
|
|
33
|
+
"./package.json": "./package.json",
|
|
34
|
+
"./locale/*.json": "./locale/*.json",
|
|
35
|
+
"./assets/director/*.js": "./assets/director/*.js",
|
|
36
|
+
"./assets/canvas-director/*.js": "./assets/canvas-director/*.js",
|
|
37
|
+
"./skills": "./skills.js"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"index.js",
|
|
41
|
+
"client.js",
|
|
42
|
+
"icon.svg",
|
|
43
|
+
"cordis.patch.yml",
|
|
44
|
+
"locale/*.json",
|
|
45
|
+
"assets/*/SKILL.md",
|
|
46
|
+
"assets/support/*",
|
|
47
|
+
"assets/director/*.js",
|
|
48
|
+
"assets/director/models/*.json",
|
|
49
|
+
"assets/director/clips/*.json",
|
|
50
|
+
"NOTICE",
|
|
51
|
+
"README.md",
|
|
52
|
+
"README.zh-CN.md",
|
|
53
|
+
"docs/*.md",
|
|
54
|
+
"docs/images/*",
|
|
55
|
+
"skill-pack.js",
|
|
56
|
+
"skill-pack-loader.js",
|
|
57
|
+
"index-core-skill-profile.js",
|
|
58
|
+
"assets/canvas-director/**",
|
|
59
|
+
"skills.js",
|
|
60
|
+
"skills/**"
|
|
61
|
+
],
|
|
62
|
+
"dsh": {
|
|
63
|
+
"bundle": {
|
|
64
|
+
"patch": "./cordis.patch.yml"
|
|
23
65
|
},
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"files": [
|
|
39
|
-
"index.js",
|
|
40
|
-
"client.js",
|
|
41
|
-
"icon.svg",
|
|
42
|
-
"cordis.patch.yml",
|
|
43
|
-
"locale/*.json",
|
|
44
|
-
"assets/*/SKILL.md",
|
|
45
|
-
"assets/support/*",
|
|
46
|
-
"assets/director/*.js",
|
|
47
|
-
"assets/director/models/*.json",
|
|
48
|
-
"assets/director/clips/*.json",
|
|
49
|
-
"README.md",
|
|
50
|
-
"README.zh-CN.md",
|
|
51
|
-
"docs/*.md",
|
|
52
|
-
"docs/images/*",
|
|
53
|
-
"skill-pack.js",
|
|
54
|
-
"skill-pack-loader.js",
|
|
55
|
-
"index-core-skill-profile.js",
|
|
56
|
-
"assets/canvas-director/**"
|
|
57
|
-
],
|
|
58
|
-
"dsh": {
|
|
59
|
-
"bundle": {
|
|
60
|
-
"patch": "./cordis.patch.yml"
|
|
61
|
-
},
|
|
62
|
-
"client": {
|
|
63
|
-
"platform": "web",
|
|
64
|
-
"immediately": true
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
"devDependencies": {
|
|
68
|
-
"fbx2gltf": "^0.9.7-p1"
|
|
66
|
+
"client": {
|
|
67
|
+
"platform": "web",
|
|
68
|
+
"immediately": true
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"fbx2gltf": "^0.9.7-p1"
|
|
73
|
+
},
|
|
74
|
+
"peerDependencies": {
|
|
75
|
+
"@ninkch/dsh-artcraft": ">=0.3.0"
|
|
76
|
+
},
|
|
77
|
+
"peerDependenciesMeta": {
|
|
78
|
+
"@ninkch/dsh-artcraft": {
|
|
79
|
+
"optional": true
|
|
69
80
|
}
|
|
81
|
+
}
|
|
70
82
|
}
|
package/skill-pack-loader.js
CHANGED
|
@@ -113,6 +113,7 @@ export function packInventory(hereDir) {
|
|
|
113
113
|
group: String(it.group || ''),
|
|
114
114
|
oneliner: String(it.oneliner || ''),
|
|
115
115
|
tier: String(it.tier || ''),
|
|
116
|
+
aliases: Array.isArray(it.aliases) ? it.aliases.map(String) : [],
|
|
116
117
|
glyph: String(it.glyph || it.id || ''),
|
|
117
118
|
file: String(it.file || ''),
|
|
118
119
|
available: !!readCard(base, layer, it.file),
|
package/skill-pack.js
CHANGED
|
@@ -148,7 +148,7 @@ export function renderInventory(inv, profile, zh) {
|
|
|
148
148
|
byGroup.get(g).push(it);
|
|
149
149
|
}
|
|
150
150
|
for (const [g, arr] of byGroup) {
|
|
151
|
-
lines.push((g ? g + ':' : '') + arr.map((x) => x.id + '=' + x.name + (x.available ? '' : '(缺文件)')).join(' '));
|
|
151
|
+
lines.push((g ? g + ':' : '') + arr.map((x) => x.id + '=' + x.name + ((x.aliases && x.aliases.length) ? '(' + x.aliases.join('/') + ')' : '') + (x.available ? '' : '(缺文件)')).join(' '));
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
const mv = (inv && inv.moves) || [];
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: director-workshop
|
|
3
|
+
description: 导演工作坊——媒体创作模式的引导式创作流程。从"选导演"到"交付成片",每一步给菜单、讲原理、一次只做一个决定。适用于:进入媒体创作模式、想拍短剧/视频但不知从哪下手、或说"带我做/引导我创作"时。
|
|
4
|
+
whenToUse: 用户进入媒体创作模式、表达创作意图(拍片/短剧/视频/分镜)、或面对空白页不知如何开始时,先加载本技能再动手。
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 导演工作坊(Director Workshop)
|
|
8
|
+
|
|
9
|
+
你现在是一位驻组导演,用户是你的监制兼学徒。你的职责不只是替他做,而是**带着他做**:每一步先给菜单、再讲一句"为什么"、让他做决定、你负责执行与专业兜底。
|
|
10
|
+
|
|
11
|
+
> 本技能随「媒体创作」预设(@ninkch/dsh-infinite-canvas 内置)分发:装好包即自动注册,改本文件即改所有机器上的引导话术。
|
|
12
|
+
>
|
|
13
|
+
> **版本无关性**:本正文不写任何份数、名单或绝对路径——它们都会随上游插件版本或安装位置变化。菜单一律现场调 `canvas_list_skills` / `artcraft_recipes` 取。若本文件末尾附了《当期清单快照》,那是 skills.js 在你加载时实时读取上游清单生成的,以它为准;读不到时不附,忽略即可。
|
|
14
|
+
|
|
15
|
+
## 铁律(全程有效)
|
|
16
|
+
|
|
17
|
+
1. **一次只问一个问题**。用 ask_user_question 给 2–4 个选项,每个选项必须带一行说明(它意味着什么/适合什么)。永远允许用户自定义输入。
|
|
18
|
+
2. **先菜单,后代劳**。只有用户说"你定/听你的"时才可以代选,且必须说明理由。**菜单必须是工具返回的当期清单**,不是记忆里的旧清单。
|
|
19
|
+
3. **阶段小结**。每完成一个阶段,用 3–5 行汇报:✅ 已定(列决定)/ ⏭️ 下一步是什么,用户确认后才进下一阶段。
|
|
20
|
+
4. **术语即教即用**。第一次出现专业词(ASL、景别、轴线、J-cut、LUFS…)时,用一句大白话解释。
|
|
21
|
+
5. **决定即落盘**。每个创作决定立刻写进画布(canvas_update_bible 设定集 / canvas_skill_pack set:true 项目配置),不要只停留在对话里。
|
|
22
|
+
6. **用户卡壳时降级**:给一个"最稳的默认组合"让他确认,而不是让他面对空白。
|
|
23
|
+
7. **取到什么说什么(版本无关)**。本技能刻意不写死任何份数与名单——导演透镜、题材、规格、影调、编排链的集合都会随上游插件版本增长。凡是"现在有哪些 / 多少条 / 什么规格"的问题,一律现场调 `canvas_list_skills` / `artcraft_recipes` 取当期答案;工具没返回就说不确定并去调它,**绝不拿旧版本记忆充数**。本文件末尾可能附有一份由 skills.js 在加载时实时读取上游清单生成的《当期清单快照》,有就以它为准,没有忽略即可。
|
|
24
|
+
|
|
25
|
+
## 阶段 0 · 启动仪式(每次新项目必做)
|
|
26
|
+
|
|
27
|
+
**先自检,再问起点**(自检不通过就不要往下走):
|
|
28
|
+
|
|
29
|
+
1. 各调一次 `canvas_list_skills` 与 `artcraft_status`。
|
|
30
|
+
2. 三档结论,如实说、不猜版本:
|
|
31
|
+
- 两者正常 → 继续启动仪式。
|
|
32
|
+
- `canvas_*` 缺失 → 告诉用户需要先安装 `@ninkch/dsh-infinite-canvas`(profile 级 bundle),然后停。**不要用文字假装建项目、不要用对话模拟分镜、不要凭空描述"已生成的画面"。**
|
|
33
|
+
- `artcraft_*` 缺失 → 剧本/分镜/生成照做,但走到阶段 7 必须停下来说明缺 `@ninkch/dsh-artcraft`,不得宣称已交付成片。
|
|
34
|
+
|
|
35
|
+
然后问起点(三选一):
|
|
36
|
+
- **A 从零开始** —— 走完整流程(阶段 1 起)
|
|
37
|
+
- **B 有个想法** —— 先听他用两三句话讲完,你复述成 logline 给他确认,跳到阶段 2
|
|
38
|
+
- **C 已有剧本/画布项目** —— canvas_list_projects 查重后接管,从阶段 5 或 7 续做
|
|
39
|
+
|
|
40
|
+
同时展示一张"全景地图"让他知道要经过哪几站:
|
|
41
|
+
> 选导演 → 定题材规格影调 → 一句话故事 → 分场剧本 → 分镜 → 生成 → 后期成片 → 质检交付
|
|
42
|
+
|
|
43
|
+
## 阶段 1 · 选导演(创作四件套第一件)
|
|
44
|
+
|
|
45
|
+
1. **先取清单,不要凭记忆背名单**:调 `canvas_list_skills`(不传 projectId)。
|
|
46
|
+
画布插件的透镜集会随版本增长,本技能有意不在正文里写死份数与人名——你也不许替它记。
|
|
47
|
+
2. **按返回值的 group 分组展示**:分组名(如华语 / 日韩 / 欧美)由插件给出,直接沿用,**不要自己重排或合并**。每人一行、用 `id=名字` 的形态,让用户可以直接报 id。
|
|
48
|
+
3. **每人补一句风格签名 + 一句"适合拍什么"**:清单只有名字时,调 `canvas_skill_pack({ kind: 'prompt', lens: '<id>' })` 取该透镜切片开头再概括成人话。能一句话说清就别整份倒给用户;不要为了凑签名去编造参数。
|
|
49
|
+
4. 用户选定后**立刻执行三件事**:
|
|
50
|
+
- `canvas_skill_pack(kind=script, lens=<id>, set:true)` 把透镜写进项目
|
|
51
|
+
- 加载 canvas-director 技能确认透镜完整参数
|
|
52
|
+
- 产出**《导演合作指南》**给用户(见下)
|
|
53
|
+
|
|
54
|
+
### 《导演合作指南》模板(选完导演必给)
|
|
55
|
+
|
|
56
|
+
> **和「<用户选定的导演>」合作拍戏,你会得到 / 他会要求:**
|
|
57
|
+
> ① 他的眼睛 —— 构图与机位偏好(如:广角贴脸手持、前景遮挡、失焦呼吸)
|
|
58
|
+
> ② 他的钟 —— 镜长与节奏(如:ASL 2–4 秒碎剪 + 突然 10 秒凝视;抽帧降格)
|
|
59
|
+
> ③ 他的光与色 —— 色板、光比、质感(如:高饱和霓虹绿红、低调光、颗粒感)
|
|
60
|
+
> ④ 他的声音 —— 对白/音乐/静默的使用(如:画外独白贯穿、音乐先于画面进)
|
|
61
|
+
> ⑤ 五条拍摄军规 —— 翻译成可执行参数(分镜怎么写、FilmCraft Lumetri 怎么调、SoundCraft 怎么混)
|
|
62
|
+
> ⑥ 禁区 —— 他绝不会做的事(如:不用大平光、不写解释性对白)
|
|
63
|
+
>
|
|
64
|
+
> 六节内容全部来自该透镜切片的实际内容(`canvas_skill_pack` 返回),不要套模板空话;切片里没写的节,就说"该透镜未给出这一项",不补造。
|
|
65
|
+
|
|
66
|
+
这份指南是后续每一阶段的"口味基准":写剧本、拆分镜、调色、混音前都回看它。
|
|
67
|
+
|
|
68
|
+
## 阶段 2 · 定题材 / 规格 / 影调(四件套其余三件)
|
|
69
|
+
|
|
70
|
+
**清单同样现场取,不背**:`canvas_list_skills` 的返回里,题材(含叙事与行业两类)、规格、影调各有一节,每节带份数与 `id=名字`;按它给选项。
|
|
71
|
+
|
|
72
|
+
每项先用一句话说清"它管什么"再给选项:
|
|
73
|
+
- **题材**(管观众为什么上头):叙事题材管压力机制与集尾钩子,行业题材管产品/服务类片子的证据与质感。份数以清单为准。
|
|
74
|
+
- **类型规格**(管结构块与节奏):决定结构块、ASL 下限与质量档;**画幅不归它管**(画幅在视频节点生成时选)。
|
|
75
|
+
- **影调质感**(管画面成什么样):决定成像底层与材质;默认跟随导演透镜。
|
|
76
|
+
|
|
77
|
+
不要用记忆里的旧名单举例;清单里有哪些就给哪些,用户点名清单里没有的,去 `canvas_skill_pack` 确认后再答复。
|
|
78
|
+
|
|
79
|
+
给一个"推荐组合"(导演×题材×规格×影调)并解释为什么搭,用户确认后 `canvas_skill_pack(..., set:true)` 一次写入。
|
|
80
|
+
|
|
81
|
+
## 阶段 3 · 一句话故事(logline)
|
|
82
|
+
|
|
83
|
+
用模板引导,缺哪项问哪项:
|
|
84
|
+
> **【主角】**为了**【欲望】**,必须**【行动】**,但**【障碍】**,若失败则**【赌注】**;转折在于**【反转】**。
|
|
85
|
+
|
|
86
|
+
确认后写入剧本节点的创作意图(idea),并给出分集/分段结构提案(短剧默认:前 3 秒钩子 → 每 30 秒一个反转 → 卡点结尾)。
|
|
87
|
+
|
|
88
|
+
## 阶段 4 · 分场剧本(逐场引导)
|
|
89
|
+
|
|
90
|
+
每场只问四件事,别贪多:
|
|
91
|
+
1. 这场戏的**情绪目标**是什么?(让观众感到什么)
|
|
92
|
+
2. **谁想要什么**,**谁在阻止**?
|
|
93
|
+
3. 结尾的**钩子/转折**是什么?
|
|
94
|
+
4. 这场多少秒?(用导演的钟算)
|
|
95
|
+
|
|
96
|
+
用户答完 → 你按 canvas-craft 的四要素成稿 → `canvas_write_node` 写入 → 给他看,确认后下一场。每写完 3 场提醒一次:"要不要先回头通读一遍再继续?"
|
|
97
|
+
|
|
98
|
+
## 阶段 5 · 分镜(把文字翻译成镜头)
|
|
99
|
+
|
|
100
|
+
先教"三连问"再拆镜:
|
|
101
|
+
1. 这场让观众**感觉**什么?(决定景别:贴脸=近景/特写,命运感=大远景)
|
|
102
|
+
2. 观众的**眼睛该看哪**?(决定机位角度与构图)
|
|
103
|
+
3. 镜头**跟着谁动/为什么动**?(决定运动:跟拍/推拉/固定)
|
|
104
|
+
|
|
105
|
+
每场给 2–3 个镜头方案让用户挑(如"方案A 手持贴脸 3 秒×6 切 / 方案B 一镜到底 18 秒缓推"),讲清各自的代价。拆镜用 type=storyboard 节点、connectFrom 连上游剧本;六要素(景别/机位/运动/画面/台词/时长)齐了才算过。加载 artcraft-shot-language 技能兜底专业判断。
|
|
106
|
+
|
|
107
|
+
## 阶段 6 · 生成与验收
|
|
108
|
+
|
|
109
|
+
- 触发生成(canvas_generate_node / canvas_run_node),后台跑,不忙轮询。
|
|
110
|
+
- 出图/出片后**先自查再给用户**:一致性 warnings 逐条补设定集;抽帧看角色脸、场景、道具是否跑偏。
|
|
111
|
+
- 给用户看时附"验收要点":让他按 3 条具体的点看(如"看女主发色是否一致/看第 4 镜有没有穿帮"),而不是问"你觉得怎么样"。
|
|
112
|
+
|
|
113
|
+
## 阶段 7 · 后期成片与交付
|
|
114
|
+
|
|
115
|
+
1. `artcraft_status` / `artcraft_live` 探活 → 先 `artcraft_recipes(action=chains)` 看当期链清单,再取竖屏短剧成片链(当前 id 为 short-drama-episode;**以当期清单为准**,id 不在就用名称最接近的成片链)。
|
|
116
|
+
2. **按返回的 steps 逐步执行**:每步的 `app` / `cmd` / `params` 以返回值为准,本技能不写死步骤数与顺序(链 id 与步骤都会随 ArtCraft 版本变化)。返回里带 `verify` 的,按它逐条验收。
|
|
117
|
+
3. `artcraft_qc` 质检,PASS 前不宣称完成;只报事实与实测值,判定留给用户。
|
|
118
|
+
4. 交付时给"三件套":成片路径 + 质检报告摘要 + 下一集/多平台分发包(social-pack)提议。
|
|
119
|
+
|
|
120
|
+
## 出口检查表(全部完成后自查)
|
|
121
|
+
|
|
122
|
+
- [ ] 开工自检已做,缺失依赖已如实告知(未假装成功)
|
|
123
|
+
- [ ] 四件套已 set 进项目配置,且四项都是从当期清单选的
|
|
124
|
+
- [ ] 设定集含世界观/风格/全部出场角色场景道具
|
|
125
|
+
- [ ] 每场剧本有钩子,分镜六要素齐全
|
|
126
|
+
- [ ] 视频全部生成且一致性告警清零
|
|
127
|
+
- [ ] 成片 QC PASS,响度/画幅/字幕安全区达标(以 artcraft_qc 实测为准)
|
|
128
|
+
- [ ] 用户拿到了成片 + 报告 + 下一步建议
|
package/skills.js
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ninkch/dsh-infinite-canvas — 技能提供者(媒体创作预设随本包分发)
|
|
3
|
+
*
|
|
4
|
+
* 模式沿用 @ninkch/dsh-artcraft/skills:`inject: ['skills']` 是硬依赖,
|
|
5
|
+
* 单独成行,即使某环境没有 skills 服务也只影响本行,不拖垮预设其余部分。
|
|
6
|
+
*
|
|
7
|
+
* 技能来源:包内 skills/<name>/SKILL.md(YAML frontmatter: name + description)。
|
|
8
|
+
* rank 600 与内置技能同级 —— 项目级(100/200)与用户级(400/500)技能可覆盖同名技能。
|
|
9
|
+
* 随包分发:装好本包 = 导演工作坊引导技能自动就位,无需手工拷贝到 ~/.dsh/skills。
|
|
10
|
+
*
|
|
11
|
+
* ── 版本无关性(v1.2.0 起)───────────────────────────────────────────────
|
|
12
|
+
* 预设正文不写透镜/题材/规格/影调/编排链的份数与名单:它们会随
|
|
13
|
+
* @ninkch/dsh-infinite-canvas 与 @ninkch/dsh-artcraft 的版本增长。
|
|
14
|
+
* 本文件在**加载技能时**用 require.resolve 定位这两个已安装的包,实时读取
|
|
15
|
+
*
|
|
16
|
+
* <canvas>/assets/canvas-director/manifest.json → lenses/genres/industries/forms/looks/moves
|
|
17
|
+
* <artcraft>/recipes/recipes.json → chains/recipes
|
|
18
|
+
*
|
|
19
|
+
* 并把当期清单作为附录拼在技能正文末尾。读不到(包未装、版本过旧、路径异常)时
|
|
20
|
+
* **静默省略,绝不抛错**——技能本体永远可用。
|
|
21
|
+
*/
|
|
22
|
+
import fs from 'node:fs/promises';
|
|
23
|
+
import fsSync from 'node:fs';
|
|
24
|
+
import os from 'node:os';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
import { createRequire } from 'node:module';
|
|
27
|
+
import { fileURLToPath } from 'node:url';
|
|
28
|
+
|
|
29
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
const SKILLS_DIR = path.join(HERE, 'skills');
|
|
31
|
+
const PROVIDER = 'media-studio';
|
|
32
|
+
const RANK = 600;
|
|
33
|
+
|
|
34
|
+
const CANVAS_PKG = '@ninkch/dsh-infinite-canvas';
|
|
35
|
+
const ARTCRAFT_PKG = '@ninkch/dsh-artcraft';
|
|
36
|
+
|
|
37
|
+
/** 附录默认上限(字符)。技能正文约 2.6k,DSH 工具结果修剪阈值 8192,
|
|
38
|
+
* 故附录留到 5000 以内,保证"正文+附录"整体不被修剪截断。 */
|
|
39
|
+
const DEFAULT_SNAPSHOT_MAX = 5000;
|
|
40
|
+
|
|
41
|
+
const CANVAS_LAYERS = [
|
|
42
|
+
['lenses', '导演透镜'],
|
|
43
|
+
['genres', '叙事题材'],
|
|
44
|
+
['industries', '行业题材'],
|
|
45
|
+
['forms', '规格'],
|
|
46
|
+
['looks', '影调质感'],
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
/** Cordis 插件名 */
|
|
50
|
+
export const name = 'media-studio-skills';
|
|
51
|
+
/** 需要技能服务 */
|
|
52
|
+
export const inject = ['skills'];
|
|
53
|
+
|
|
54
|
+
/** 解析 SKILL.md 的 YAML frontmatter(只取扁平的 name/description 标量,够用且不引入依赖) */
|
|
55
|
+
function parseSkill(raw) {
|
|
56
|
+
const m = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/u.exec(raw);
|
|
57
|
+
if (!m) return null;
|
|
58
|
+
const meta = {};
|
|
59
|
+
for (const line of m[1].split(/\r?\n/)) {
|
|
60
|
+
const kv = /^([A-Za-z_][\w-]*)\s*:\s*(.+)$/.exec(line);
|
|
61
|
+
if (!kv) continue;
|
|
62
|
+
let v = kv[2].trim();
|
|
63
|
+
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) v = v.slice(1, -1);
|
|
64
|
+
meta[kv[1]] = v;
|
|
65
|
+
}
|
|
66
|
+
if (!meta.name || !meta.description) return null;
|
|
67
|
+
return { name: meta.name, description: meta.description, content: raw.slice(m[0].length).trim() };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** 扫描包内技能目录,产出候选清单 */
|
|
71
|
+
async function loadCatalog() {
|
|
72
|
+
const out = [];
|
|
73
|
+
let entries = [];
|
|
74
|
+
try {
|
|
75
|
+
entries = await fs.readdir(SKILLS_DIR, { withFileTypes: true });
|
|
76
|
+
} catch {
|
|
77
|
+
return out; // 无 skills/ 目录 → 空目录(不报错)
|
|
78
|
+
}
|
|
79
|
+
for (const ent of entries) {
|
|
80
|
+
if (!ent.isDirectory()) continue;
|
|
81
|
+
const file = path.join(SKILLS_DIR, ent.name, 'SKILL.md');
|
|
82
|
+
try {
|
|
83
|
+
const raw = await fs.readFile(file, 'utf8');
|
|
84
|
+
const parsed = parseSkill(raw);
|
|
85
|
+
if (!parsed) continue;
|
|
86
|
+
out.push({
|
|
87
|
+
name: parsed.name,
|
|
88
|
+
description: parsed.description,
|
|
89
|
+
invocation: { modelInvocable: true, userInvocable: true },
|
|
90
|
+
provider: PROVIDER,
|
|
91
|
+
source: 'bundled',
|
|
92
|
+
rank: RANK,
|
|
93
|
+
resourceBase: { kind: 'directory', path: path.dirname(file) },
|
|
94
|
+
locator: file,
|
|
95
|
+
});
|
|
96
|
+
} catch { /* 单个技能读取失败不影响其余 */ }
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ── 上游清单快照 ────────────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
/** require.resolve 的解析锚点:包自身 → 往上逐级找 node_modules(覆盖 profile/开发目录两种安装形态)。 */
|
|
104
|
+
function resolveAnchors() {
|
|
105
|
+
const anchors = [path.join(HERE, 'package.json')];
|
|
106
|
+
let dir = HERE;
|
|
107
|
+
for (let i = 0; i < 6; i += 1) {
|
|
108
|
+
dir = path.dirname(dir);
|
|
109
|
+
if (!dir || dir === path.dirname(dir)) break;
|
|
110
|
+
anchors.push(path.join(dir, 'package.json'));
|
|
111
|
+
}
|
|
112
|
+
return anchors;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 定位已安装的上游包目录;失败返回 null(不抛错)。
|
|
117
|
+
*
|
|
118
|
+
* 覆盖三种安装形态:
|
|
119
|
+
* A. 预设与上游同在 profile 的 node_modules(npm 安装)—— 锚点向上 6 级必命中;
|
|
120
|
+
* B. 预设为 link:(本地开发)、上游也在同一工作区 —— 向上查找同样覆盖;
|
|
121
|
+
* C. 预设 npm 安装、上游为 link: 指到别处 —— 上面的锚点走不出去,
|
|
122
|
+
* 再从 DSH 标准目录(环境变量 DSH_HOME 或主目录下的 .dsh,及其每个
|
|
123
|
+
* profile 的 node_modules)兜底。
|
|
124
|
+
* 目录一律来自运行时环境,不写死任何机器路径。
|
|
125
|
+
*/
|
|
126
|
+
function resolvePkgDir(pkgName) {
|
|
127
|
+
const spec = `${pkgName}/package.json`;
|
|
128
|
+
for (const anchor of resolveAnchors()) {
|
|
129
|
+
try {
|
|
130
|
+
return path.dirname(createRequire(anchor).resolve(spec));
|
|
131
|
+
} catch { /* 换下一个锚点 */ }
|
|
132
|
+
}
|
|
133
|
+
// C 形态兜底:扫描 DSH 标准目录下的 profile node_modules(同步、一次性、量级为个位数)
|
|
134
|
+
try {
|
|
135
|
+
const home = process.env.DSH_HOME || path.join(os.homedir(), '.dsh');
|
|
136
|
+
const profiles = path.join(home, 'profiles');
|
|
137
|
+
for (const name of fsSync.readdirSync(profiles)) {
|
|
138
|
+
const nm = path.join(profiles, name, 'node_modules');
|
|
139
|
+
const direct = path.join(nm, ...pkgName.split('/'));
|
|
140
|
+
try {
|
|
141
|
+
if (fsSync.statSync(path.join(direct, 'package.json')).isFile()) return direct;
|
|
142
|
+
} catch { /* 该 profile 没有此包 */ }
|
|
143
|
+
}
|
|
144
|
+
} catch { /* 无 DSH 目录或不可读 */ }
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function readJson(file) {
|
|
149
|
+
try {
|
|
150
|
+
return JSON.parse(await fs.readFile(file, 'utf8'));
|
|
151
|
+
} catch {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function readVersion(pkgDir) {
|
|
157
|
+
const pkg = pkgDir ? await readJson(path.join(pkgDir, 'package.json')) : null;
|
|
158
|
+
return (pkg && typeof pkg.version === 'string') ? pkg.version : '';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function toList(v) {
|
|
162
|
+
return Array.isArray(v) ? v : [];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** `id=名字`,或仅 `id`。 */
|
|
166
|
+
function renderItems(list, withName) {
|
|
167
|
+
return list
|
|
168
|
+
.map((x) => {
|
|
169
|
+
const id = x && x.id != null ? String(x.id) : '';
|
|
170
|
+
if (!id) return '';
|
|
171
|
+
if (!withName) return id;
|
|
172
|
+
const nm = x && x.name != null ? String(x.name) : '';
|
|
173
|
+
return nm ? `${id}=${nm}` : id;
|
|
174
|
+
})
|
|
175
|
+
.filter(Boolean)
|
|
176
|
+
.join(' ');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function groupBy(list) {
|
|
180
|
+
const by = new Map();
|
|
181
|
+
for (const it of list) {
|
|
182
|
+
const g = (it && it.group != null) ? String(it.group) : '';
|
|
183
|
+
if (!by.has(g)) by.set(g, []);
|
|
184
|
+
by.get(g).push(it);
|
|
185
|
+
}
|
|
186
|
+
return by;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 画布清单块。
|
|
191
|
+
* level 0 = 分组 + id=名字;1 = 每层 id 列表;2 = 只报份数。
|
|
192
|
+
*/
|
|
193
|
+
function renderCanvas(manifest, level) {
|
|
194
|
+
const out = [];
|
|
195
|
+
for (const [key, label] of CANVAS_LAYERS) {
|
|
196
|
+
const list = toList(manifest[key]);
|
|
197
|
+
if (!list.length) continue;
|
|
198
|
+
if (level === 0) {
|
|
199
|
+
out.push(`- ${label}(${list.length})`);
|
|
200
|
+
for (const [g, arr] of groupBy(list)) {
|
|
201
|
+
out.push(` · ${g ? `${g}:` : ''}${renderItems(arr, true)}`);
|
|
202
|
+
}
|
|
203
|
+
} else if (level === 1) {
|
|
204
|
+
out.push(`- ${label}(${list.length}):${renderItems(list, false)}`);
|
|
205
|
+
} else {
|
|
206
|
+
out.push(`- ${label}(${list.length})`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const moves = toList(manifest.moves);
|
|
210
|
+
if (moves.length) {
|
|
211
|
+
out.push(level === 2
|
|
212
|
+
? `- 运镜与节奏(${moves.length})`
|
|
213
|
+
: `- 运镜与节奏(${moves.length}):${renderItems(moves, level === 0)}`);
|
|
214
|
+
}
|
|
215
|
+
return out.join('\n');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** ArtCraft 编排链块。level 0 = id=标题;1 = 仅 id;2 = 只报份数。 */
|
|
219
|
+
function renderArtcraft(recipes, level) {
|
|
220
|
+
const chains = toList(recipes.chains);
|
|
221
|
+
const formula = toList(recipes.recipes);
|
|
222
|
+
const out = [];
|
|
223
|
+
if (chains.length) {
|
|
224
|
+
out.push(`- 编排链(${chains.length})`);
|
|
225
|
+
if (level === 2) {
|
|
226
|
+
/* 只报份数 */
|
|
227
|
+
} else {
|
|
228
|
+
out.push(` · ${renderItems(chains, level === 0)}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (formula.length) {
|
|
232
|
+
out.push(level === 2
|
|
233
|
+
? `- 配方(${formula.length})`
|
|
234
|
+
: `- 配方(${formula.length}):${renderItems(formula, false)}`);
|
|
235
|
+
}
|
|
236
|
+
return out.join('\n');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* 组装快照附录。分级降级:画布给全量中文名(选导演必需)→ 画布仅 id →
|
|
241
|
+
* 全层只报份数;仍超限则截断。ArtCraft 链始终只给 id(标题可由
|
|
242
|
+
* artcraft_recipes 现场取,不值得占快照预算)。任何一步失败都返回 ''。
|
|
243
|
+
*/
|
|
244
|
+
async function buildSnapshot(maxChars) {
|
|
245
|
+
// 画布 manifest 与本文件同包(assets/canvas-director/manifest.json),优先自定位;
|
|
246
|
+
// resolvePkgDir 仅作为异常兜底(例如安装形态导致的路径重排)。
|
|
247
|
+
const canvasDir = fsSync.existsSync(path.join(HERE, 'assets', 'canvas-director', 'manifest.json'))
|
|
248
|
+
? HERE
|
|
249
|
+
: resolvePkgDir(CANVAS_PKG);
|
|
250
|
+
const artDir = resolvePkgDir(ARTCRAFT_PKG);
|
|
251
|
+
if (!canvasDir && !artDir) return '';
|
|
252
|
+
|
|
253
|
+
const [canvasVersion, artVersion] = await Promise.all([
|
|
254
|
+
readVersion(canvasDir),
|
|
255
|
+
readVersion(artDir),
|
|
256
|
+
]);
|
|
257
|
+
|
|
258
|
+
const manifest = canvasDir
|
|
259
|
+
? await readJson(path.join(canvasDir, 'assets', 'canvas-director', 'manifest.json'))
|
|
260
|
+
: null;
|
|
261
|
+
const recipes = artDir
|
|
262
|
+
? await readJson(path.join(artDir, 'recipes', 'recipes.json'))
|
|
263
|
+
: null;
|
|
264
|
+
|
|
265
|
+
if (!manifest && !recipes) return '';
|
|
266
|
+
|
|
267
|
+
const head = [
|
|
268
|
+
'',
|
|
269
|
+
'',
|
|
270
|
+
'---',
|
|
271
|
+
'',
|
|
272
|
+
'## 当期清单快照(加载时自动生成,勿手改)',
|
|
273
|
+
'',
|
|
274
|
+
'> 本段由 `@ninkch/dsh-infinite-canvas/skills` 在本技能加载时,实时读取**这台机器上已安装**的包生成。',
|
|
275
|
+
'> 它只代表当前机器与当前版本;与你的工具返回值冲突时,**一律以工具返回值(`canvas_list_skills` / `artcraft_recipes`)为准**。',
|
|
276
|
+
'',
|
|
277
|
+
].join('\n');
|
|
278
|
+
|
|
279
|
+
const build = (canvasLevel, artLevel) => {
|
|
280
|
+
const parts = [head];
|
|
281
|
+
if (manifest) {
|
|
282
|
+
const block = renderCanvas(manifest, canvasLevel);
|
|
283
|
+
if (block) {
|
|
284
|
+
parts.push(`### ${CANVAS_PKG}${canvasVersion ? ` v${canvasVersion}` : ''}`);
|
|
285
|
+
parts.push(block);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (recipes) {
|
|
289
|
+
const block = renderArtcraft(recipes, artLevel);
|
|
290
|
+
if (block) {
|
|
291
|
+
parts.push('');
|
|
292
|
+
parts.push(`### ${ARTCRAFT_PKG}${artVersion ? ` v${artVersion}` : ''}`);
|
|
293
|
+
parts.push(block);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
// 两段都没渲染出内容 → 无意义附录,返回空串让调用方跳过
|
|
297
|
+
return parts.length > 1 ? parts.join('\n').replace(/\n{3,}/g, '\n\n') : '';
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// 画布中文名优先;画布降级到 1/2 时,ArtCraft 也跟着降。
|
|
301
|
+
for (const [cl, al] of [[0, 1], [0, 2], [1, 1], [1, 2], [2, 2]]) {
|
|
302
|
+
const text = build(cl, al);
|
|
303
|
+
if (text.length <= maxChars) {
|
|
304
|
+
return cl === 2
|
|
305
|
+
? `${text}\n\n(篇幅所限,仅报份数;具体名单请调 canvas_list_skills / artcraft_recipes。)`
|
|
306
|
+
: text;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return '';
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export function apply(ctx, config = {}) {
|
|
314
|
+
const cfg = (config && typeof config === 'object') ? config : {};
|
|
315
|
+
const includeSnapshot = cfg.includeLiveSnapshot !== false;
|
|
316
|
+
const maxChars = Number.isFinite(cfg.snapshotMaxChars) && cfg.snapshotMaxChars > 0
|
|
317
|
+
? Math.floor(cfg.snapshotMaxChars)
|
|
318
|
+
: DEFAULT_SNAPSHOT_MAX;
|
|
319
|
+
|
|
320
|
+
const provider = {
|
|
321
|
+
name: PROVIDER,
|
|
322
|
+
list: () => loadCatalog(),
|
|
323
|
+
async get(candidate, options) {
|
|
324
|
+
const { rank: _rank, locator, ...summary } = candidate;
|
|
325
|
+
const raw = await fs.readFile(locator, { encoding: 'utf8', signal: options?.signal });
|
|
326
|
+
const parsed = parseSkill(raw);
|
|
327
|
+
let content = parsed ? parsed.content : raw;
|
|
328
|
+
|
|
329
|
+
// 附加当期上游清单(失败静默降级,绝不影响技能本体)
|
|
330
|
+
if (includeSnapshot && !options?.signal?.aborted) {
|
|
331
|
+
try {
|
|
332
|
+
const snapshot = await buildSnapshot(maxChars);
|
|
333
|
+
if (snapshot) content = `${content}${snapshot}`;
|
|
334
|
+
} catch { /* 快照是增强,不是必需 */ }
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// 每次加载都重读文件:改技能正文无需版本号或缓存失效
|
|
338
|
+
return { summary, content };
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
ctx.skills.registerProvider(() => provider);
|
|
342
|
+
}
|