在对话里描述镜头,自动生成视频并记账防重复扣费
29.3/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/28 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add tkliuxing/dsh-video-forge该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:仅索引
是什么
dsh 原生插件 · model
装得上吗
本站尚未做安装检查
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 11 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
读写/删除本地文件发起外部网络请求
查看扫描证据(共 63 处,展示前 61 条)
- 发起网络请求src/client/panel-api.ts:117
const response = await fetch(apiUrl(action), { - 读取本地文件src/compose.ts:145
const real = await realpath(candidate).catch(() => candidate) - 读取本地文件src/compose.ts:170
if (!(await stat(artifact.lastFrame.path).then(info => info.isFile(), () => false))) { - 读取本地文件src/destination.ts:89
if (!(await access(existing.path, constants.W_OK).then(() => true, () => false))) { - 写入本地文件src/destination.ts:198
await copyFile(frame.path, target, constants.COPYFILE_EXCL) - 写入本地文件src/destination.ts:200
await chmod(target, 0o666 & ~process.umask()).catch(() => undefined) - 删除本地文件src/destination.ts:207
await unlink(downloaded.path).catch(() => undefined) - 删除本地文件src/destination.ts:209
await unlink(downloaded.lastFrame.path).catch(() => undefined) - 删除本地文件src/destination.ts:211
await rmdir(dirname(downloaded.path)).catch(() => undefined) - 读取本地文件src/destination.ts:281
real = await realpath(cwd) - 读取本地文件src/destination.ts:307
const info = await stat(probe).catch(absent) - 读取本地文件src/destination.ts:308
if (info !== null) return { path: probe, real: await realpath(probe), directory: info.isDirectory(), dangling: false } - 读取本地文件src/destination.ts:310
if ((await lstat(probe).catch(absent))?.isSymbolicLink() === true) return { path: probe, real: probe, directory: false, dangling: true } - 写入本地文件src/destination.ts:346
await mkdir(wanted, { recursive: true }) - 读取本地文件src/destination.ts:348
const directory = await realpath(wanted) - 写入本地文件src/destination.ts:365
await copyFile(artifact.path, staging, constants.COPYFILE_EXCL) - 写入本地文件src/destination.ts:368
await chmod(staging, 0o666 & ~process.umask()) - 删除本地文件src/destination.ts:380
await rm(staging, { force: true }) - 写入本地文件src/destination.ts:408
await rename(staging, final) - 删除本地文件src/destination.ts:411
await unlink(final).catch(() => undefined) - 发起网络请求src/download.ts:76
const response = await options.fetch(options.url, { - 写入本地文件src/download.ts:97
await mkdir(options.directory, { recursive: true, mode: 0o700 }) - 写入本地文件src/download.ts:101
await writeFile(path, body, { mode: 0o600 }) - 发起网络请求src/download.ts:131
const response = await options.fetch(url, { method: 'GET', headers, signal }) - 写入本地文件src/download.ts:142
await writeFile(path, body, { mode: 0o600 }) - 读取本地文件src/ledger.ts:127
text = await readFile(this.#options.path, { encoding: 'utf8' }) - 写入本地文件src/ledger.ts:140
await rename(this.#options.path, quarantine).catch(() => undefined) - 写入本地文件src/ledger.ts:288
await mkdir(directory, { recursive: true, mode: 0o700 }) - 写入本地文件src/ledger.ts:291
await writeFile(temporary, body, { encoding: 'utf8', mode: 0o600 }) - 写入本地文件src/ledger.ts:293
await rename(temporary, this.#options.path) - 删除本地文件src/ledger.ts:295
await rm(temporary, { force: true }) - 读取本地文件src/media-route.ts:242
const real = await realpath(path) - 读取本地文件src/media-route.ts:243
const named = await stat(real) - 发起网络请求src/operations.ts:335
return fetch(input, init) - 读取本地文件src/panel-api.ts:286
return (await stat(artifact.path)).isFile() ? 'present' : 'missing' - 读取本地文件src/panel-api.ts:288
const info = await lstat(artifact.path) - 读取本地文件src/panel-api.ts:290
return (await realpath(artifact.path)) === artifact.path ? 'present' : 'missing' - 读取本地文件src/price-source.ts:269
raw = readFileSync(this.#options.path, 'utf8') - 写入本地文件src/price-source.ts:521
await mkdir(dirname(path), { recursive: true }) - 写入本地文件src/price-source.ts:523
await writeFile(temporary, payload, { mode: 0o600 }) - 写入本地文件src/price-source.ts:524
await rename(temporary, path) - 发起网络请求src/providers/adapter.ts:186
const response = await context.fetch(url, { - 发起网络请求src/providers/adapter.ts:227
const response = await context.fetch(url, { - 读取本地文件src/selection.ts:168
const reading = this.#readFile().then( - 读取本地文件src/selection.ts:226
async #readFile(): Promise<void> { - 读取本地文件src/selection.ts:229
text = await readFile(this.#options.path, 'utf8') - 写入本地文件src/selection.ts:294
await mkdir(directory, { recursive: true, mode: 0o700 }) - 写入本地文件src/selection.ts:297
await writeFile(temporary, `${JSON.stringify(document, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 }) - 写入本地文件src/selection.ts:299
await rename(temporary, this.#options.path) - 删除本地文件src/selection.ts:301
await rm(temporary, { force: true }) - 读取本地文件src/skills.ts:88
const body = await readFile(join(ASSETS_DIR, file), 'utf8') - 读取本地文件src/workspace-fs.ts:29
stat(target: WorkspaceTarget, signal?: AbortSignal): Promise<{ type: 'file' | 'directory' | 'other'; size?: number } | undefined> - 读取本地文件src/workspace-fs.ts:89
async stat(target) { - 读取本地文件src/workspace-fs.ts:91
const info = await stat(String(target.targetKey)) - 读取本地文件src/workspace-fs.ts:98
const info = await stat(String(target.targetKey)) - 读取本地文件src/workspace-fs.ts:106
const entries = await readdir(String(target.targetKey), { withFileTypes: true }) - 写入本地文件src/workspace-fs.ts:119
await mkdir(dirname(path), { recursive: true }) - 写入本地文件src/workspace-fs.ts:122
await writeFile(path, content, { flag: 'wx' }) - 写入本地文件src/workspace-fs.ts:132
await writeFile(temporary, content) - 写入本地文件src/workspace-fs.ts:133
await rename(temporary, path) - 读取本地文件src/workspace-fs.ts:140
return await realpath(path)
扫描于 2026/10/31(0 天前) · 范围 全包扫描(包内未声明入口) · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/schemastery@deepseek-ai/cordis@deepseek-ai/cordis-plugin-loader@deepseek-ai/dsh-app-boot@deepseek-ai/dsh-client-connection@deepseek-ai/dsh-client-locale@deepseek-ai/dsh-client-resources@deepseek-ai/dsh-client-store@deepseek-ai/dsh-client-ui-conversation@deepseek-ai/dsh-client-ui-layout@deepseek-ai/dsh-client-ui-plugin-manager@deepseek-ai/dsh-client-ui-primitives给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
DeepSeek Harness 的在线模型视频生成:三个模型工具、一份重启后仍保留的服务商任务台账、实时聊天卡片和 Videos 面板,以及根据公开价格源估算的费用。
README
由 DeepSeek 最新模型翻译生成dsh-video-forge
在 DeepSeek Harness(dsh)的工作区对话里用在线模型生成视频。说一句要什么镜头,成片就会落在工作区的 renders/<shot>/vNNN.mp4;每次生成都记在一份持久化台账里,所以超时或重启 不会让你付两次钱。
按设计没有本地推理路径:这个插件只是在线模型的客户端。
English: README.md
环境要求
- DSH 0.2.x(含 rc 版本)。包里声明了 engines.dsh
(^0.2.0-rc.1 || ^0.2.1-rc.2),宿主会拒绝安装声明区间与运行版本不符的插件。
它不会悄悄加载一个按宿主没有的 API 编译出来的插件。
- Node ^22.19.0 || >=24.0.0。
- 至少一家受支持供应商的账号与 API key(火山方舟 ARK Seedance、MiniMax,或任意 OpenAI-Videos 兼容网关)。没配置之前不会生成,也不会产生费用。
- 不需要 ffmpeg、模型权重或 GPU:渲染在供应商侧,本插件只搬字节。
安装
dsh plugin --profile web add dsh-video-forge
web 是你启动的 profile(dsh web);换成你实际在用的 desktop、tui 等即可。
装完重启该 profile。
展开完整文档(README 剩余 1 节)
dsh-video-forge
在 DeepSeek Harness(dsh)的工作区对话里用在线模型生成视频。说一句要什么镜头,成片就会落在工作区的 renders/<shot>/vNNN.mp4;每次生成都记在一份持久化台账里,所以超时或重启 不会让你付两次钱。
按设计没有本地推理路径:这个插件只是在线模型的客户端。
English: README.md
环境要求
- DSH 0.2.x(含 rc 版本)。包里声明了 engines.dsh
(^0.2.0-rc.1 || ^0.2.1-rc.2),宿主会拒绝安装声明区间与运行版本不符的插件。
它不会悄悄加载一个按宿主没有的 API 编译出来的插件。
- Node ^22.19.0 || >=24.0.0。
- 至少一家受支持供应商的账号与 API key(火山方舟 ARK Seedance、MiniMax,或任意 OpenAI-Videos 兼容网关)。没配置之前不会生成,也不会产生费用。
- 不需要 ffmpeg、模型权重或 GPU:渲染在供应商侧,本插件只搬字节。
安装
dsh plugin --profile web add dsh-video-forge
web 是你启动的 profile(dsh web);换成你实际在用的 desktop、tui 等即可。
装完重启该 profile。
配置供应商
常规做法是在 GUI 里:侧栏 → 插件 → Video Forge。它会以可选列表给出本版本支持的协议:火山方舟 ARK(Seedance)、MiniMax(H3)、OpenAI(Sora),或自定义的 OpenAI-Videos 兼容网关,并自动填好 Base URL、凭据引用与厂商文档里的模型,允许添加多家供应商。API key 经宿主的凭据服务保存,界面只显示掩码后的尾号。
同样的配置也可以手写进 profile 的 cordis.patch.yml:
- id: video-forge
name: dsh-video-forge
config:
providers:
- id: ark
protocol: ark-seedance
baseUrl: https://ark.cn-beijing.volces.com/api/v3
authRef: ARK_API_KEY
models:
- id: doubao-seedance-2-0-fast-260128
modes: [t2v, i2v, flf2v, s2v]
resolutions: ['480p', '720p']
aspectRatios: ['16:9', '9:16', '1:1']
defaultProvider: ark
defaultModel: doubao-seedance-2-0-fast-260128
模型按 providerId::modelId 引用。凭据先走宿主凭据服务,再走环境变量
(<PROVIDER>_API_KEY);回环网关可以不带 key。
在对话里怎么用
- "做一个黄昏灯塔的 5 秒镜头" → 调用 video_generate;聊天卡片跟着任务实时更新,出片后可以直接播放。
- "让 materials/cat.png 里的猫转头看镜头" → 用工作区图片做首帧(i2v)生成。
拖进对话的附件也行:模型把它看到的路径传进来,插件按字节认出这是图片。
- "接着上一个镜头" → from 取上一版 take 的尾帧(ARK)作为这一版的首帧,落在同一个镜头目录里。
- "Alice 走进来" → characters: ['alice'] 会带上 Alice 注册在案的描述与参考图,让她在每个镜头里长得一样。
- 超时或重启之后问"好了吗?" → video_status。插件在重启后接管的任务,完成时还会主动在对话里说一声。
工具
| 工具 | 作用 |
| video_generate | 提交一次生成(t2v / i2v / flf2v / s2v),立刻返回任务 id 与后台 job id。shot 决定 renders/ 下的目录名;from 接着上一版拍;characters 应用 video-project.yaml 里注册的角色。拒绝(只读对话、远端工作区、清单写坏、重复请求)都发生在花钱之前 |
| video_status | 恢复入口:最近的任务、进度、每部片在哪、失败原因、内容审核拒绝、供应商上报的用量。不给任务 id 时还会报告当前设置:有哪些供应商、key 是否配好、模型与模式,以及本工作区的清单、权限模式与角色。超时之后先查这里,不要重新提交 |
| video_cancel | 请供应商停下(与 job_kill 不同:后者只是停止盯守,供应商照旧干活照旧计费) |
费用,以及什么离开了你的机器
video_status 也回答钱的问题:cost: true 把已结算用量乘价目表求和(按供应商/模型/镜头分组,附价目表日期,以及还有哪些任务的用量未知);estimate 在提交前先给一次请求估价。每个数字都标注为估算,真正算数的是供应商的账单,而且厂商确实有过不打招呼就调价的历史。
价格来自公开数据源(LiteLLM 的价格库,按 7.0 CNY/USD 折算并按分辨率档拆分),而不是手写在源码里;手写的那份写下的当天就已经错了。这份数据在后台抓取,绝不发生在工具调用里;整体校验后缓存在 $DSH_HOME/dsh-video-forge/price-cache.json,取值顺序是 你自己的行 > 新抓到的 > 缓存 > 内置快照。每笔金额都带 price_source (remote / cache / bundled / none,以及时效与最近一次失败),价目表里查不到的模型报"未定价"而不报 0。raw GitHub 到不了时,把 priceSourceUrl 指向镜像(jsDelivr、npmmirror);留空则关闭该数据源。
会离开你机器的是提示词、你点名的参考图、video-project.yaml 里的角色描述,还有 from 用到的上一个镜头的尾帧。这些都会用你配置的 key 上传给你配置的供应商,受那家厂商的条款约束。没有任何东西会发给插件作者;本插件不带遥测。供应商的内容审核可能直接拒绝任务:拒绝会被记录下来,完全相同的请求不会被重提,改过的请求照常放行。
随包附带的 skill
插件带 5 个 skill(由模型用 skill 工具加载,其中 3 个也注册为 /名字 命令)。
它们负责引导工作流;工具本身是自洽的,从不依赖某个 skill 是否被加载过。
| Skill | 覆盖什么 |
| video-project(/video-project) | 工作区布局与 video-project.yaml:路径、默认值、风格、角色、版本号 |
| video-prompting | 怎么写/改提示词(主体、动作、镜头、风格),时长与画幅,供应商差异 |
| video-storyboard(/video-storyboard) | 把剧本拆成 storyboard/shots.yaml,一次一个镜头地生成 |
| video-iterate | 重拍还是续拍(from)、角色一致性,以及交付前的尾帧自检 |
| video-guide(/video-guide) | 端到端用法:配置、能做什么不能做什么、费用、成片落在哪 |
工作区就是项目
<workspace>/
video-project.yaml # 可选:默认值、风格、角色、目录映射 materials/ # 参考图(任意布局;路径按工作区相对写) renders/<shot>/v001.mp4 # 每一版 take,永不覆盖 renders/<shot>/v001.json # 这一版由什么产生:提示词、模型、素材、用量 renders/<shot>/v001.last.jpg # 它的尾帧(ARK),供 from 使用
一个工作区里第一条视频还会顺手写出带注释的 video-project.yaml,以及内容为 的 renders/.gitignore(想提交成片就删掉它,比如配合 LFS)。清单大致是这样:
version: 1
defaults: { provider: ark, model: doubao-seedance-2-0-fast-260128, resolution: 720p, aspect_ratio: "16:9" }
style: warm film grain, soft daylight
characters:
alice: { refs: [materials/alice/.png], description: "red scarf, short black hair" }
paths: { renders: renders }
清单解析不了,或某一节里键名拼错,都会在提交之前被拒。插件只写 renders/(以及首次产出的那两个附带文件);它尊重对话的权限模式:只读对话会被拒绝,workspace-write 下一个其实是指向工作区外部的 renders/ 也会被拒绝。万一工作区最终放不下这条已经付过钱的视频,它留在 $DSH_HOME 里,video_status 会说明原因。
协议
| 协议 | 覆盖 |
| ark-seedance | 火山方舟 Seedance(POST/GET/DELETE {base}/contents/generations/tasks),带免费的尾帧(return_last_frame) |
| minimax-video | MiniMax 视频生成 V2(POST /v2/video_generation、GET /v2/query/video_generation/{id});不能取消,没有尾帧 |
| generic-openai-videos | OpenAI-Videos 形状的网关(/v1/videos),每条路径与状态词都可配 |
界面入口
| 位置 | 是什么 |
| 输入框工具行 | Video model chip:这个对话用哪个模型生成,以及时长 / 分辨率 / 画幅。它的优先级高于 video-project.yaml 和插件设置;你(或模型)在调用里显式传的参数仍然最优先。"自动"把决定权还回去。按会话隔离:新对话从"自动"开始 |
| 聊天 | 每次 video_generate 一张卡片,跟着任务实时更新:状态与进度、出片后是真正的 播放器、文件在哪(或已被移走/删除)、供应商还可能停下时的取消按钮,以及两条出口:在文件查看器中打开该视频,或去面板看 全部视频 |
| 右侧栏 → 视频 | 本工作区里的每一部片子,新镜头在前:一版 take 一行,带状态、大小与时长,最新的一条默认展开并可播放。进行中与失败的排在最下面;renders/ 里台账不认识的片子标为"仅文件"。插件交付到工作区之外的片子根本不列出:这个面板是本项目的视频,那条片子落在哪由聊天卡片负责说明。面板只读:从不提交,也不删东西 |
| 右侧栏 → 文件查看器 | 打开 .mp4(从面板、文件树,或对话里的路径)会在与文本、图片同一个 tab 里播放,上方仍是宿主自己的工具栏(刷新、用你的播放器打开) |
| 引导页(右侧栏的"+") | 可以选到"视频"面板 |
| 右下角角标 | 在飞的任务数(没有时隐藏) |
| 侧栏 → 插件 → Video Forge | 供应商与模型以表单呈现;API key 经宿主凭据服务保存,界面只显示掩码后的尾号 |
没有单独的工作台页面,视频面板也不是:浏览器侧任何东西都无法提交一次付费生成或写文件。
浏览器侧只访问 /video-forge/api/{overview,cancel},除已配对的设备外仅限回环。
宿主不为插件路由做门禁,所以由本插件自己做。
东西都在哪
- 视频:工作区的 renders/(见上)。不在任何工作区对话里发起的调用,成片留在 $DSH_HOME/dsh-video-forge/outputs/。
- 台账:$DSH_HOME/dsh-video-forge/tasks.json。什么都不自动删,retain 只报告插件自己目录的压力。
- 媒体:GET|HEAD /video-forge/media/<taskId>/<token>/<name>。路径只从台账解析,绝不来自请求;一个 128-bit token 授权访问,且工作区里的片子只在它仍是当初写入的那个文件时才提供。
- 流式播放:GET|HEAD /api/video-forge/stream?path=…[&sessionId=…] 以 HTTP Range 提供 mp4,长片可以跳到中间而不必整段读完。它挂在宿主自己的已认证 /api 通道上(仅 owner、同源),读的是同一个用户本来就能通过 /api/file 打开的文件,加上 Range 并没有多给任何可达性。上面那条插件自己的信任门路由做不到这点:它只认识台账里记过的文件。
升级与卸载
dsh plugin --profile web add dsh-video-forge@latest # 升到最新版
dsh plugin --profile web remove dsh-video-forge # 卸载
两步之后都重启 profile。卸载不会删除 $DSH_HOME/dsh-video-forge/(台账、产物、价格缓存)或工作区的 renders/。那些是你的数据,要清自己清。
开发
pnpm install
pnpm check # tsc --noEmit、tsdown 构建、vitest
fixtures/provider.ts 是一个离线替身,跑在真 socket 上,模拟 ARK / MiniMax / OpenAI 兼容协议,所以整条"提交 → 轮询 → 下载 → 交付"链路不需要 key 也不需要网络就能跑完。
fixtures/litellm-video-sample.json 是上游价格数据的一份冻结切片,内置快照被断言等于这份冻结样本的归一化结果,两者因此不会漂移。
针对真实供应商的实测是分开的,永不进入 pnpm check。free 只发不可能产生计费任务的请求;paid 还会为每家供应商真跑一次最便宜的生成:
VIDEO_FORGE_LIVE=free ARK_API_KEY=… MINIMAX_API_KEY=… pnpm test:live
设计说明(中文,一个里程碑一节,坑也写在里面)在 docs/DESIGN.md;发版流程在 docs/RELEASING.md。
许可
MIT,见 LICENSE。
内置价格快照派生自 LiteLLM 的价格库(MIT)。
厂商名称与模型 id 仅作描述性使用;本项目与这些厂商没有隶属关系。预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
tkliuxing/dsh-video-forge
在对话里描述镜头,自动生成视频并记账防重复扣费
相关插件 · 模型接入
API 中继安全审计toby-bridges/api-relay-audit
面向 AI API 中转与 LLM 代理的本地安全审计。
★ 897已汉化DSH
69.4
免费搜索插件DDDMUC/dsh-free-search
DeepSeek Harness 免费搜索插件 —— 无需 API key,零成本,多引擎可切换。 一个给…
★ 761已汉化DSH
68.7
免费联网搜索liustack/modsearch
在官方 App 里模型能联网,切到 API 就不能了。ModSearch 把联网补回来:网页搜索、X…
★ 863已汉化DSH
68.5
OpenCode 免费模型FishBottle7/opencode2dsh
在 DSHDeepSeek Harness里原生使用 OpenCode Zen 的免费匿名模型。
★ 379已汉化DSH
67.8
AI 模型网关tingly-dev/tingly-box
Tingly Box Web UI 演示
★ 373已汉化DSH
67.4
WorkBuddy 账号池XDTrees/dsh-xdpool
把你能白嫖的模型额度接进 DSH 同一个池:WorkBuddy 多账号自动容错、十三家扫码登录、76…
★ 359已汉化DSH
67.1