dsh-posterflow-ai
一个 DeepSeek Harness(DSH)插件:在 Web 界面侧栏加一个「开启生图模式」入口,点击后铺满整个窗口播放一段过场动画,然后跳转到 PosterFlow。
English: README.en.md
它长什么样
侧栏 —— 排在「插件」「自动化任务」下面,最后一行
┌────────────────────────────┐
│ + 新会话 │
├────────────────────────────┤
│ ◇ 插件 │
│ 🕘 自动化任务 │
│ 🖼 开启生图模式 ← 新增 │
└────────────────────────────┘
↓ 点击
过场动画铺满整个 DSH 窗口(object-fit: cover)
结束 / 出错 / 超时 / 点击画面 → 立即继续
↓
打开 https://www.posterflow-ai.xyz/(默认新标签页)
面板上只保留两行文字:生图模式已开启 与 手动打开 PosterFlow。诊断信息只写控制台
(DSH 窗口按 Ctrl+Shift+I,过滤 [posterflow-ai]),界面上不显示。
安装
四种方式,按"对方环境"挑:
| 方式 | 地址 / 命令 | 需要 git | 说明 |
|---|
| npm 包名(推荐) | dsh-posterflow-ai | 不需要 | 已发布到 npm 并实测通过:界面里只输这一串即可 |
| 源码压缩包 | GitHub → Code → Download ZIP | 不需要 | 仓库带构建产物 lib/,解压即可装(已实测) |
| Git 仓库 | github:Something11235/dsh-posterflow-ai#main | 需要 | 机器上有 git 时可用;无 git 会报 'git' 不是内部或外部命令 |
| 本地包 | 指向本地目录 / .tgz | 不需要 | 内网、无 git 的机器用这个 |
刚发布不久时的特殊情况:pnpm 11 带"最小发布年龄"供应链保护,极新的版本可能被解析策略跳过
(现象是装到了 0.0.0-stage 这类占位版并报 tarball 校验错)。这时指定版本号即可,实测有效:
dsh plugin --profile <你的profile> add "dsh-posterflow-ai@0.1.0"
等版本"变老"之后,裸包名也会恢复正常(本机实测裸包名随后即安装成功)。
安装失败排查
- 报
'git' 不是内部或外部命令(或 Command failed with exit code 1: git ls-remote ...)
→ 那台机器没装 Git 或 Git 不在 PATH。pnpm add github:... 必须调用 git ls-remote 解析仓库。
装 Git for Windows 后完全退出 DSH 再打开重试;
或者改用 npm 包名 / 本地包安装(都不需要 git)。
- 报
ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED
→ 该 git 依赖的 package.json 里有 prepare 脚本。本插件没有 prepare,且 lib/ 已随仓库提交。
- 界面装完按钮不出现
→
dsh.client 声明的扫描结果会缓存:必须完全退出应用(托盘退出)再启动。
- 报代理相关错误(
ECONNREFUSED 127.0.0.1:65532)
→ 那是 ~/.npmrc 里的失效代理;pnpm 不会向上层目录找 .npmrc,请在profile 目录里放一份
.npmrc(proxy= / https-proxy=)或修掉全局代理设置。
机器上没有 git 时(已实测走通)
- 打开 https://github.com/Something11235/dsh-posterflow-ai → Code → Download ZIP
(或直接下
https://github.com/Something11235/dsh-posterflow-ai/archive/refs/heads/main.tar.gz,约 2 MB)
- 解压到任意目录,例如
D:\dsh-posterflow-ai-main。
仓库里已经带了 lib/client.js、lib/index.js,所以不需要 Node、不需要 pnpm install、不需要编译。
- 安装(二选一):
- 应用内「设置 → 插件 → 添加插件」→ 选本地路径,指向该目录;
- 命令行:
dsh plugin --profile <你的profile> add "file:D:\dsh-posterflow-ai-main"
- 完全退出 DSH 再打开(
dsh.client 扫描结果缓存到重启),侧栏最后一行就会出现入口。
- 装完可以删掉解压目录:pnpm 已把文件复制/硬链进 profile,实测把源目录改名后插件仍能正常加载。
# 从 GitHub 安装(推荐,会被社区市场自动收录)
dsh plugin --profile web add "github:Something11235/dsh-posterflow-ai#main"
# 本地开发
dsh plugin --profile plugindev add link:/绝对/路径/dsh-posterflow-ai
确认组合树:
dsh --profile plugindev --dump-config | grep -A10 'dsh-posterflow-ai'
桌面版注意:desktop profile 由 Electron 应用独占,CLI 会拒绝操作它。
桌面版请用应用内「设置 → 插件 → 添加插件」粘贴仓库地址安装,然后完全退出应用再打开
(dsh.client 声明的扫描结果会缓存到重启)。
配置
在你自己 profile 的 cordis.patch.yml 里按行 id 覆盖。config 是整行替换、不是深合并——没写的字段回落到 schema 默认值。
- id: dsh-posterflow-ai
name: dsh-posterflow-ai
config:
targetUrl: https://www.posterflow-ai.xyz/
buttonLabel: 开启生图模式
openIn: new-tab # 或 same-tab
transition: video # 或 none(点击后直接跳转)
videoSource: inline # 或 route
muted: true
maxWaitMs: 8000
| 字段 | 类型 | 默认 | 含义 |
|---|
targetUrl | string | https://www.posterflow-ai.xyz/ | 过场结束后跳转的地址 |
buttonLabel | string | 开启生图模式 | 按钮文案 |
openIn | new-tab | same-tab | new-tab | 新标签页打开(不会丢掉当前会话界面)或当前页跳转 |
transition | video | none | video | 是否播放过场视频 |
videoSource | inline | route | inline | inline = 用产物内联的视频(一定能播);route = 用宿主 HTTP 路由(仅在页面确由 ctx.webServer 提供服务时有效) |
videoFile | string | assets/transition.webm | route 模式下要提供的包内文件 |
muted | boolean | false | 默认带声音;被浏览器自动播放策略拒绝时自动降级为静音,并在画面右上角给出「开启声音」按钮 |
maxWaitMs | integer 0–60000 | 8000 | 视频最长等待;超时直接跳转,不让用户卡在过场里 |
它是怎么工作的(两层)
这是一个双面插件:
| 半边 | 产物 | 做什么 |
|---|
| 宿主(Node) | lib/index.js | 只注册一条配置路由,把部署期配置以 JSON 递给浏览器(另保留一条可选视频路由给 videoSource: route) |
| 浏览器 | lib/client.js | 惰性 CJS 表,注册两处:侧栏主列表最后一行(sidebar.panellist,order 100)与同 id 的主面板(main keyed posterflow-ai),由后者播放内联过场并跳转 |
六个值得说明的取舍:
- 侧栏那一行不是普通按钮槽。
sidebar.panellist 的每个 id 对应一个主面板——「插件」「自动化任务」就是
plugins(order 0) 与 schedules(order 10)。侧栏自己渲染按钮、从注册元数据取 label,
我们的组件只负责图标(owner props 只有 size / active)。所以点这一行会切到同 id 的面板:
我们同时注册 main keyed posterflow-ai 来承载「过场 → 跳转」,并用 order 100 排在最后一行。
- 视频内联在产物里(默认),不依赖任何 HTTP 路由。 这是踩坑后的修正:桌面版 GUI 的
127.0.0.1:19387 不是 ctx.webServer 的路由面——实测连内核自己的 /plugins/... 都返回 404,
所以"宿主注册路由、页面去取"这条路在桌面版走不通,视频必然加载失败并立刻放行跳转
(表现就是"完全没有播放视频")。
现在源片先用 ffmpeg 压到 1080 宽 / CRF 48 / 24fps / 24kbps 单声道(2.66 MiB → 458 KB),
再由 scripts/embed-video.mjs 生成 data URI 内联进 lib/client.js(产物约 659 KB)。
换来的是一定能播:不依赖端口、协议、CORS 或路由。
- 过场铺满整个窗口。 覆盖层
position: fixed; inset: 0 + 视频 width/height: 100%、object-fit: cover,
所以是整窗填充而不是居中带黑边的信箱式播放。默认带声音起播(muted: false);
若带声音起播被自动播放策略拒绝,则静音重试保证画面一定播出来,
同时在画面底部居中给出「🔊 点击开启声音」——那一下是用户手势,必定能出声。
另外内联的短片用 ffmpeg loudnorm 把音轨规整到约 −15 dB(原始只有 −25 dB,偏轻到容易以为"没声音")。
- 一次点击只跑一次,而且只开一个标签页。 这条踩了两轮坑,最终结论:
- DSH 桌面版的 Electron 主进程对任何
window.open 都返回 deny,并顺手 shell.openExternal(url)
(见 app.asar/lib/main.js)。所以桌面版里 window.open 必然返回 null,而网站已经被宿主用系统浏览器打开过一次。
老代码把 null 当"被拦截"又对当前页 location.assign() → 新标签页与当前页各打开一次。
现在 null 只记为 blocked,绝不自动导航,改由面板提示用户点手动链接。
- 去重窗口从 1.5 秒放大到 20 秒(
LAUNCH_DEDUPE_MS):面板可能被重新挂载(React 严格模式 / slot 重注册),
而 1.5 秒挡不住"过场播完(约 6.5 秒)后再挂载一次"。
new-tab 路径只调用一次 window.open(具名窗口 + 打开后手动把 opener 置空)。
诊断计数(触发 / apply / effect / 渲染 / 被拦)仍然在维护,但只写控制台,界面上不显示。
测试:、(含"返回 null 不导航")、
(面板只有两行)。
开发
pnpm install
pnpm run embed # 由 assets/transition.webm 生成内联 data URI 模块
pnpm run typecheck && pnpm run lint && pnpm run test
pnpm run build
pnpm run test:artifact # 宿主产物:真 WebServer 上真发 HTTP 请求
pnpm run test:client # 浏览器产物:惰性 CJS 契约(纯 Node,无需浏览器)
七道质量门
| 门 | 覆盖什么 |
|---|
verify:embed | 内联视频模块与 assets/transition.webm 一致(防止改了视频忘了重新生成) |
typecheck | 严格 TS,含 client 半边的惰性 CJS 形态 |
lint | oxlint(生成的视频模块已排除) |
test | 55 个用例,六个文件:纯逻辑(Range 解析、路径逃逸防护)、注册契约(最后一行 + 同 id 主面板)、面板 UI(只有两行)、编排去重(20 秒窗口 / 进行中 / 诊断计数 / 声音判定)、开窗(只开一次、null 不导航)、真 WebServer + 真 HTTP 请求 |
build | tsdown 产出 lib/index.js(ESM)+ lib/client.js(IIFE 普通脚本,约 659 KB) |
test:artifact | 构建产物挂真 WebServer:路由可用、卸载即撤 |
test:client | 在 Node 里执行 lib/client.js:执行期 0 次模块请求、0 次 DOM 变更(惰性契约),materialize 后导出 name/inject/apply |
已知限制
- 只支持 Web 界面。 浏览器半边只在 Web 外壳里加载;
desktop/headless 里只有宿主半边。
- 改
dsh.client 声明需要重启(扫描结果缓存到重启);只有产物字节变化能在线生效。
- 过场视频是构建期内联的,改视频要重新跑
pnpm run embed 并重新构建(verify:embed 会在 CI 里拦住不同步)。
- 跳转走外部浏览器(默认新标签页)。"跳进 DSH 内置浏览器"需要
sidebarRightTabs 服务的契约,
该包未随包发布类型定义,目前未实现。
- 入口固定在侧栏主列表最后一行(
PANEL_ORDER = 100)。要挪位置改这个 order,或换成
sidebar.footer.action 这类按钮槽(那时只需注册一处)。可用挂载点见参考工作区的 reference/live-slot-catalog.md(90 个)。
维护者:发布到 npm
0. 首次:先有 npm 账号(只需一次)
- 打开 https://www.npmjs.com/signup 注册(用户名会公开显示为发布者,建议与 GitHub 同名;
邮箱必须能收信,发布前要验证)
- 建议顺手开 2FA(Authenticator App):npm 发布时会要求输入 6 位动态码,
不开也常会被要求邮箱一次性验证码
- 回到终端登录(会打开浏览器授权):
cd 'D:\Projects\DSH工作台\插件制作\dsh-posterflow-ai'
npm login # 首次登录;本目录的 .npmrc 已把机器上的失效代理置空,registry 直连
npm publish # prepublishOnly 会先跑七道门(嵌入校验/类型/lint/测试/构建/产物/被包契约),全绿才上传
包名 dsh-posterflow-ai 是无 scope 的,直接发到你的账号下;发布前可用
npm view dsh-posterflow-ai version 确认没被占用(返回 E404 就是空的)。
若终端报 ECONNREFUSED 127.0.0.1:65532,先 npm config delete proxy; npm config delete https-proxy。
发布后任何人(包括没装 git 的机器)在「设置 → 插件 → 添加插件」里只输 dsh-posterflow-ai
即可安装:不需要 git、不需要 Node、不需要编译。
发新版:改完 → npm version patch(或 minor / major)→ git push --follow-tags → npm publish。
许可
MIT