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'
桌面版注意:
desktopprofile 由 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 的面板: 我们同时注册mainkeyedposterflow-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); 若带声音起播被自动播放策略拒绝,则静音重试保证画面一定播出来, 同时在画面底部居中给出「🔊 点击开启声音」——那一下是用户手势,必定能出声。 另外内联的短片用 ffmpegloudnorm把音轨规整到约 −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 / 渲染 / 被拦)仍然在维护,但只写控制台,界面上不显示。 测试:tests/launcher.test.ts、tests/open-target.test.ts(含"返回 null 不导航")、tests/panel-ui.test.ts(面板只有两行)。
- DSH 桌面版的 Electron 主进程对任何
- client 半边读不到宿主的
Config,所以宿主用/posterflow-ai/config.json把它递过去 (Cache-Control: no-store)。client 侧读取失败时回落到内置默认值,而默认值就是"内联视频", 所以入口永远不会因为路由不通而失灵。 - 不
inject: ['webServer']。 用ctx.get('webServer')读取并降级,这样插件在 headless 之类的 profile 里也能正常加载(只是不注册路由),而不是因为依赖缺失一直等在那里。
开发
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
No comments yet. Be the first to write one.