@ninkch/dsh-artcraft 0.2.7 → 0.2.9

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/README.md CHANGED
@@ -14,18 +14,22 @@
14
14
 
15
15
  ## Why this exists
16
16
 
17
- [ArtCraft](https://getartcraft.com/apps) is a family of free, fair-source reinventions of Adobe's
18
- studio tools, written from scratch in pure Rust: **PhotoCraft** (Photoshop), **FilmCraft**
19
- (Premiere Pro), **EffectCraft** (After Effects) and more. They share one superpower that most
20
- desktop apps don't have: **every menu item, slider and dialog is a command**, and the exact same
21
- commands can be driven from a headless CLI, a JSON control channel, or an MCP server. The apps
22
- were built for agents from day one.
17
+ [ArtCraft](https://getartcraft.com/apps) is a family of **twelve** free, fair-source reinventions
18
+ of Adobe's studio tools, written from scratch in pure Rust: **PhotoCraft** (Photoshop),
19
+ **VectorCraft** (Illustrator), **FilmCraft** (Premiere Pro), **LightCraft** (Lightroom),
20
+ **PdfCraft** (Acrobat), **EffectCraft** (After Effects), **DesignCraft** (InDesign),
21
+ **WordCraft** (Word), **DeckCraft** (PowerPoint), **GridCraft** (Excel), **CADCraft** (AutoCAD)
22
+ and **SoundCraft** (Pro Tools). They share one superpower that most desktop apps don't have:
23
+ **every menu item, slider and dialog is a command**, and the exact same commands can be driven
24
+ from a headless CLI, a JSON control channel, or an MCP server. The apps were built for agents
25
+ from day one.
23
26
 
24
27
  This plugin brings that surface into DeepSeek Harness, so an AI agent can *operate* the apps —
25
28
  not just talk about them:
26
29
 
27
- - **In chat** — four agent tools let any Harness session check the toolchain, run CLI commands,
28
- send control-channel commands to a running app, and do bridged file operations.
30
+ - **In chat** — eight agent tools let any Harness session check the toolchain, run CLI commands,
31
+ send control-channel commands to a running app, do bridged file operations, and consult a
32
+ per-app capability catalog.
29
33
  - **In the browser** — a same-origin HTTP API (`/artcraft/api/*`) that any Harness web UI can call,
30
34
  no CORS, no keys. It was built for [无限画布 / infinite-canvas](../dsh-infinite_canvas/README.md),
31
35
  a node-based AI short-drama storyboard canvas, but any front end can use it.
@@ -140,10 +144,14 @@ artcraft_cli { bin:"photocraft", args:["batch","--actions","demo/craft/grade.js
140
144
 
141
145
  | Tool | What it does |
142
146
  |---|---|
143
- | `artcraft_status` | Toolchain health check: which CLIs resolved, config location, control ports, running jobs |
144
- | `artcraft_cli` | Run any CLI subcommand synchronously (`photocraft-cli run/batch`, `filmcraft-cli commands`, …) |
145
- | `artcraft_control` | One command over the JSON control channel of a *running* FilmCraft / EffectCraft |
147
+ | `artcraft_status` | Toolchain health check: which of the 12 CLIs resolved, config location, control ports, running jobs |
148
+ | `artcraft_cli` | Run any CLI subcommand synchronously (`photocraft-cli run/batch`, `wordcraft-cli convert`, …) |
149
+ | `artcraft_control` | One command over the JSON control channel of a *running* app (filmcraft 9876 / effectcraft 9877 / vectorcraft 7979 / lightcraft 7980, or pass `port`) |
146
150
  | `artcraft_fs` | Bridged file ops — list / stat / read / write / mkdir at caller-decided absolute paths |
151
+ | `artcraft_discover` | Family install auto-discovery: fills missing paths, auto-corrects stale ones |
152
+ | `artcraft_apps` | Capability catalog for all 12 apps: CLI subcommands, control ports, MCP, repo/docs links |
153
+ | `artcraft_recipes` | Built-in expert recipes: 8 cross-app professional workflows |
154
+ | `artcraft_update` | The plugin's own update (check / apply) |
147
155
 
148
156
  ## Driving it from the browser
149
157
 
@@ -175,7 +183,10 @@ Full route list:
175
183
  | `GET /artcraft/api/fs?op=list\|stat\|readtext&path=…` | Read directories, metadata, small text files |
176
184
  | `GET /artcraft/api/file?path=…` | Download a file — streamed, up to 2 GB |
177
185
  | `POST /artcraft/api/job` · `GET ?id=…` · `POST /artcraft/api/job/cancel` | Job lifecycle with output statting and tail logs |
178
- | `POST /artcraft/api/control` | Synchronous control-channel call |
186
+ | `POST /artcraft/api/control` | Synchronous control-channel call (`app` or `port`) |
187
+ | `GET /artcraft/api/apps[?id=…]` | Family capability catalog (12 apps) |
188
+ | `POST /artcraft/api/discover` | Family install discovery + config self-repair |
189
+ | `GET /artcraft/api/update/check` · `POST /artcraft/api/update/apply` | The plugin's own update |
179
190
 
180
191
  Uploads are capped at 320 MB per request (Blob direct, no base64 needed for the raw path);
181
192
  downloads stream up to 2 GB. All path arguments are **absolute** (or `base` absolute directory +
@@ -209,10 +220,14 @@ POST /artcraft/api/control
209
220
  Time accepts `seconds`, `frame` or `timecode` (internally FilmCraft counts ticks at
210
221
  254,016,000,000/s). `filmcraft-cli commands` lists the 650+ engine commands; the protocol is
211
222
  documented in [filmcraft/docs/control-protocol.md](https://github.com/storytold/filmcraft/blob/main/docs/control-protocol.md).
212
- EffectCraft listens the same way on its own port (default 9877).
223
+ EffectCraft listens the same way on its own port (default 9877); VectorCraft defaults to 7979
224
+ and LightCraft to 7980 (both built into `artcraft_control`'s `app`). The remaining apps pick
225
+ dynamic ports: start them with `app --port PORT`, then pass `port` to `artcraft_control`. Each
226
+ app's control surface is described in the `artcraft_apps` catalog.
213
227
 
214
- > PhotoCraft's control channel is an authenticated variant and is deliberately **not** proxied
215
- > here — for PhotoCraft the headless CLI covers everything this plugin needs.
228
+ > PhotoCraft's control channel is an authenticated variant and PdfCraft's is file-based
229
+ > (`--control FILE`); both are deliberately **not** proxied here — the headless CLI covers
230
+ > everything this plugin needs.
216
231
 
217
232
  ## Working with 无限画布 (infinite-canvas)
218
233
 
@@ -268,7 +283,7 @@ a web UI.
268
283
 
269
284
  The HTTP surface has **no authentication** and rides on the Harness web server, which listens on
270
285
  loopback only — treat the port as trusted-local and don't expose it to a network; put a reverse
271
- proxy with auth in front if you must. Child processes are locked to the seven allow-listed
286
+ proxy with auth in front if you must. Child processes are locked to the twelve allow-listed
272
287
  family CLIs; the control channel only ever dials `127.0.0.1`.
273
288
 
274
289
  ## Publishing
package/README.zh-CN.md CHANGED
@@ -15,16 +15,18 @@
15
15
  ## 为什么做这个
16
16
 
17
17
  [ArtCraft](https://getartcraft.com/apps) 是一套用纯 Rust 从零重写的免费公平源代码(fair source)
18
- Adobe 对标工具:
19
- **PhotoCraft**(对标 Photoshop)、**FilmCraft**(对标 Premiere Pro)、**EffectCraft**(对标 After
20
- Effects)等。它们有一个大多数桌面软件都不具备的共同基因:**每个菜单项、滑块、对话框都是一条
18
+ Adobe 对标工具,共 **12 个应用**:**PhotoCraft**(Photoshop)、**VectorCraft**(Illustrator)、
19
+ **FilmCraft**(Premiere Pro)、**LightCraft**(Lightroom)、**PdfCraft**(Acrobat)、
20
+ **EffectCraft**(After Effects)、**DesignCraft**(InDesign)、**WordCraft**(Word)、
21
+ **DeckCraft**(PowerPoint)、**GridCraft**(Excel)、**CADCraft**(AutoCAD)、**SoundCraft**
22
+ (Pro Tools)。它们有一个大多数桌面软件都不具备的共同基因:**每个菜单项、滑块、对话框都是一条
21
23
  命令**,而同一套命令既能从无头 CLI 调用,也能走 JSON 控制通道或 MCP——这些 App 从第一天起
22
24
  就是为 agent 设计的。
23
25
 
24
26
  本插件把这套自动化面接进 DeepSeek Harness,让 AI 不只是"谈论"这些软件,而是真的**操作**它们:
25
27
 
26
- - **对话里** —— 四个 Agent 工具,任何 Harness 会话都能检查工具链、执行 CLI 命令、向运行中的
27
- App 发控制通道命令、做桥接文件操作。
28
+ - **对话里** —— 八个 Agent 工具,任何 Harness 会话都能检查工具链、执行 CLI 命令、向运行中的
29
+ App 发控制通道命令、做桥接文件操作、查每个应用的能力目录。
28
30
  - **浏览器里** —— 同源 HTTP API(`/artcraft/api/*`),任何 Harness 前端都能直接调用:无 CORS、
29
31
  无密钥。它为 [无限画布 / infinite-canvas](../dsh-infinite_canvas/README.md)(节点式 AI 短剧
30
32
  分镜画布)而建,但任何前端都可用。
@@ -129,10 +131,14 @@ artcraft_cli { bin:"photocraft", args:["batch","--actions","demo/craft/grade.js
129
131
 
130
132
  | 工具 | 用途 |
131
133
  |---|---|
132
- | `artcraft_status` | 工具体检:CLI 是否找到、配置目录、控制端口、进行中的任务 |
133
- | `artcraft_cli` | 同步执行任意 CLI 子命令(`photocraft-cli run/batch`、`filmcraft-cli commands` 等) |
134
- | `artcraft_control` | 向**运行中**的 FilmCraft / EffectCraft 控制通道发一条命令 |
134
+ | `artcraft_status` | 工具体检:12 个 CLI 是否找到、配置目录、控制端口、进行中的任务 |
135
+ | `artcraft_cli` | 同步执行任意 CLI 子命令(`photocraft-cli run/batch`、`wordcraft-cli convert` 等) |
136
+ | `artcraft_control` | 向**运行中**的 App 控制通道发一条命令(filmcraft 9876 / effectcraft 9877 / vectorcraft 7979 / lightcraft 7980,或 port 直连) |
135
137
  | `artcraft_fs` | 桥接文件操作 —— list / stat / 读 / 写 / 建目录,路径由调用方绝对指定 |
138
+ | `artcraft_discover` | 家族安装自动探测:填充缺失路径、自动纠正失效路径 |
139
+ | `artcraft_apps` | 12 应用能力目录:每个应用的 CLI 子命令、控制端口、MCP、仓库/文档链接 |
140
+ | `artcraft_recipes` | 内置专家处理方案:8 套跨应用专业工作流配方 |
141
+ | `artcraft_update` | 插件自身更新(检查 / 应用) |
136
142
 
137
143
  ## 在浏览器里驱动
138
144
 
@@ -163,7 +169,10 @@ POST /artcraft/api/job/cancel { "id":"7f2c…" }
163
169
  | `GET /artcraft/api/fs?op=list\|stat\|readtext&path=…` | 读目录、元数据、小文本 |
164
170
  | `GET /artcraft/api/file?path=…` | 下载文件 —— 流式,上限 2 GB |
165
171
  | `POST /artcraft/api/job` · `GET ?id=…` · `POST /artcraft/api/job/cancel` | 任务生命周期,含产物探测与日志尾部 |
166
- | `POST /artcraft/api/control` | 控制通道同步调用 |
172
+ | `POST /artcraft/api/control` | 控制通道同步调用(`app` 或 `port`) |
173
+ | `GET /artcraft/api/apps[?id=…]` | 家族能力目录(12 应用) |
174
+ | `POST /artcraft/api/discover` | 家族安装探测 + 配置自动修复 |
175
+ | `GET /artcraft/api/update/check` · `POST /artcraft/api/update/apply` | 插件自更新 |
167
176
 
168
177
  上传单次上限 320 MB(原始路径 Blob 直传,无需 base64);下载流式上限 2 GB。
169
178
  所有路径参数为**绝对路径**(或 `base` 绝对目录 + 相对 `rel`)—— 位置由调用方决定:
@@ -195,9 +204,12 @@ POST /artcraft/api/control
195
204
  时间可传 `seconds` / `frame` / `timecode`(FilmCraft 内部以每秒 254,016,000,000 tick 计)。
196
205
  `filmcraft-cli commands` 列出全部 650+ 引擎命令;协议详见
197
206
  [filmcraft/docs/control-protocol.md](https://github.com/storytold/filmcraft/blob/main/docs/control-protocol.md)。
198
- EffectCraft 同族协议,端口默认 9877。
207
+ EffectCraft 同族协议,端口默认 9877;VectorCraft 默认 7979、LightCraft 默认 7980(`artcraft_control`
208
+ 的 `app` 已内置)。其余应用端口动态:以 `app --port PORT` 启动后,`artcraft_control` 传 `port`
209
+ 直连即可(协议同族)。每个应用的控制面详见 `artcraft_apps` 能力目录。
199
210
 
200
- > PhotoCraft 的控制通道是带鉴权的变体,本插件**有意不代理** —— PhotoCraft 走无头 CLI 足够。
211
+ > PhotoCraft 的控制通道是带鉴权的变体、PdfCraft 是文件式(`--control FILE`),本插件**有意不代理**
212
+ > —— 两者走无头 CLI 足够。
201
213
 
202
214
  ## 与无限画布(infinite-canvas)协作
203
215
 
@@ -250,7 +262,7 @@ NVENC 硬编)、ProRes、DNxHR,附 EBU R128 响度归一与字幕烧录。
250
262
  ## 安全与信任模型
251
263
 
252
264
  HTTP 面无鉴权,挂在 Harness Web 服务之下(仅回环监听)—— 把端口当本机可信处理,**不要暴露
253
- 到网络**;确需远程请加带鉴权的反向代理。子进程锁定 7 个白名单家族 CLI;控制通道只会拨
265
+ 到网络**;确需远程请加带鉴权的反向代理。子进程锁定 12 个白名单家族 CLI;控制通道只会拨
254
266
  `127.0.0.1`。
255
267
 
256
268
  ## 发布
@@ -0,0 +1,293 @@
1
+ {
2
+ "version": 1,
3
+ "note": "ArtCraft 家族 12 应用的自动化能力目录。来源:官方 getartcraft.com/apps、各应用 GitHub 仓库(storytold/*)、本机 12 个 <app>-cli --help 实测(0.2.9 时点)。要拿某应用的实时命令注册表,用 artcraft_cli 跑该应用的 commands 子命令。",
4
+ "controlProtocol": "家族共用 JSON-lines 控制协议(127.0.0.1:<port>,请求 {\"id\",\"method\",\"params\"},方法如 engine.execute / engine.commands / ui.inspect / ui.screenshot)。App 需以 --control 启动。artcraft_control 已代理有固定默认端口的应用;其余应用端口动态(app --port 启动时指定),可用 artcraft_control 的 port 参数直连。",
5
+ "apps": [
6
+ {
7
+ "id": "photocraft",
8
+ "name": "PhotoCraft",
9
+ "tagline": "图像编辑",
10
+ "ref": "Adobe Photoshop",
11
+ "stage": "early alpha",
12
+ "repo": "https://github.com/storytold/photocraft",
13
+ "docs": "https://getartcraft.com/apps/photocraft",
14
+ "cli": "photocraft-cli",
15
+ "control": null,
16
+ "controlNote": "控制通道为令牌鉴权模式,artcraft_control 不代理;headless 用 serve/mcp",
17
+ "mcp": true,
18
+ "subcommands": [
19
+ { "name": "convert", "usage": "convert <in> <out> [--format ext] [--quality 1-100] [--tiff-layers]", "summary": "格式互转(.pcraft/.psd/.png/.jpg/.tif/.webp/.exr…);TIFF 默认拼合,--tiff-layers 保留图层" },
20
+ { "name": "info", "usage": "info <file> [--compact]", "summary": "文档 JSON(尺寸/颜色模式/位深/图层树)" },
21
+ { "name": "run", "usage": "run (<file> | --new <json>) --cmd <id> [--params <json>]… [--out <file>] [--format] [--quality] [--tiff-layers]", "summary": "打开文件按序执行引擎命令后保存;每个 --params 归属前一个 --cmd" },
22
+ { "name": "batch", "usage": "batch --actions <actions.json> --in <dir> --out <dir> [--format] [--quality] [--in-place] [--tiff-layers]", "summary": "动作列表批处理整个目录(录制动作/droplet 格式);--in-place 才允许覆盖原目录" },
23
+ { "name": "droplet", "usage": "droplet <file.pcdroplet> <file-or-dir>… [--out <dir>]", "summary": "运行 Photoshop 式 droplet" },
24
+ { "name": "commands", "usage": "commands [--json] [--filter <text>]", "summary": "列出引擎命令注册表" },
25
+ { "name": "serve", "usage": "serve [--port <port>] [--control-token …] [--automation-read-root <dir>] [--automation-write-root <dir>]", "summary": "常驻 headless 会话:stdio 或 TCP JSON lines(engine.execute、doc.open/new/save/inspect/render/select/close、session.list、batch、methods)" },
26
+ { "name": "mcp", "usage": "mcp [--bridge 127.0.0.1:port] [--control-token …] [--automation-read-root] [--automation-write-root]", "summary": "stdio MCP 服务器(headless 引擎,或 bridge 到运行中的 app)" }
27
+ ]
28
+ },
29
+ {
30
+ "id": "vectorcraft",
31
+ "name": "VectorCraft",
32
+ "tagline": "矢量插画",
33
+ "ref": "Adobe Illustrator",
34
+ "stage": "in development",
35
+ "repo": "https://github.com/storytold/vectorcraft",
36
+ "docs": "https://getartcraft.com/apps/vectorcraft",
37
+ "cli": "vectorcraft-cli",
38
+ "control": 7979,
39
+ "mcp": true,
40
+ "subcommands": [
41
+ { "name": "run", "usage": "run [--in FILE] [--cmd ID [--params JSON]]… [--export FILE]… [--scale N]", "summary": "headless 批处理:打开或新建文档,按序执行命令,按扩展名导出" },
42
+ { "name": "commands", "usage": "commands", "summary": "命令目录 JSON" },
43
+ { "name": "convert", "usage": "convert IN OUT [--scale N] [--artboard I | --range R] [--outline-text] [--replace-lossy]", "summary": "转换/导出(SVG/PDF/PNG…);PDF 出全部画板;--outline-text 文字转路径" },
44
+ { "name": "info", "usage": "info FILE", "summary": "JSON 摘要:导入警告、标题、颜色模式、单位、画板、对象统计、字体" },
45
+ { "name": "bench", "usage": "bench FILE [--size WxH] [--iters N]", "summary": "渲染基准(ms/帧,多线程与单线程)" },
46
+ { "name": "perf", "usage": "perf [--paths N]", "summary": "性能预算检查(渲染/平移/命中/存取/SVG/Pathfinder)" },
47
+ { "name": "mcp", "usage": "mcp [--connect ADDR | --headless] [--automation-read-root DIR] [--automation-write-root DIR]", "summary": "stdio MCP(默认连运行中的 app 127.0.0.1:7979,回退 headless)" }
48
+ ]
49
+ },
50
+ {
51
+ "id": "filmcraft",
52
+ "name": "FilmCraft",
53
+ "tagline": "视频剪辑、调色和声音处理",
54
+ "ref": "Adobe Premiere Pro",
55
+ "stage": "in development",
56
+ "repo": "https://github.com/storytold/filmcraft",
57
+ "docs": "https://getartcraft.com/apps/filmcraft",
58
+ "cli": "filmcraft-cli",
59
+ "control": 9876,
60
+ "mcp": true,
61
+ "global": ["--project <p.fcproj>", "--demo", "--save", "--save-as <p.fcproj>"],
62
+ "subcommands": [
63
+ { "name": "exec", "usage": "exec <id> [key=value …] | exec <id> '<json params>'", "summary": "运行单条命令,结果 JSON" },
64
+ { "name": "run", "usage": "run <script.jsonl | ->", "summary": "脚本批处理:每行一个 {\"id\",\"params\"}(# 注释可用)" },
65
+ { "name": "commands", "usage": "commands [filter] [--json]", "summary": "命令清单(id/label/快捷键/参数)" },
66
+ { "name": "describe", "usage": "describe <id>", "summary": "单条命令 JSON(菜单/快捷键/参数/当前可用性)" },
67
+ { "name": "inspect", "usage": "inspect [project|sequence]", "summary": "项目树或活动序列 JSON(默认两者)" },
68
+ { "name": "import", "usage": "import <file>…", "summary": "导入媒体到项目" },
69
+ { "name": "export", "usage": "export <out> [--preset name] [--format f] [--range entire|inOut|workArea|秒] [--settings json] [--gpu-rendering auto|off] [--scale f] [--quality 0-100] [--no-audio] [--queue]", "summary": "导出活动序列(h264/hevc/prores/dnxhr/apv/mjpeg/mxf-op1a/mxf-opatom/png/tiff/bmp/gif/wav/aiff/aac);--list-presets 列预设" },
70
+ { "name": "render", "usage": "render --seconds S --out f.png [--scale 0.5]", "summary": "渲染 Program 单帧 PNG" },
71
+ { "name": "probe", "usage": "probe <media> [--image-sequence]", "summary": "媒体信息 JSON(含 MXF/Ogg/BWF 细节;编号静帧序列取首帧)" },
72
+ { "name": "bench-decode", "usage": "bench-decode <media> [--frames N]", "summary": "解码基准" },
73
+ { "name": "mcp", "usage": "mcp", "summary": "stdio MCP(headless,或 --bridge 接运行中的 app)" }
74
+ ]
75
+ },
76
+ {
77
+ "id": "lightcraft",
78
+ "name": "LightCraft",
79
+ "tagline": "照片管理与 RAW 处理",
80
+ "ref": "Adobe Lightroom",
81
+ "stage": "in development",
82
+ "repo": "https://github.com/storytold/lightcraft",
83
+ "docs": "https://getartcraft.com/apps/lightcraft",
84
+ "cli": "lightcraft-cli",
85
+ "control": 7980,
86
+ "mcp": true,
87
+ "subcommands": [
88
+ { "name": "run", "usage": "run [--demo | --library DIR | --import PATH… | --connect [ADDR] | --script FILE|- | --keep-going] COMMAND [k=v | '{json}']…", "summary": "命令批处理(每命令一行 JSON 结果):develop.set/auto、app.export 等控制协议方法;--library 模式编辑落盘" },
89
+ { "name": "mcp", "usage": "mcp [--connect [ADDR] | --demo | --library DIR] [FILES/FOLDERS…]", "summary": "stdio MCP(headless 默认;--compact 只列辅助工具)" },
90
+ { "name": "render", "usage": "render <IN> -o <OUT> [--set CONTROL=VALUE…] [--settings FILE] [--preset ID] [--size N] [--quality Q] [--opt KEY=VALUE…]", "summary": "单文件 RAW 冲洗导出(.jpg/.png/.tif/.webp/.avif/.dng);--opt 支持 colorSpace/bitDepth/percent/ppi/format=original" },
91
+ { "name": "snapshot", "usage": "snapshot [FILES/FOLDERS…] [OPTIONS]", "summary": "无窗口 CPU 光栅化跑完整 UI 并输出 PNG(界面截图自动化)" }
92
+ ]
93
+ },
94
+ {
95
+ "id": "pdfcraft",
96
+ "name": "PdfCraft",
97
+ "tagline": "PDF 阅读、整理和保护",
98
+ "ref": "Adobe Acrobat",
99
+ "stage": "early alpha",
100
+ "repo": "https://github.com/storytold/pdfcraft",
101
+ "docs": "https://getartcraft.com/apps/pdfcraft",
102
+ "cli": "pdfcraft-cli",
103
+ "control": null,
104
+ "controlNote": "控制通道为文件式(--control FILE),不走 TCP,artcraft_control 不代理",
105
+ "mcp": true,
106
+ "subcommands": [
107
+ { "name": "info", "usage": "info <file.pdf> [--password PW]", "summary": "文档摘要 JSON" },
108
+ { "name": "render", "usage": "render <file.pdf> --page N [--dpi 96] --out x.png", "summary": "页面渲染(.png/.jpg/.tif/.pam)" },
109
+ { "name": "text", "usage": "text <file.pdf> [--page N]", "summary": "提取文本(页间换页符分隔)" },
110
+ { "name": "edit", "usage": "edit <in.pdf> --out out.pdf [--rotate 1,3:90] [--delete 2,4] [--move 5:1] [--insert-blank 1] [--title T] [--author A] [--full]", "summary": "页级编辑:旋转/删除/移动/插空白页/元数据" },
111
+ { "name": "combine", "usage": "combine <a.pdf> <b.pdf> … --out combined.pdf", "summary": "合并多个 PDF" },
112
+ { "name": "extract", "usage": "extract <in.pdf> --pages 1,3,5 --out out.pdf", "summary": "抽取指定页" },
113
+ { "name": "split", "usage": "split <in.pdf> (--every N | --before 3,7) [--out-dir DIR]", "summary": "拆分(每 N 页一档 / 指定页前切)" },
114
+ { "name": "check", "usage": "check <files or dirs…> [--timeout 20] [--dpi 36] [--json out.json]", "summary": "批量打开校验(损坏/加密扫描)" },
115
+ { "name": "tools", "usage": "tools", "summary": "自动化工具与其 JSON Schema" },
116
+ { "name": "run", "usage": "run <tool> [key=value …] [--root DIR] [--out image.png] | run --script steps.json [--root DIR]", "summary": "运行自动化工具(或脚本数组 [{\"tool\",\"args\"}…])" },
117
+ { "name": "mcp", "usage": "mcp [--root DIR] [--compact]", "summary": "stdio MCP(--compact 精简工具集 + tool_search/tool_call)" },
118
+ { "name": "ui", "usage": "ui --control FILE <method> [key=value …] [--out shot.png]", "summary": "驱动以 --control FILE 启动的运行中 app(engine.execute/ui.* 等)" }
119
+ ]
120
+ },
121
+ {
122
+ "id": "effectcraft",
123
+ "name": "EffectCraft",
124
+ "tagline": "动态图形与视觉特效",
125
+ "ref": "Adobe After Effects",
126
+ "stage": "in development",
127
+ "repo": "https://github.com/storytold/effectcraft",
128
+ "docs": "https://getartcraft.com/apps/effectcraft",
129
+ "cli": "effectcraft-cli",
130
+ "control": 9877,
131
+ "mcp": true,
132
+ "global": ["--project F.ecproj | --demo | --empty", "--save | --save-as F", "--bridge PORT", "--json", "--gpu"],
133
+ "subcommands": [
134
+ { "name": "info", "usage": "info", "summary": "项目 + 引擎摘要" },
135
+ { "name": "commands", "usage": "commands [--filter TEXT] [--enabled]", "summary": "引擎命令清单" },
136
+ { "name": "exec", "usage": "exec <command-id> [--params JSON]", "summary": "运行单条引擎命令" },
137
+ { "name": "run", "usage": "run <id> <json> [<id> <json> …]", "summary": "按序运行多条命令" },
138
+ { "name": "props", "usage": "props <comp> <layer> [--flat] [--time S]", "summary": "图层属性树(含路径;<comp>/<layer> 可用 id/名称,'-' 为当前)" },
139
+ { "name": "get", "usage": "get <comp> <layer> <path> [--time S]", "summary": "读属性" },
140
+ { "name": "set", "usage": "set <comp> <layer> <path> <value> [--time S] [--expression E]", "summary": "写属性(可设表达式)" },
141
+ { "name": "render-frame", "usage": "render-frame [--comp C] [--time S | --frame N] [--max-side PX | --scale K] [--out F.png] [--transparent]", "summary": "单帧渲染(可透明底)" },
142
+ { "name": "render", "usage": "render [--comp C] --out FILE [--format h264|hevc|av1|prores|webm|png|jpeg|tiff|exr|gif|wav|aiff] [--start/--end/--work-area/--fps/--resolution/--quality/--channels/--bitrate/--prores PROFILE/--rate-control/--keyint/--webm-codec vp9|av1…] [--queue] | --template T.ectemplate --values V.json", "summary": "合成输出(编码参数齐全;模板渲染用 --template + --values)" },
143
+ { "name": "bench", "usage": "bench [--comp C] [--time S] [--scale K] [--n N] [--play N] [--gpu] | bench --ops", "summary": "渲染基准(CPU/GPU 对比;--ops 大项目日常操作基准)" },
144
+ { "name": "script", "usage": "script FILE.jsx [F.ecproj] | --eval CODE", "summary": "AE 风格 JS 对象模型(app.project/comps/layers/properties…),错误带 file:line:col" },
145
+ { "name": "mcp", "usage": "mcp [--autosave | --bridge PORT] [--automation-read-root DIR] [--automation-write-root DIR]", "summary": "stdio MCP(--autosave 保留未保存的 headless 成果;脚本可被 root 限制拒绝)" }
146
+ ]
147
+ },
148
+ {
149
+ "id": "designcraft",
150
+ "name": "DesignCraft",
151
+ "tagline": "页面排版与出版",
152
+ "ref": "Adobe InDesign",
153
+ "stage": "in development",
154
+ "repo": "https://github.com/storytold/designcraft",
155
+ "docs": "https://getartcraft.com/apps/designcraft",
156
+ "cli": "designcraft-cli",
157
+ "control": null,
158
+ "controlNote": "控制通道端口动态:app --port PORT 启动后用 artcraft_control 的 port 参数直连",
159
+ "mcp": true,
160
+ "subcommands": [
161
+ { "name": "run", "usage": "run [--in FILE | --sample] [--cmd ID[=JSON]]… [--page N] [--scale S] [--pdf-options JSON] [--export OUT] [--all-pages DIR]", "summary": "headless 执行命令并导出(--page/--scale/--pdf-options 归属其后的导出)" },
162
+ { "name": "commands", "usage": "commands [FILTER]", "summary": "命令清单" },
163
+ { "name": "describe", "usage": "describe COMMAND", "summary": "单命令详情" },
164
+ { "name": "script", "usage": "script [FILE|-] [--in FILE | --sample] [--connect PORT] [--save OUT] [--export OUT] [--keep-going]", "summary": "脚本批处理" },
165
+ { "name": "app", "usage": "app [--port PORT] COMMAND [JSON] | --method METHOD [JSON]", "summary": "调用运行中 app 的控制通道(引擎命令或协议方法)" },
166
+ { "name": "mcp", "usage": "mcp [--connect PORT] [--sample]", "summary": "stdio MCP" },
167
+ { "name": "perf", "usage": "perf [--pages N] [--runs N] [--strict]", "summary": "性能预算" },
168
+ { "name": "bench", "usage": "bench FILE [--runs N]", "summary": "渲染基准" },
169
+ { "name": "links", "usage": "links", "summary": "社区/文档链接" }
170
+ ]
171
+ },
172
+ {
173
+ "id": "wordcraft",
174
+ "name": "WordCraft",
175
+ "tagline": "写作与文档设计",
176
+ "ref": "Microsoft Word",
177
+ "stage": "in development",
178
+ "repo": "https://github.com/storytold/wordcraft",
179
+ "docs": "https://getartcraft.com/apps/wordcraft",
180
+ "cli": "wordcraft-cli",
181
+ "control": null,
182
+ "controlNote": "控制通道端口动态(mcp --connect HOST:PORT 桥接运行中的 app)",
183
+ "mcp": true,
184
+ "subcommands": [
185
+ { "name": "convert", "usage": "convert <in> <out>", "summary": "格式互转:docx/doc(读)/pdf/odt/rtf/html/md/tex/txt/json/png" },
186
+ { "name": "info", "usage": "info <file>", "summary": "页数/字数/段落/属性 JSON" },
187
+ { "name": "text", "usage": "text <file>", "summary": "纯文本提取" },
188
+ { "name": "inspect", "usage": "inspect <file>", "summary": "文档结构 JSON" },
189
+ { "name": "render", "usage": "render <file> <out.png> [--page N] [--scale S]", "summary": "页面渲染 PNG" },
190
+ { "name": "run", "usage": "run [--file F | --template T] --cmd 'id={json}' [--cmd …] [--save OUT] [--print] [--author NAME]", "summary": "headless 命令(--author 给修订/批注署名)" },
191
+ { "name": "commands", "usage": "commands [--json]", "summary": "全部命令清单" },
192
+ { "name": "parity", "usage": "parity [--markdown]", "summary": "特性目录对照报告" },
193
+ { "name": "zotero", "usage": "zotero <command> <file> [--cmd …] [--save OUT] [--trace] [--port P]", "summary": "Zotero 文献命令(需 Zotero 运行中):addEditCitation/addEditBibliography/addNote/refresh/removeCodes/setDocPrefs" },
194
+ { "name": "mcp", "usage": "mcp [--connect HOST:PORT | --author NAME]", "summary": "stdio MCP(headless 或桥接运行中的 app)" }
195
+ ]
196
+ },
197
+ {
198
+ "id": "deckcraft",
199
+ "name": "DeckCraft",
200
+ "tagline": "演示文稿与幻灯片",
201
+ "ref": "Microsoft PowerPoint",
202
+ "stage": "in development",
203
+ "repo": "https://github.com/storytold/deckcraft",
204
+ "docs": "https://getartcraft.com/apps/deckcraft",
205
+ "cli": "deckcraft-cli",
206
+ "control": null,
207
+ "controlNote": "控制通道端口动态:app --port PORT 启动后用 artcraft_control 的 port 参数直连",
208
+ "mcp": true,
209
+ "subcommands": [
210
+ { "name": "info", "usage": "info FILE", "summary": "演示文稿摘要" },
211
+ { "name": "render", "usage": "render (FILE | --sample) [--slide N | --all] [--scale S] [--edit] OUT", "summary": "幻灯片渲染 PNG(单页/全部)" },
212
+ { "name": "convert", "usage": "convert IN OUT", "summary": "格式转换" },
213
+ { "name": "run", "usage": "run [--in FILE | --sample] [--cmd ID[=JSON]]… [--save OUT] [--export OUT] [--print]", "summary": "headless 命令批处理" },
214
+ { "name": "commands", "usage": "commands [FILTER]", "summary": "命令清单" },
215
+ { "name": "describe", "usage": "describe COMMAND", "summary": "单命令详情" },
216
+ { "name": "app", "usage": "app [--port PORT] COMMAND [JSON] | --method METHOD [JSON]", "summary": "调用运行中 app 的控制通道" },
217
+ { "name": "mcp", "usage": "mcp [--connect PORT] [--sample]", "summary": "stdio MCP" }
218
+ ]
219
+ },
220
+ {
221
+ "id": "gridcraft",
222
+ "name": "GridCraft",
223
+ "tagline": "电子表格与数据处理",
224
+ "ref": "Microsoft Excel",
225
+ "stage": "in development",
226
+ "repo": "https://github.com/storytold/gridcraft",
227
+ "docs": "https://getartcraft.com/apps/gridcraft",
228
+ "cli": "gridcraft-cli",
229
+ "control": null,
230
+ "controlNote": "控制通道端口动态(send <port> 直发)",
231
+ "mcp": true,
232
+ "subcommands": [
233
+ { "name": "info", "usage": "info <file> [--json]", "summary": "工作表/已用范围/表格/图表/名称" },
234
+ { "name": "convert", "usage": "convert <in> <out> [--sheet NAME]", "summary": "xlsx/csv/tsv/json/html 按扩展名互转" },
235
+ { "name": "eval", "usage": "eval <formula> [--in FILE] [--sheet S] [--cell A1] [--json]", "summary": "求值公式" },
236
+ { "name": "cat", "usage": "cat <file> [--range A1:D20] [--sheet S] [--formulas] [--csv]", "summary": "读单元格区域(可含公式)" },
237
+ { "name": "run", "usage": "run [--in FILE | --sample NAME] [--cmd 'id={json}']… [--script FILE.jsonl] [--out FILE] [--print RANGE] [--quiet]", "summary": "headless 命令批处理(可输出文件/打印区域)" },
238
+ { "name": "commands", "usage": "commands [--json] [--search X]", "summary": "命令清单" },
239
+ { "name": "functions", "usage": "functions [--json] [--search X] [--category C]", "summary": "函数库清单" },
240
+ { "name": "send", "usage": "send <port> <method> [json]", "summary": "向运行中 app 的控制通道直发一条方法" },
241
+ { "name": "mcp", "usage": "mcp [--connect PORT] [--in FILE | --sample NAME]", "summary": "stdio MCP" }
242
+ ]
243
+ },
244
+ {
245
+ "id": "cadcraft",
246
+ "name": "CADCraft",
247
+ "tagline": "计算机辅助设计与制图",
248
+ "ref": "Autodesk AutoCAD",
249
+ "stage": "in development",
250
+ "repo": "https://github.com/storytold/cadcraft",
251
+ "docs": "https://getartcraft.com/apps/cadcraft",
252
+ "cli": "cadcraft-cli",
253
+ "control": null,
254
+ "controlNote": "控制通道端口动态(mcp --connect HOST:PORT 桥接运行中的 app)",
255
+ "mcp": true,
256
+ "subcommands": [
257
+ { "name": "info", "usage": "info FILE.dxf", "summary": "图纸摘要" },
258
+ { "name": "convert", "usage": "convert IN.dxf OUT.(dxf|dwg|svg|png|pdf) [FRAMING]", "summary": "格式转换/出图" },
259
+ { "name": "run", "usage": "run [FILE | --sample | --metric] [--script TEXT | --script-file F.scr] [--cmd 'id {json}']… [--save OUT.dxf] [--export OUT.(png|svg|pdf)] [FRAMING]", "summary": "headless 执行(支持 AutoCAD 风格 .scr 脚本)后存/出图" },
260
+ { "name": "commands", "usage": "commands [FILTER]", "summary": "命令清单" },
261
+ { "name": "sample", "usage": "sample (bracket|floorplan) OUT.(dxf|dwg|svg|png)", "summary": "生成示例图(支架/户型平面)" },
262
+ { "name": "perf", "usage": "perf [N]", "summary": "性能基准" },
263
+ { "name": "mcp", "usage": "mcp [--connect HOST:PORT]", "summary": "stdio MCP" },
264
+ { "name": "FRAMING", "usage": "--window x0,y0,x1,y1 | --view extents|current", "summary": "出图取景:指定矩形(图形单位)或范围/当前视图" }
265
+ ]
266
+ },
267
+ {
268
+ "id": "soundcraft",
269
+ "name": "SoundCraft",
270
+ "tagline": "音频录制、编辑与混音",
271
+ "ref": "Avid Pro Tools",
272
+ "stage": "in development",
273
+ "repo": "https://github.com/storytold/soundcraft",
274
+ "docs": "https://getartcraft.com/apps/soundcraft",
275
+ "cli": "soundcraft-cli",
276
+ "control": null,
277
+ "controlNote": "控制通道端口动态:app --port P 启动后用 artcraft_control 的 port 参数直连",
278
+ "mcp": true,
279
+ "subcommands": [
280
+ { "name": "info", "usage": "info FILE", "summary": "音频文件或 .scraft 工程摘要" },
281
+ { "name": "convert", "usage": "convert IN OUT [--bit-depth 16|24|32f] [--rate HZ] [--normalize]", "summary": "音频转换(位深/采样率/响度归一)" },
282
+ { "name": "run", "usage": "run [--in S.scraft | --demo] [--cmd ID[=JSON]]… [--bounce OUT] [--start T --end T] [--save S.scraft] [--inspect]", "summary": "headless 命令批处理并 bounce(区间可选)" },
283
+ { "name": "script", "usage": "script FILE [--in S | --demo] [--save S] [--bounce OUT]", "summary": "脚本批处理(每行 `ID JSON`)" },
284
+ { "name": "commands", "usage": "commands [FILTER]", "summary": "命令清单" },
285
+ { "name": "describe", "usage": "describe ID", "summary": "单命令详情" },
286
+ { "name": "app", "usage": "app [--port P] METHOD [JSON]", "summary": "调用运行中 app 的控制通道" },
287
+ { "name": "parity", "usage": "parity [--write PATH]", "summary": "特性对照报告(markdown)" },
288
+ { "name": "plugins", "usage": "plugins", "summary": "内置插件清单" },
289
+ { "name": "mcp", "usage": "mcp [--connect PORT] [--demo]", "summary": "stdio MCP" }
290
+ ]
291
+ }
292
+ ]
293
+ }
@@ -6,7 +6,12 @@
6
6
  "lightcraft": "C:/Program Files/LightCraft",
7
7
  "pdfcraft": "C:/Program Files/PdfCraft",
8
8
  "effectcraft": "C:/Program Files/EffectCraft",
9
- "designcraft": "C:/Program Files/DesignCraft"
9
+ "designcraft": "C:/Program Files/DesignCraft",
10
+ "wordcraft": "C:/Program Files/WordCraft",
11
+ "deckcraft": "C:/Program Files/DeckCraft",
12
+ "gridcraft": "C:/Program Files/GridCraft",
13
+ "cadcraft": "C:/Program Files/CADCraft",
14
+ "soundcraft": "C:/Program Files/SoundCraft"
10
15
  },
11
16
  "control": {
12
17
  "filmcraft": 9876,
package/client.js CHANGED
@@ -15,7 +15,7 @@ window.__ModuleLoader__.load({
15
15
 
16
16
  const NS = 'artcraft';
17
17
  const API = '/artcraft/api';
18
- const LABELS = { photocraft: 'PhotoCraft', vectorcraft: 'VectorCraft', filmcraft: 'FilmCraft', lightcraft: 'LightCraft', pdfcraft: 'PdfCraft', effectcraft: 'EffectCraft', designcraft: 'DesignCraft' };
18
+ const LABELS = { photocraft: 'PhotoCraft', vectorcraft: 'VectorCraft', filmcraft: 'FilmCraft', lightcraft: 'LightCraft', pdfcraft: 'PdfCraft', effectcraft: 'EffectCraft', designcraft: 'DesignCraft', wordcraft: 'WordCraft', deckcraft: 'DeckCraft', gridcraft: 'GridCraft', cadcraft: 'CADCraft', soundcraft: 'SoundCraft' };
19
19
 
20
20
  const CSS = `
21
21
  .dshac-card { border: 1px solid var(--dsw-border-primary, rgba(128,128,128,.35)); border-radius: 10px; padding: 12px; font-size: 13px; line-height: 1.5; }
@@ -62,10 +62,19 @@ window.__ModuleLoader__.load({
62
62
  useEffect(() => () => { alive.current = false; }, []);
63
63
 
64
64
  const loadStatus = useCallback(async () => {
65
+ setBusy('refresh');
65
66
  try {
67
+ // 刷新 = 状态 + 顺带一次家族安装探测(后装软件/改安装目录后自动修正配置;
68
+ // 探测端点在旧版插件上不存在时静默跳过)
69
+ try {
70
+ await api('/discover', {
71
+ method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}',
72
+ });
73
+ } catch { /* 旧版无此路由 → 静默跳过 */ }
66
74
  const s = await api('/status');
67
75
  if (alive.current) { setStatus(s); setStatusErr(null); }
68
76
  } catch (e) { if (alive.current) setStatusErr(String(e.message || e)); }
77
+ finally { if (alive.current) setBusy(''); }
69
78
  }, []);
70
79
 
71
80
  const checkUpdate = useCallback(async () => {
package/index.js CHANGED
@@ -8,11 +8,12 @@
8
8
  *
9
9
  * 两种形态:
10
10
  * 1) Agent Tools(对话内直接驱动):
11
- * artcraft_status —— 检查 7 个家族 CLI 是否可用、配置目录、控制通道配置
12
- * artcraft_cli —— 运行 7 个家族 CLI 任意命令(cwd / 文件参数由调用方给绝对路径)
13
- * artcraft_control —— 向运行中的 FilmCraft/EffectCraft JSON 控制通道发一条命令
11
+ * artcraft_status —— 检查 12 个家族 CLI 是否可用、配置目录、控制通道配置
12
+ * artcraft_cli —— 运行 12 个家族 CLI 任意命令(cwd / 文件参数由调用方给绝对路径)
13
+ * artcraft_control —— 向运行中的 FilmCraft/EffectCraft/VectorCraft/LightCraft JSON 控制通道发一条命令
14
14
  * artcraft_fs —— 桥接文件操作(list/stat/readtext/write/mkdir,绝对路径)
15
15
  * artcraft_update —— 插件自身更新(检查/应用)
16
+ * artcraft_apps —— 12 个家族应用的自动化能力目录(子命令/控制端口/MCP/仓库)
16
17
  * 2) HTTP API(同源 /artcraft/api/*,供无限画布等前端调用,无 CORS 问题):
17
18
  * GET /artcraft/api/status 环境自检
18
19
  * GET /artcraft/api/fs list / stat / readtext
@@ -22,11 +23,14 @@
22
23
  * POST /artcraft/api/job 启动长任务(渲染/导出/批处理)
23
24
  * POST /artcraft/api/job/cancel 取消任务
24
25
  * POST /artcraft/api/control 控制通道同步调用(engine.execute / ui.*)
26
+ * GET /artcraft/api/apps 家族能力目录(list / ?id= 单个)
25
27
  *
26
28
  * 安全约定:
27
- * - 子进程只允许 7 个白名单键(家族 CLI),具体可执行文件由 artcraft.config.json 指定;
29
+ * - 子进程只允许 12 个白名单键(家族 CLI),具体可执行文件由 artcraft.config.json 指定;
28
30
  * - 控制通道只连 127.0.0.1,与 ArtCraft 官方协议一致(App 需以 --control 启动;
29
- * PhotoCraft 的控制通道为鉴权模式,本插件不代理,故仅支持 filmcraft/effectcraft);
31
+ * 已知默认端口:filmcraft 9876 / effectcraft 9877 / vectorcraft 7979 / lightcraft 7980;
32
+ * PhotoCraft 控制通道为令牌鉴权模式、PdfCraft 为文件式(--control FILE),均不代理;
33
+ * 其余应用端口动态,用 port 参数直连);
30
34
  * - 本插件与 infinite-canvas 零代码耦合,仅通过 HTTP + 调用方给定路径协作。
31
35
  *
32
36
  * 信任模型:HTTP API 无鉴权,随 DSH Web GUI 仅监听回环;不要把 GUI 端口暴露到局域网。
@@ -42,12 +46,15 @@ import crypto from 'node:crypto';
42
46
  import { fileURLToPath } from 'node:url';
43
47
 
44
48
  const NAME = 'artcraft';
45
- const VERSION = '0.2.7';
49
+ const VERSION = '0.2.9';
46
50
  const HERE = path.dirname(fileURLToPath(import.meta.url));
47
51
 
48
- const BINS = ['photocraft', 'vectorcraft', 'filmcraft', 'lightcraft', 'pdfcraft', 'effectcraft', 'designcraft'];
52
+ const BINS = [
53
+ 'photocraft', 'vectorcraft', 'filmcraft', 'lightcraft', 'pdfcraft', 'effectcraft',
54
+ 'designcraft', 'wordcraft', 'deckcraft', 'gridcraft', 'cadcraft', 'soundcraft',
55
+ ];
49
56
  const PKG_NAME = '@ninkch/dsh-artcraft';
50
- const DEFAULT_PORTS = { filmcraft: 9876, effectcraft: 9877 };
57
+ const DEFAULT_PORTS = { filmcraft: 9876, effectcraft: 9877, vectorcraft: 7979, lightcraft: 7980 };
51
58
 
52
59
  const BODY_CAP = 2 * 1024 * 1024; // 普通 JSON 请求体上限
53
60
  const WRITE_CAP = 320 * 1024 * 1024; // 上传(写盘)上限
@@ -66,10 +73,22 @@ const DEFAULT_CONFIG = {
66
73
  pdfcraft: 'pdfcraft-cli',
67
74
  effectcraft: 'effectcraft-cli',
68
75
  designcraft: 'designcraft-cli',
76
+ wordcraft: 'wordcraft-cli',
77
+ deckcraft: 'deckcraft-cli',
78
+ gridcraft: 'gridcraft-cli',
79
+ cadcraft: 'cadcraft-cli',
80
+ soundcraft: 'soundcraft-cli',
69
81
  },
70
82
  control: { ...DEFAULT_PORTS },
71
83
  };
72
84
 
85
+ const APP_DISPLAY = {
86
+ photocraft: 'PhotoCraft', vectorcraft: 'VectorCraft', filmcraft: 'FilmCraft',
87
+ lightcraft: 'LightCraft', pdfcraft: 'PdfCraft', effectcraft: 'EffectCraft',
88
+ designcraft: 'DesignCraft', wordcraft: 'WordCraft', deckcraft: 'DeckCraft',
89
+ gridcraft: 'GridCraft', cadcraft: 'CADCraft', soundcraft: 'SoundCraft',
90
+ };
91
+
73
92
  /* ============================ 配置 ============================ */
74
93
 
75
94
  async function loadConfig() {
@@ -77,6 +96,7 @@ async function loadConfig() {
77
96
  bins: { ...DEFAULT_CONFIG.bins },
78
97
  control: { ...DEFAULT_PORTS },
79
98
  configPath: null, // 实际生效的配置文件(供界面显示"配置目录")
99
+ autoDiscovered: false, // 首次运行是否由自动探测生成了配置
80
100
  };
81
101
  // 配置查找顺序:~/.dsh(持久,推荐 —— 更新替换包目录也不丢)→ 包目录(旧版兼容)
82
102
  const candidates = [
@@ -101,9 +121,46 @@ async function loadConfig() {
101
121
  break; // 第一个可解析的配置生效
102
122
  } catch { /* 该候选不存在/非法 → 试下一个 */ }
103
123
  }
124
+ // 首次运行(没有任何配置文件):自动探测本机家族软件安装位置并生成持久配置
125
+ if (!cfg.configPath) {
126
+ try {
127
+ const found = await discoverBinPaths();
128
+ const target = path.join(os.homedir(), '.dsh', 'artcraft.config.json');
129
+ await fs.mkdir(path.dirname(target), { recursive: true });
130
+ await fs.writeFile(target, JSON.stringify({ bins: { ...cfg.bins, ...found }, control: { ...cfg.control } }, null, 2) + '\n');
131
+ for (const k of Object.keys(found)) cfg.bins[k] = found[k];
132
+ cfg.configPath = target;
133
+ cfg.autoDiscovered = Object.keys(found).length > 0;
134
+ } catch { /* 自动探测失败不影响默认行为 */ }
135
+ }
104
136
  return cfg;
105
137
  }
106
138
 
139
+ /** 自动探测:扫描常见安装位置(Program Files 等)里的家族 CLI */
140
+ async function discoverBinPaths() {
141
+ const isWin = process.platform === 'win32';
142
+ const pf = process.env['ProgramFiles'] || (isWin ? 'C:\\Program Files' : '/usr/local/bin');
143
+ const pf86 = process.env['ProgramFiles(x86)'] || '';
144
+ const lap = process.env['LOCALAPPDATA']
145
+ ? path.join(process.env['LOCALAPPDATA'], 'Programs')
146
+ : path.join(os.homedir(), '.local');
147
+ const roots = [pf, ...(pf86 ? [pf86] : []), lap];
148
+ const found = {};
149
+ for (const key of BINS) {
150
+ const cliName = cliNameFor(key);
151
+ const names = [APP_DISPLAY[key], key, key.toUpperCase(), key.toLowerCase()];
152
+ outer: for (const root of roots) {
153
+ for (const name of names) {
154
+ const exe = path.join(root, name, cliName);
155
+ try {
156
+ if ((await fs.stat(exe)).isFile()) { found[key] = path.join(root, name); break outer; }
157
+ } catch { /* 继续 */ }
158
+ }
159
+ }
160
+ }
161
+ return found;
162
+ }
163
+
107
164
  /* ============================ 路径与进程 ============================ */
108
165
 
109
166
  /**
@@ -421,7 +478,7 @@ async function statusInfo(cfg, meta) {
421
478
  plugin: NAME,
422
479
  version: VERSION,
423
480
  platform: process.platform,
424
- config: { path: configPath, found: !!configPath },
481
+ config: { path: configPath, found: !!configPath, autoDiscovered: !!cfg.autoDiscovered },
425
482
  bins,
426
483
  control: cfg.control,
427
484
  jobs: {
@@ -533,17 +590,30 @@ async function detectInstall() {
533
590
  return { mode: repoDir ? 'git' : 'npm', here, real, repoDir, profileDir, gitClean, branch };
534
591
  }
535
592
 
593
+ const REGISTRIES = ['https://registry.npmjs.org', 'https://registry.npmmirror.com'];
594
+
536
595
  async function fetchLatestVersion() {
537
- const url = `https://registry.npmjs.org/${PKG_NAME.replace('/', '%2f')}/latest`;
538
- try {
539
- const res = await fetch(url, { signal: AbortSignal.timeout(12000) });
540
- if (res.status === 404) return { latest: null, note: 'npm 上还没有这个包' };
541
- if (!res.ok) throw new Error('registry 返回 ' + res.status);
542
- const j = await res.json();
543
- return { latest: j.version ?? null, tarball: j.dist?.tarball ?? null, integrity: j.dist?.integrity ?? null };
544
- } catch (e) {
545
- return { latest: null, note: '查询 npm 失败: ' + String(e?.cause?.message || e?.message || e) };
596
+ const slug = PKG_NAME.replace('/', '%2f');
597
+ const errors = [];
598
+ // 回退链:官方源 → npmmirror 镜像(国内无代理机器也能完成检查与更新)
599
+ for (const reg of REGISTRIES) {
600
+ try {
601
+ const res = await fetch(`${reg}/${slug}/latest`, { signal: AbortSignal.timeout(12000) });
602
+ if (res.status === 404) { errors.push(`${reg}: 404`); continue; }
603
+ if (!res.ok) throw new Error('HTTP ' + res.status);
604
+ const j = await res.json();
605
+ if (!j.version) { errors.push(`${reg}: 无版本数据`); continue; }
606
+ return {
607
+ latest: j.version ?? null,
608
+ tarball: j.dist?.tarball ?? null,
609
+ integrity: j.dist?.integrity ?? null,
610
+ source: reg,
611
+ };
612
+ } catch (e) {
613
+ errors.push(`${reg}: ${String(e?.cause?.message || e?.message || e)}`);
614
+ }
546
615
  }
616
+ return { latest: null, note: '查询失败(官方源与 npmmirror 均不可达)—— ' + errors.join(';') };
547
617
  }
548
618
 
549
619
  async function updateCheck() {
@@ -585,8 +655,20 @@ async function updateApply() {
585
655
  await fs.mkdir(tmp, { recursive: true });
586
656
  try {
587
657
  const tgzPath = path.join(tmp, 'pkg.tgz');
588
- const res = await fetch(meta.tarball, { signal: AbortSignal.timeout(180000) });
589
- if (!res.ok) throw new Error('下载 tarball 失败: HTTP ' + res.status);
658
+ // 下载:优先元数据自带的官方地址;失败时回退 npmmirror 镜像(sha512 校验保证内容一致)
659
+ let res;
660
+ try {
661
+ res = await fetch(meta.tarball, { signal: AbortSignal.timeout(180000) });
662
+ if (!res.ok) throw new Error('HTTP ' + res.status);
663
+ } catch (e) {
664
+ const slug = PKG_NAME.replace('/', '%2f');
665
+ const mirrorUrl = `https://registry.npmmirror.com/${slug}/-/${PKG_NAME.split('/')[1]}-${meta.latest}.tgz`;
666
+ WriteMirrorRetry: {
667
+ res = await fetch(mirrorUrl, { signal: AbortSignal.timeout(180000) });
668
+ if (!res.ok) throw new Error('HTTP ' + res.status);
669
+ break WriteMirrorRetry;
670
+ }
671
+ }
590
672
  const buf = Buffer.from(await res.arrayBuffer());
591
673
  if (buf.length > 64 * 1024 * 1024) throw new Error('tarball 超过 64MB 上限,已终止');
592
674
  const digest = crypto.createHash('sha512').update(buf).digest('base64');
@@ -663,7 +745,7 @@ export function apply(ctx) {
663
745
 
664
746
  attempt({
665
747
  name: 'artcraft_status',
666
- description: '检查 ArtCraft 家族 7 个 CLI 是否可用,报告配置目录、控制通道端口与任务状态。本插件只是桥接,产物路径由调用方决定。',
748
+ description: '检查 ArtCraft 家族 12 个 CLI 是否可用,报告配置目录、控制通道端口与任务状态。本插件只是桥接,产物路径由调用方决定。',
667
749
  schema: { type: 'object', properties: {}, additionalProperties: false },
668
750
  async execute() {
669
751
  const cfg = await loadConfig();
@@ -673,7 +755,7 @@ export function apply(ctx) {
673
755
 
674
756
  attempt({
675
757
  name: 'artcraft_cli',
676
- description: '运行 ArtCraft 家族 CLI 的任意子命令(photocraft-cli / filmcraft-cli / effectcraft-cli)。长渲染请改用 HTTP POST /artcraft/api/job 后台执行。',
758
+ description: '运行 ArtCraft 家族 CLI 的任意子命令(photocraft-cli / wordcraft-cli / soundcraft-cli 等 12 个,bin 参数的 enum 即完整清单)。长渲染请改用 HTTP POST /artcraft/api/job 后台执行。',
677
759
  schema: {
678
760
  type: 'object',
679
761
  properties: {
@@ -697,24 +779,25 @@ export function apply(ctx) {
697
779
 
698
780
  attempt({
699
781
  name: 'artcraft_control',
700
- description: '向运行中的 FilmCraft(--control 9876) / EffectCraft(--control 9877) JSON 控制通道发送一条命令,如 engine.execute / ui.screenshot。PhotoCraft 控制通道为鉴权模式,不在代理范围。',
782
+ description: '向运行中的 ArtCraft JSON 控制通道发送一条命令(同族 JSON-lines 协议:engine.execute / engine.commands / ui.inspect / ui.screenshot…)。已知默认端口:filmcraft(--control 9876) / effectcraft(9877) / vectorcraft(7979) / lightcraft(7980);其余应用端口动态,可传 port 直连。PhotoCraft 控制通道为令牌鉴权模式、PdfCraft 为文件式,不在代理范围。',
701
783
  schema: {
702
784
  type: 'object',
703
785
  properties: {
704
- app: { type: 'string', enum: ['filmcraft', 'effectcraft'], description: '目标应用(决定端口)' },
705
- method: { type: 'string', description: '如 engine.execute / ui.inspect / ui.screenshot' },
786
+ app: { type: 'string', enum: ['filmcraft', 'effectcraft', 'vectorcraft', 'lightcraft'], description: '目标应用(决定默认端口)' },
787
+ port: { type: 'number', description: '可选:直接指定端口(覆盖 app 默认;用于端口动态的应用)' },
788
+ method: { type: 'string', description: '如 engine.execute / engine.commands / ui.inspect / ui.screenshot' },
706
789
  params: { type: 'object', description: '命令参数' },
707
790
  timeoutMs: { type: 'number' },
708
791
  },
709
- required: ['app', 'method'],
792
+ required: ['method'],
710
793
  additionalProperties: false,
711
794
  },
712
795
  async execute(args) {
713
796
  const cfg = await loadConfig();
714
- const port = cfg.control[String(args.app)];
715
- if (!port) throw new Error(`未知 app: ${args.app}`);
797
+ const port = Number(args.port) > 0 ? Number(args.port) : cfg.control[String(args.app ?? '')];
798
+ if (!(Number(port) > 0)) throw new Error('需要 app (filmcraft|effectcraft|vectorcraft|lightcraft) 或 port');
716
799
  const result = await controlCall(port, String(args.method), args.params ?? {}, { timeoutMs: args.timeoutMs });
717
- return { app: args.app, port, method: args.method, result };
800
+ return { app: args.app ?? null, port, method: args.method, result };
718
801
  },
719
802
  });
720
803
 
@@ -793,6 +876,92 @@ export function apply(ctx) {
793
876
  },
794
877
  });
795
878
 
879
+ attempt({
880
+ name: 'artcraft_discover',
881
+ description: '自动探测本机 ArtCraft 家族软件安装位置(Program Files / LocalAppData\\Programs 等),写入/修复配置:填充默认空缺项,并自动纠正已失效的路径(改安装目录、卸载重装到别处);用户显式配置仍有效则不动。',
882
+ schema: { type: 'object', properties: {}, additionalProperties: false },
883
+ async execute() {
884
+ const cfg = await loadConfig();
885
+ const found = await discoverBinPaths();
886
+ const applied = {};
887
+ for (const k of BINS) {
888
+ if (!found[k]) continue;
889
+ let effective = null;
890
+ try { effective = await resolveBin(cfg, k); } catch { /* 未配置 */ }
891
+ const ok = effective ? await fileExists(effective) : false;
892
+ if (ok) continue; // 现有配置仍有效 → 不动
893
+ applied[k] = found[k];
894
+ }
895
+ if (!Object.keys(applied).length) {
896
+ return { applied: {}, configPath: cfg.configPath, note: '家族程序路径均已就绪,无需更新' };
897
+ }
898
+ const target = cfg.configPath || path.join(os.homedir(), '.dsh', 'artcraft.config.json');
899
+ let obj = {};
900
+ try { obj = JSON.parse(await fs.readFile(target, 'utf8')); } catch { /* 新文件 */ }
901
+ obj.bins = { ...(obj.bins ?? {}), ...applied };
902
+ await fs.mkdir(path.dirname(target), { recursive: true });
903
+ await fs.writeFile(target, JSON.stringify(obj, null, 2) + '\n');
904
+ return { applied, configPath: target, note: '已写入配置,立即生效' };
905
+ },
906
+ });
907
+
908
+ attempt({
909
+ name: 'artcraft_recipes',
910
+ description: '内置专家处理方案库:ArtCraft 家族的专业工作流配方(短剧海报、分镜批量调色、横转竖、响度标准化、SRT 字幕烧录、动态片头、多平台分发包、先探测后验证纪律)。action=list 列出全部;action=get + id 取单个配方的专业原则、执行步骤与验证清单。',
911
+ schema: {
912
+ type: 'object',
913
+ properties: {
914
+ action: { type: 'string', enum: ['list', 'get'] },
915
+ id: { type: 'string', description: 'action=get 时的配方 id' },
916
+ },
917
+ required: ['action'],
918
+ additionalProperties: false,
919
+ },
920
+ async execute(args) {
921
+ const recipesPath = path.join(HERE, 'recipes', 'recipes.json');
922
+ const catalog = JSON.parse(await fs.readFile(recipesPath, 'utf8'));
923
+ if (String(args.action) === 'get') {
924
+ const r = catalog.recipes.find((x) => x.id === String(args.id ?? ''));
925
+ if (!r) throw new Error('配方不存在: ' + args.id + '(可用: ' + catalog.recipes.map((x) => x.id).join(', ') + ')');
926
+ return r;
927
+ }
928
+ return {
929
+ version: catalog.version,
930
+ recipes: catalog.recipes.map((r) => ({ id: r.id, title: r.title, apps: r.apps, goal: r.goal })),
931
+ hint: 'action=get + id 取完整配方(专业原则 + 执行步骤 + 验证清单)',
932
+ };
933
+ },
934
+ });
935
+
936
+ attempt({
937
+ name: 'artcraft_apps',
938
+ description: 'ArtCraft 家族 12 应用的自动化能力目录:每个应用的定位、对标软件、CLI 子命令用法、控制通道端口、MCP 可用性与仓库/文档链接。action=list 列出全部;action=get + id 取单个应用的完整能力卡。要拿某应用的实时命令注册表,用 artcraft_cli 跑该应用的 commands 子命令。',
939
+ schema: {
940
+ type: 'object',
941
+ properties: {
942
+ action: { type: 'string', enum: ['list', 'get'] },
943
+ id: { type: 'string', description: 'action=get 时的应用 id(如 wordcraft)' },
944
+ },
945
+ required: ['action'],
946
+ additionalProperties: false,
947
+ },
948
+ async execute(args) {
949
+ const catalogPath = path.join(HERE, 'apps', 'catalog.json');
950
+ const catalog = JSON.parse(await fs.readFile(catalogPath, 'utf8'));
951
+ if (String(args.action) === 'get') {
952
+ const a = catalog.apps.find((x) => x.id === String(args.id ?? ''));
953
+ if (!a) throw new Error('应用不存在: ' + args.id + '(可用: ' + catalog.apps.map((x) => x.id).join(', ') + ')');
954
+ return a;
955
+ }
956
+ return {
957
+ version: catalog.version,
958
+ controlProtocol: catalog.controlProtocol,
959
+ apps: catalog.apps.map((a) => ({ id: a.id, name: a.name, tagline: a.tagline, ref: a.ref, cli: a.cli, control: a.control, mcp: a.mcp, stage: a.stage })),
960
+ hint: 'action=get + id 取完整能力卡(子命令用法 + 控制端口 + 仓库/文档链接)',
961
+ };
962
+ },
963
+ });
964
+
796
965
  return () => {
797
966
  for (const d of disposers) { try { d(); } catch { /* ignore */ } }
798
967
  };
@@ -939,12 +1108,60 @@ export function apply(ctx) {
939
1108
  sendJson(res, 200, await updateApply());
940
1109
  }),
941
1110
 
1111
+ makeRoute(`/${NAME}/api/discover`, async (req, res) => {
1112
+ if (req.method !== 'POST') throw new Error('仅支持 POST');
1113
+ const cfg = await loadConfig();
1114
+ const found = await discoverBinPaths();
1115
+ // 只填充默认空缺项;显式配置仅在已失效(改安装目录/卸载重装)时自动纠正,有效则不动
1116
+ const applied = {};
1117
+ for (const k of BINS) {
1118
+ if (!found[k]) continue;
1119
+ let effective = null;
1120
+ try { effective = await resolveBin(cfg, k); } catch { /* 未配置 */ }
1121
+ const ok = effective ? await fileExists(effective) : false;
1122
+ if (ok) continue;
1123
+ applied[k] = found[k];
1124
+ }
1125
+ if (Object.keys(applied).length) {
1126
+ const target = cfg.configPath || path.join(os.homedir(), '.dsh', 'artcraft.config.json');
1127
+ let obj = {};
1128
+ try { obj = JSON.parse(await fs.readFile(target, 'utf8')); } catch { /* 新文件 */ }
1129
+ obj.bins = { ...(obj.bins ?? {}), ...applied };
1130
+ await fs.mkdir(path.dirname(target), { recursive: true });
1131
+ await fs.writeFile(target, JSON.stringify(obj, null, 2) + '\n');
1132
+ }
1133
+ sendJson(res, 200, {
1134
+ discovered: found,
1135
+ applied,
1136
+ configPath: cfg.configPath,
1137
+ note: Object.keys(applied).length ? '已探测并写入配置,立即生效' : '没有需要更新的家族程序',
1138
+ });
1139
+ }),
1140
+
1141
+ makeRoute(`/${NAME}/api/apps`, async (req, res) => {
1142
+ if (req.method !== 'GET') throw new Error('仅支持 GET');
1143
+ const url = new URL(req.url, 'http://x');
1144
+ const id = url.searchParams.get('id');
1145
+ const catalog = JSON.parse(await fs.readFile(path.join(HERE, 'apps', 'catalog.json'), 'utf8'));
1146
+ if (id) {
1147
+ const a = catalog.apps.find((x) => x.id === id);
1148
+ if (!a) throw new Error('应用不存在: ' + id + '(可用: ' + catalog.apps.map((x) => x.id).join(', ') + ')');
1149
+ sendJson(res, 200, a);
1150
+ return;
1151
+ }
1152
+ sendJson(res, 200, {
1153
+ version: catalog.version,
1154
+ controlProtocol: catalog.controlProtocol,
1155
+ apps: catalog.apps.map((a) => ({ id: a.id, name: a.name, tagline: a.tagline, ref: a.ref, cli: a.cli, control: a.control, mcp: a.mcp, stage: a.stage })),
1156
+ });
1157
+ }),
1158
+
942
1159
  makeRoute(`/${NAME}/api/control`, async (req, res) => {
943
1160
  if (req.method !== 'POST') throw new Error('仅支持 POST');
944
1161
  const cfg = await loadConfig();
945
1162
  const body = await readJson(req);
946
1163
  const port = body.port ?? cfg.control[String(body.app ?? '')];
947
- if (!(Number(port) > 0)) throw new Error('需要 app (filmcraft|effectcraft) 或 port');
1164
+ if (!(Number(port) > 0)) throw new Error('需要 app (filmcraft|effectcraft|vectorcraft|lightcraft) 或 port');
948
1165
  const result = await controlCall(
949
1166
  Number(port),
950
1167
  String(body.method),
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@ninkch/dsh-artcraft",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "type": "module",
5
- "description": "Drive the open-source ArtCraft studio apps (PhotoCraft / FilmCraft / EffectCraft) from DeepSeek Harness ? agent tools, a same-origin HTTP API, and plugins-page status with self-update.",
5
+ "description": "Drive the open-source ArtCraft studio suite (12 family apps: PhotoCraft, VectorCraft, FilmCraft, LightCraft, PdfCraft, EffectCraft, DesignCraft, WordCraft, DeckCraft, GridCraft, CADCraft, SoundCraft) from DeepSeek Harness: agent tools, a same-origin HTTP API, and plugins-page status with self-update.",
6
6
  "keywords": [
7
7
  "deepseek-harness",
8
8
  "dsh-plugin",
@@ -35,6 +35,8 @@
35
35
  "client.js",
36
36
  "cordis.patch.yml",
37
37
  "artcraft.config.json.example",
38
+ "apps",
39
+ "recipes",
38
40
  "README.md",
39
41
  "README.zh-CN.md",
40
42
  "NOTICE.md"
@@ -0,0 +1,143 @@
1
+ {
2
+ "version": 1,
3
+ "updated": "2026-10-10",
4
+ "recipes": [
5
+ {
6
+ "id": "poster-short-drama",
7
+ "title": "短剧海报(竖版)",
8
+ "apps": ["photocraft"],
9
+ "goal": "生成竖版短剧海报:标题层级、装饰元素、无损输出",
10
+ "principles": [
11
+ "视觉层级:主标题 → 副标题 → 装饰元素,字号/字重至少差 2 级,同屏不超过 3 个信息层",
12
+ "对比度:文字与背景亮度差 ≥ 4.5:1,浅背景加深色字,深背景加浅色字,或加投影/描边兜底",
13
+ "安全边距:四周留 5% 不放关键内容(平台裁切友好)",
14
+ "中文字体显式指定(如 Noto Sans SC),不要依赖默认字体(缺 CJK 字形会变方框)",
15
+ "HTTP 传中文参数一律走 UTF-8 字节体,避免编码损坏"
16
+ ],
17
+ "steps": [
18
+ "photocraft-cli run --new '{\"width\":1080,\"height\":1440}' --cmd <背景填充/渐变命令> --out <工作空间>/poster.png",
19
+ "标题:Noto Sans SC,主标题字号 ≥ 120,副标题 ≤ 48;装饰条用像素层绘制",
20
+ "逐层 --cmd 叠加(每条 --params 作用于前一个 --cmd),最后 --out 输出 PNG 无损"
21
+ ],
22
+ "checks": ["取回产物做视觉检查:字形完整、层级清晰、四边安全区无关键内容"]
23
+ },
24
+ {
25
+ "id": "storyboard-color-grade",
26
+ "title": "分镜批量调色(一致性)",
27
+ "apps": ["photocraft"],
28
+ "goal": "整批分镜图统一色调,保持跨镜一致性",
29
+ "principles": [
30
+ "先定基调再微调:一条柔和 S 曲线 + 少量 vibrance,避免逐张各调各的",
31
+ "保护肤色:调整幅度宁小勿大,人脸偏色比不够风格化更伤观感",
32
+ "锐化放在调色之后(amount 60-80),避免锐化放大噪点",
33
+ "actions.json 一处定义、整批复用,保证批次内完全一致"
34
+ ],
35
+ "steps": [
36
+ "写 craft/grade.json:[{\"cmd\":\"layer.newAdjustmentLayer.curves\",\"params\":{\"points\":[[0,0],[64,48],[192,212],[255,255]]}},{\"cmd\":\"filter.sharpen.smartSharpen\",\"params\":{\"amount\":80}}]",
37
+ "photocraft-cli batch --actions <项目>/craft/grade.json --in <项目>/media --out <项目>/out/graded",
38
+ "抽 2-3 张对比原图检查,不满意只调曲线中段(64,48 / 192,212 两点)重跑"
39
+ ],
40
+ "checks": ["抽样对比:同场景相邻分镜色调一致;高光不过曝、暗部不死黑"]
41
+ },
42
+ {
43
+ "id": "vertical-reframe",
44
+ "title": "横转竖重构图",
45
+ "apps": ["filmcraft", "photocraft"],
46
+ "goal": "16:9 素材适配 9:16 竖屏,三种策略按画面主体选择",
47
+ "principles": [
48
+ "策略选择:主体居中 → 居中裁切;主体偏侧 → 位移裁切(保主体);多主体/全景 → 模糊填充(上原图下补景,不裁掉信息)",
49
+ "人脸安全:裁切框上缘留在头顶上方 10%,下缘到胸口",
50
+ "分辨率对齐目标平台:抖音/快手/视频号 1080×1920@30fps"
51
+ ],
52
+ "steps": [
53
+ "filmcraft-cli probe <素材> 先量分辨率/时长/帧率(永远先探测再动手)",
54
+ "序列设 1080×1920@30,放置后按位移/缩放保留主体",
55
+ "导出 H.264:--quality 80 起步"
56
+ ],
57
+ "checks": ["导出后 probe 验证分辨率/时长;抽帧目检主体完整、不切脸"]
58
+ },
59
+ {
60
+ "id": "loudness-web",
61
+ "title": "网络平台响度标准化",
62
+ "apps": ["filmcraft"],
63
+ "goal": "成片响度对齐平台标准,避免用户手动调音量",
64
+ "principles": [
65
+ "主流短视频平台目标:integrated -14 LUFS,true peak ≤ -1 dBTP",
66
+ "导出时一次完成(file.exportMedia loudnessLufs 参数),不要二次压限",
67
+ "BGM 与人声分层:人声为主轨,BGM 侧链闪避 6-10 dB"
68
+ ],
69
+ "steps": [
70
+ "exec file.exportMedia {\"path\":<out>,\"format\":\"h264\",\"loudnessLufs\":-14,\"wait\":true}",
71
+ "probe 产物核对时长/编码/音轨"
72
+ ],
73
+ "checks": ["probe 报告音轨存在且响度参数已应用;多平台分发时每平台独立导出"]
74
+ },
75
+ {
76
+ "id": "srt-burn",
77
+ "title": "字幕烧录(SRT)",
78
+ "apps": ["filmcraft"],
79
+ "goal": "SRT 字幕导入并烧录进成片",
80
+ "principles": [
81
+ "单行 ≤ 15 个中文字符,超长拆条;每条最短停留 1 秒",
82
+ "位置:底部安全区,避开平台 UI(进度条/互动栏约占底部 15%)",
83
+ "样式统一:描边 2-3px 或半透明底条,全片一种字体"
84
+ ],
85
+ "steps": [
86
+ "exec captions.import {\"path\":<srt 绝对路径>}",
87
+ "可选 exec captions.setStyle {\"font\":…,\"size\":…,\"outline\":…}",
88
+ "exec file.exportMedia {\"path\":<out>,\"format\":\"h264\",\"burnCaptions\":true,\"wait\":true}"
89
+ ],
90
+ "checks": ["抽帧目检字幕:无溢出、无遮挡主体、时间轴同步"]
91
+ },
92
+ {
93
+ "id": "title-motion",
94
+ "title": "动态片头/花字(EffectCraft)",
95
+ "apps": ["effectcraft"],
96
+ "goal": "生成动态片头,叠加回剪辑或直接使用",
97
+ "principles": [
98
+ ".ecproj 是版本化 JSON —— 直接生成或模板替换文本,比逐帧描述高效",
99
+ "动画时长 1.5-3s:入场 0.3-0.5s 缓动(ease),停留 ≥ 1s,出场 0.3s",
100
+ "双出口:MP4 直用;或导出 Lottie 嵌网页预览;带 alpha 的 ProRes 4444 可叠进 FilmCraft 当图形层"
101
+ ],
102
+ "steps": [
103
+ "生成/填充 intro.ecproj(composition 尺寸对齐主序列)",
104
+ "effectcraft-cli render --comp Main --out <项目>/out/intro.mp4 <项目>/craft/intro.ecproj",
105
+ "或 exec file.exportLottie 导出 .json 给网页端播放"
106
+ ],
107
+ "checks": ["render 后 probe 时长/分辨率;视觉检查动画节奏(入场不突兀、停留可读完整标题)"]
108
+ },
109
+ {
110
+ "id": "delivery-pack",
111
+ "title": "多平台分发包",
112
+ "apps": ["filmcraft"],
113
+ "goal": "同一成片产出多平台版本(分辨率/帧率/响度/命名规范)",
114
+ "principles": [
115
+ "平台参数表:抖音/快手/视频号 1080×1920@30 -14LUFS;B 站/西瓜 1920×1080@30 -14LUFS",
116
+ "命名规范:<项目>_<平台>_v<序号>.mp4,一次导出成组不散落",
117
+ "每平台独立导出(各自过 loudness 与 probe),不要一份文件多处转发"
118
+ ],
119
+ "steps": [
120
+ "逐平台 exec file.exportMedia {\"path\":<out>,\"format\":\"h264\",\"width\":…,\"height\":…,\"loudnessLufs\":-14,\"wait\":true}",
121
+ "每个产物跑一次 probe 核对"
122
+ ],
123
+ "checks": ["probe 全部产物:分辨率/帧率/音轨符合平台表;文件命名一致"]
124
+ },
125
+ {
126
+ "id": "probe-verify",
127
+ "title": "通用纪律:先探测、后验证",
128
+ "apps": ["filmcraft", "photocraft", "effectcraft"],
129
+ "goal": "所有媒体操作的通用工作流纪律(来自专业剪辑技能的通用实践)",
130
+ "principles": [
131
+ "先探测再动手:永远先 probe 素材拿真实时长/分辨率/帧率/音轨,不从文件名猜",
132
+ "能无损不重编:剪切片段优先流复制,只在必须(精确剪切/滤镜/换格式)时重编码",
133
+ "做完必验证:产物 probe 一次 + 关键帧目检,报告里写明验证结果",
134
+ "诚实标注:成功是 Done、失败是 Failed,部分完成必须写出缺口"
135
+ ],
136
+ "steps": [
137
+ "任何处理前:filmcraft-cli probe <media>",
138
+ "处理后:filmcraft-cli probe <out> + 抽帧目检"
139
+ ],
140
+ "checks": ["probe 输出与预期一致;差异写入报告而不是含糊带过"]
141
+ }
142
+ ]
143
+ }