🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

文件锻造器 Tianbuyu-wwx/DSH-FormatForge

DeepSeek Harnessv3.0.0
Tianbuyu-wwx28 天前有提交已提供中文文档扫描:含敏感能力

把任意文件拖进 DeepSeek Harness,立刻变成 AI 能读懂的数据。

38.3/100
综合分
10
GitHub 星标
457
npm 周下载
—/5
用户评分

数据截至 2026/10/7 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add @tianbuyu-wwx/dsh-formatforge
npm 源 · @tianbuyu-wwx/dsh-formatforge 的发布者账号已校验与本仓库一致

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · tool
装得上吗
本站已在 dsh 0.2.1-rc.5 下真实安装成功
非静态推断
L5 · 运行时冒烟等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 28 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过

安装清单取自 packages/dsh-formatforge/package.json

展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包@tianbuyu-wwx/dsh-formatforge @ 3.0.0
✓Node 引擎要求 >=22.13.0 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/11/2 09:41:35

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件写入剪贴板(不计风险)发起外部网络请求
查看扫描证据(共 64 处,展示前 47 条)
  • 读取本地文件
    index.mjs:33
    const VERSION = JSON.parse(readFileSync(join(pluginDir, 'package.json'), 'utf8')).version
  • 发起网络请求
    lib/client.js:64
    const res = await fetch(FF_UPLOAD, {
  • 发起网络请求
    lib/client.js:736
    const res = await fetch(FF_API + path, {
  • 写入剪贴板
    lib/client.js:1012
    void navigator.clipboard.writeText(p)
  • 读取本地文件
    tools/result.mjs:112
    const st = statSync(full)
  • 读取本地文件
    tools/result.mjs:117
    doc = JSON.parse(readFileSync(full, { encoding: 'utf8' }))
  • 读取本地文件
    tools/result.mjs:156
    names = readdirSync(dir)
  • 读取本地文件
    tools/result.mjs:317
    fileCount = readdirSync(inboxDir()).filter((n) => n.endsWith('.ff.json')).length
  • 读取本地文件
    tools/result.mjs:379
    if (row && row.json_path && existsSync(row.json_path)) target = basename(row.json_path)
  • 读取本地文件
    tools/result.mjs:384
    const names = readdirSync(dir)
  • 执行外部命令来自配置/变量
    services/python-runner.mjs:42
    child = spawn(python, ['--version'], { windowsHide: true })
  • 读取本地文件
    services/python-runner.mjs:89
    existsSync(join(dir, 'formatforge')) &&
  • 读取本地文件
    services/python-runner.mjs:90
    existsSync(join(dir, 'core')) &&
  • 读取本地文件
    services/python-runner.mjs:91
    existsSync(join(dir, 'pyproject.toml'))
  • 执行外部命令固定命令
    services/python-runner.mjs:103
    spawn('taskkill', ['/PID', String(child.pid), '/T', '/F'], { windowsHide: true })
  • 执行外部命令来自配置/变量
    services/python-runner.mjs:134
    child = spawn(python, args, {
  • 读取本地文件
    services/python-runner.mjs:205
    st = statSync(pathStr)
  • 读取本地文件
    services/inbox-watcher.mjs:85
    if (!existsSync(d)) {
  • 读取本地文件
    services/inbox-watcher.mjs:103
    return readdirSync(inbox, { withFileTypes: true })
  • 读取本地文件
    services/inbox-watcher.mjs:122
    st = statSync(full)
  • 读取本地文件
    services/inbox-watcher.mjs:135
    if (existsSync(jsonPath) && statSync(jsonPath).mtimeMs >= st.mtimeMs) {
  • 读取本地文件
    services/inbox-watcher.mjs:135
    if (existsSync(jsonPath) && statSync(jsonPath).mtimeMs >= st.mtimeMs) {
  • 写入本地文件
    services/inbox-watcher.mjs:218
    writeFileSync(errPath, `[too_large] ${msg}`)
  • 删除本地文件
    services/inbox-watcher.mjs:223
    try { unlinkSync(errPath) } catch { }
  • 写入本地文件
    services/inbox-watcher.mjs:234
    writeFileSync(jsonPath, JSON.stringify(res, null, 2))
  • 写入本地文件
    services/inbox-watcher.mjs:236
    writeFileSync(mdPath, content)
  • 读取本地文件
    services/inbox-watcher.mjs:240
    doneAt.set(name, statSync(full).mtimeMs)
  • 写入本地文件
    services/inbox-watcher.mjs:256
    writeFileSync(errPath, `[${kind}] ${message}`)
  • 删除本地文件
    services/inbox-watcher.mjs:342
    unlinkSync(full)
  • 写入本地文件
    services/inbox-watcher.mjs:352
    appendFileSync(retiredLogPath, lines, 'utf-8')
  • 写入本地文件
    http/upload.mjs:84
    mkdirSync(dir, { recursive: true })
  • 读取本地文件
    http/upload.mjs:91
    while (existsSync(join(dir, finalName))) {
  • 读取本地文件
    http/api.mjs:27
    const existing = readFileSync(file, 'utf8').trim()
  • 写入本地文件
    http/api.mjs:34
    mkdirSync(ffHomeDir(), { recursive: true })
  • 写入本地文件
    http/api.mjs:35
    writeFileSync(file, token + '\n', { encoding: 'utf8', mode: 0o600 })
  • 读取本地文件
    http/api.mjs:182
    if (existsSync(mdPath)) text = readFileSync(mdPath, 'utf8')
  • 读取本地文件
    http/api.mjs:182
    if (existsSync(mdPath)) text = readFileSync(mdPath, 'utf8')
  • 读取本地文件
    http/api.mjs:184
    const doc = JSON.parse(readFileSync(jsonPath, 'utf8'))
  • 读取本地文件
    http/api.mjs:224
    if (row && row.json_path && existsSync(row.json_path)) {
  • 读取本地文件
    http/api.mjs:230
    names = readdirSync(dir)
  • 删除本地文件
    http/api.mjs:460
    if (existsSync(p)) unlinkSync(p)
  • 读取本地文件
    services/ff-paths.mjs:53
    for (const cand of readdirSync(dir).sort()) {
  • 读取本地文件
    services/inbox-db.mjs:42
    if (!existsSync(dbPath())) return { available: false, reason: 'index.db not found', db_path: dbPath() }
  • 读取本地文件
    services/inbox-db.mjs:77
    if (!mod || !existsSync(dbPath())) return null
  • 发起网络请求
    lib/client.source.js:58
    const res = await fetch(FF_UPLOAD, {
  • 发起网络请求
    lib/panel.source.js:123
    const res = await fetch(FF_API + path, {
  • 写入剪贴板
    lib/panel.source.js:399
    void navigator.clipboard.writeText(p)
扫描于 2026/11/3(1 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

界面预览

3 张 · 点击查看大图

功能亮点

  • 拖入文件转结构化数据
  • 支持30多种文件格式
  • AI可读数据转换
v3.0.02026-10-02内容较长已截断
## v3.0.0 — 收件箱升级为「库」+ 宿主右侧栏面板 破坏性:最低 DSH 0.2.0-rc.1;新增 SQLite 索引库与自定义路由。协议仍为 v1(只增不改)。 面板首屏 401 / 单条 404 / 大产物 404 等问题已在本版全部修掉,并逐条补了回归测试。 鐢ㄦ埛鎷嶆澘锛?*鍙仛闈㈡澘锛堜笉鍋氭暣绐楀彛椤?鑷墭绠¢〉锛壜?鎸佷箙鍖栧垎灞傜敱瀹炵幇鏂瑰畾 路 浠庨浂鍐?*锛堜笉澶嶆椿鍐荤粨鍓嶇锛壜?涓€娆℃€уぇ鐗堟湰 路 鍏堣嚜妫€銆佺瓑瀹℃煡鍐嶅彂銆? 璁″垝涓庡彇鑸嶄緷鎹細UI_DB_PLAN.md · 鏂板 **鈶?鏀朵欢绠辩储寮曞簱锛圫QLite锛宍/formatforge/index.db锛?* 鈥斺€?ROADMAP R6.2 鐨勮惤鍦帮細 | 鑳藉姏 | 瀹炵幇 | |---|---| | 鍏冩暟鎹储寮?| artifacts 琛?+ schema 杩佺Щ锛坄schema_migrations锛屽箓绛夛紱澶辫触鑷姩閲嶅缓锛?| | 鍏ㄦ枃妫€绱?| FTS5 trigram锛堜腑鏂囧瓙涓插彲鐢級+ 鐭瘝 LIKE 鍏滃簳锛坱rigram 鍙储寮?3 瀛楀簭鍒楋紝銆屼粯娆俱€嶈繖绫?2 瀛楁煡璇㈠繀椤昏蛋 LIKE锛?| | 鍐呭鍘婚噸 | source_sha256锛堚墹8MB 鍏ㄦ枃鍝堝笇 / 鏇村ぇ鍙栭 1MB+灏哄鏍囪锛夛紝鎷栧叆鍛戒腑鍗崇鍥?| | 浼氳瘽婧簮 | session_id 鍒楋紙棰勭暀锛? events 浜嬩欢琛?| | 鍥炲~ | python -m formatforge inbox reindex锛氭壂 .ff.json 骞傜瓑鍥炲~锛?*鍙殢鏃跺垹搴撻噸寤?* | | 淇濈暀瀵归綈 | inbox prune锛氱鐩樹笂宸叉秷澶辩殑浜х墿鏍?retired_at锛堜笌 watcher 鐨?TTL/LRU 瀵归綈锛?| | 杩愮淮 | inbox stats / vacuum / backup(VACUUM INTO) / find / delete(杞垹) | 鏂?CLI锛歚python -m formatforge inbox {init,index,query,stats,reindex,delete,prune,digest,find,vacuum,backup,prefs} 锛堟部鐢ㄣ€宻tdout 鍞竴鍑哄彛銆嶅崟琛屽崗璁?JSON锛夈€? **鈶?瀹夸富鍙充晶鏍忛潰鏉?+ 渚ф爮瀵艰埅鍏ュ彛锛堜粠闆舵墜鍐欙紝闆舵瀯寤恒€侀浂 npm 渚濊禆锛?*锛? · 渚ф爮瀵艰埅鏉$洰锛堢敤鎴锋寚瀹氫綅缃級锛氭敞鍐?sidebar.panellist锛堝浘鏍?+ label锛宍order=20锛夆啋 钀藉湪 銆屾彃浠?/ 鑷姩鍖栦换鍔°€嶄笅鏂圭殑瀵艰埅鍒楄〃閲岋紱鐐瑰嚮鐢卞涓?selectPanel(id) 鍒囧埌 main 鎻掓Ы鍚?key 鐨?*涓诲尯椤甸潰 锛堜笌瀹樻柟 schedule 鎻掍欢鍚屼竴濂楀叕寮€鍐欐硶锛夈€? · 鍙充晶鏍忛〉绛?*锛堜繚鐣欙級锛歚sidebar.right.pane.tab + .title锛坘eyed 鎻掓Ы锛? 绫诲瀷娉ㄥ唽 ctx.sidebarRightTabs.register({kind:'formatforge-inbox', priority:'extension', guide:[鈥}) + 渚ф爮搴曢儴鎸夐挳銆? · 鍚屼竴涓潰鏉跨粍浠朵袱绉嶈惤鐐癸細涓诲尯椤甸潰锛坄variant=page锛屽灞忓眳涓級涓庡彸鏍忛〉绛撅紙variant=pane锛岀獎鏍忚嚜閫傚簲锛夈€? · 鍔熻兘锛氭悳绱紙涓枃瀛愪覆锛夈€佷骇鐗╁垪琛紙鏉ユ簮/瑙f瀽鍣?浣撶Н/鏃堕棿/澶辫触鏍囪锛夈€佽鎯呴瑙堬紙姝f枃 + 鍏冩暟鎹級銆? 澶嶅埗璺緞 / 閲嶆柊閿婚€狅紙鍒犱骇鐗?+ 閫氱煡 watcher 閲嶈窇锛? 浠庡垪琛ㄧЩ闄わ紙杞垹锛夈€?*姣忛〉鏉℃暟鍋忓ソ銆? · 鎶€鏈細瀹夸富 Module Loader lane 鐨?require('react') + React.createElement锛涘浘鏍囨槸鍐呰仈 SVG锛坄currentColor锛岃窡闅忎富棰橈級銆? · 闄嶇骇**锛氭嬁涓嶅埌 react / 娌℃湁 sidebarRightTabs / 鎻掓Ы鏈0鏄庯紙register 鎶涢敊锛? slots 娉ㄥ叆澶辫触 鈫?鍙鏃ュ織锛?*缁濅笉褰卞搷鎷栨嫿妯″潡涓庡伐鍏?*锛堟湁涓撻棬鐨勯檷绾ф祴璇曪級銆? · **鎺ュ彛鏈氨缁彁绀?*锛氬涓昏嫢杩樿窇鐫€鏃х殑 Node 鍗婏紙API 404锛夛紝
v2.0.22026-10-02
· 🖱️ 拖拽文件夹不再卡死 现场:把文件夹拖进窗口也会激活插件,然后界面「卡死」——宿主的 DropOverlay 用它自己的 dragDepth 计数显示、且只在它自己的 drop/dragleave/dragend 里归零;被插件吞掉的终止事件让计数停在 >0,全屏遮罩一直挂到刷新。 · 修复 · 放行文件夹:一次拖拽里出现目录即整体交回宿主(webkitGetAsEntry() 逐条判定 + 「无 MIME + 0/4096 字节」特征兜底,报告.pdf 这种带扩展名的目录名也算目录),不接管、不显示遮罩、不上传。宿主自己的目录 intake(拖入文件夹 → @路径 引用)原样可用。 · × / Esc 逃生:遮罩右上角新增 ×;拖动期间右上角另常驻一颗 × 退出拖拽(含放行的文件夹拖拽),Esc 等效。点一下即清掉遮罩并补发宿主复位,不必刷新页面。逃逸只管当前这一段拖拽(其 drop 交回宿主),不影响下一次拖拽。 · 宿主计数复位:凡是我们吞掉的终止事件(drop、×、Esc、10s 看门狗、HMR 卸载)都无条件补发宿主自己的复位路径(合成 window.dragend + 指向 body 的视口边缘 dragleave);逃生按钮 8 秒后退场时再补一次。 · 复核挖出的 5 个衍生坑:未定性即松手的静默接管(仍会留卡死遮罩)、Esc/× 后仍被接管上传、带扩展名目录被当普通文件、单个条目 entry 无应答就关掉全部兜底、0 字节无类型文件「既不锻造也不交回」被静默吞掉。 · 自检追加:CLI 协议 stdout 固定 UTF-8——中文 Windows 控制台下含 ¥ 的文件会让 python -m formatforge 返回 internal 错误(宿主插件侧本来就设了 PYTHONUTF8,只有直接跑 CLI 会踩);Python 内核版本与 npm 侧对齐 2.0.2。 · 测试 · 新增 test-client-drag.mjs:按宿主方式(dragDepth 计数、dragover 的 preventDefault、droppedDirectories())重放 16 组拖拽序列 / 117 条断言;spec(MDN:entry API 只在 dragstart/drop 可读)与 chrome 两种方言都跑;对 8 个人为回归变体 8/8 全部报错(断言不是摆设)。 · 全盘自检:pytest 564 passed / 0 failed(修 UTF-8 前本机 2 failed)、ruff check + ruff format --check 通过、bandit 10 medium / 0 high(既有 pickle 缓存与 xml.etree,非本次改动文件)、node --check 全过、bundle 契约 + test-local / test-inbox / 分页一致性 e2e 全过、npm pack 内容核对。 · 实机验证:正在运行的桌面版宿主已按新的 mtime/size rev 提供修好的 client bundle(rev=f917e77716ad,200)。 升级:dsh plugin --profile desktop add @tianbuyu-wwx/dsh-formatforge;已装用户重启宿主、或在页面上等 client 模块热重载(必要时刷新一次)。 协议仍是 v1(protocol/v1/ 未变);完整明细见 CHANGELOG 2.0.2 节。
v2.0.12026-09-30
· 修掉「通知污染会话 + 跨会话失效」 用户现场反馈: 这个插件的拖入文件的上下文只生效于当前的对话,别的对话不生效,也不用再显示一遍 · 根因 宿主没有临时通知通道——KNOWN_SESSION_EVENT_TYPES 里所有模型可见事件都会持久化。 所以 services/notify.mjs 原来 append 的那条 user/message 必然带来三个后果: 1. 永久写进 transcript —— 之后每一轮都重新发给模型、UI 里反复出现,并且成为永久的上下文成本; 2. 被署名为用户 —— role: 'user' + source.kind: 'user',对话里出现了用户从没说过的话; 3. 只送达当时持有 live agent 的会话 —— ctx.agents.list() 只包含正在运行的会话, 用户在别的对话里工作时拖的文件永远不会有任何提示。 · 改动 | 改动 | 文件 | |---|---| | 推送默认关闭(FF_INBOX_NOTIFY=true 才开) | services/notify.mjs | | 按 resultId 去重,同一产物每会话最多播报一次 | services/notify.mjs + index.mjs | | 通知文案 6 行 → 1 行 | services/notify.mjs | | 拉取式取代推送:ff_result {list:true} 任何会话任何时候都能读 | skills/dsh-formatforge/SKILL.md | 为什么拉取式是对的:收件箱本来就是共享目录,任意对话都能用 ff_result 取到产物。 缺的是「知道去看」,而不是「被推一条消息」——后者的代价是永久上下文污染加错误署名。 · 新测试 test-inbox.mjs 增加 3 项断言:通知必须是单行、同一 resultId 重复播报被抑制、默认关闭且不写 transcript。 --- · 同时交付:全面自检报告 + 后续开发计划 · SELF_CHECK_v2.0.0.md — 实机自检(不是读代码猜) 用 30 个真实文件(28 种格式) 驱动正在运行的插件全量转换,并用对照实验定位根因。 门禁层全绿:pytest 564 · ruff · Node 自检 5/5 · 契约 17/17 · CI 7/7。 但产品体验层有系统性缺口,以静默错误为主: | 级别 | 发现 | |---|---| | 🔴 P0 | ff_batch 产物名冲突静默覆盖——29 个输入只出 22 个文件,报告仍写「29/29 成功」;收件箱同病 | | 🔴 P0 | meta.parser 名不副实——book.epub 显示 parser=txt,实际跑的是 EPUBParser;字段报的是 8 值粗枚举 FileType | | 🔴 P0 | 收件箱通知污染会话(本篇) | | 🟠 P1 | 图片转换 5–8 s,而 ImageParser.parse() 本身只要 4.3 ms;关掉 OCR 仍有 5.3 s | | 🟠 P1 | 可选依赖缺失时静默降级成垃圾却报 confidence=1.0(RTF/LaTeX 原样输出) | | 🟠 P1 | 临时文件名泄漏进结果(tmp2su4zugj.zip / tmpukd170ad.png) | | 🟡 P2 | 无历史/检索;ff_result list 实测 4.5 / 34 / 242 ms @100/1000/5000(I/O 不是瓶颈,问题是一次把 5000 行塞进上下文) | | 🟡 P2 | CSV BOM 未剥离;不支持的拖入静默忽略(与设计文档冲突) | · ROADMAP.md — 重写为 R6「用户体验优先」 R1–R5 已全部完成,归档到附录。R6 排序原则:先消灭静默错误(信任)→ 再补历史与检索(好用)→ 再谈速度(爽)。 关于「搞个数据库存放已上传文件」,结论是方向对但要连对地方: · 推荐 SQLite,由 Python 内核写(sqlite3 是标准库),Node 侧继续走 CLI 查询—— 两侧都零新增依赖;.ff.md/.ff.json 留在磁盘上保持人类可读,DB 只存元数据 + FTS5 索引。 · 引入它的理由不是快(实测 5000 条约 242 ms,可接受),而是现在根本没有的 全文检索、内容去重、来源溯源、结构化筛选,以及 limit 分页。 · 被否决:JSON 索引文件(无 FTS/无并发保护,可作过渡)、Node 侧 better-sqlite3(破坏零依赖)、 宿主 dsh-storage(会话级生命周期,且产物
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。

README

由 DeepSeek 最新模型翻译生成
DSH-FormatForge 把任意文件拖进 DeepSeek Harness,立刻变成 AI 能读懂的数据。 v1.0 production-ready(2026-09):协议冻结(packages/dsh-formatforge/protocol/v1/),向后兼容承诺启用。 DSH-FormatForge 是一个 DeepSeek Harness 插件:把 PDF、DOCX、PPTX、XLSX、EML、EPUB、TOML 等 30+ 种格式锻造成 AI 模型可直接消化的结构化文本——直接拖进 dsh 网页,或在对话里粘贴文件路径。 项目前身是「Data-Format-Translator / AI 数据转换器」(Web 服务形态,冻结于 v2.1.0-ci-green)。 v3.0 起完全重构为 dsh 插件:无 Web 界面、无内置 AI 客户端,专注「格式 → 结构化数据」这一层。 它解决什么问题 dsh 的 read 工具读 PDF/DOCX 这类二进制是乱码;官方附件通道只认图片。 装上 FormatForge 后: 拖 report.pdf 进 dsh 网页 → 右下角提示「正在锻造…
展开完整文档(README 剩余 1 节)
DSH-FormatForge

把任意文件拖进 DeepSeek Harness,立刻变成 AI 能读懂的数据。

v1.0 production-ready(2026-09):协议冻结(packages/dsh-formatforge/protocol/v1/),向后兼容承诺启用。

DSH-FormatForge 是一个 DeepSeek Harness 插件:把 PDF、DOCX、PPTX、XLSX、EML、EPUB、TOML 等 30+ 种格式锻造成 AI 模型可直接消化的结构化文本——直接拖进 dsh 网页,或在对话里粘贴文件路径。

项目前身是「Data-Format-Translator / AI 数据转换器」(Web 服务形态,冻结于 v2.1.0-ci-green)。
v3.0 起完全重构为 dsh 插件:无 Web 界面、无内置 AI 客户端,专注「格式 → 结构化数据」这一层。

它解决什么问题

dsh 的 read 工具读 PDF/DOCX 这类二进制是乱码;官方附件通道只认图片。
装上 FormatForge 后:

拖 report.pdf 进 dsh 网页 → 右下角提示「正在锻造…」
→ 数秒后对话收到通知:report.pdf 已锻好 (parser=pdf, confidence=0.95) → 直接基于这份文件继续提问

也可以不走拖拽:在对话里粘贴本地路径(E:\docs\report.docx),agent 会按约定先调用 ff_translate 再回答。

拖拽锻造 toast FormatForge 收件箱产物会话锻造完成通知

安装

适配目标:DeepSeek Harness 0.2.0-rc.2(Electron 桌面版)。
0.2.0 起 profile 由应用托管:桌面端用 desktop profile,普通 web/tui 用各自 profile。
兼容性与安装拒绝规则见 ADAPTATION_PLAN.md。

前置

| 依赖 | 说明 |
| Python ≥ 3.10 | 运行转换内核;解释器探测顺序 FF_PYTHON → <repo>/.venv-fg → PATH |
| Node ≥ 22 | dsh 本身的要求 |

方式一:从源码安装(本地开发,推荐用于本仓库)

git clone https://github.com/Tianbuyu-wwx/DSH-FormatForge.git DSH-FormatForge
cd DSH-FormatForge
pip install -e . # 或 python -m venv .venv-fg && .venv-fg/Scripts/pip install -e .

装进桌面端 profile(0.2.0:add ≠ 激活,重启桌面端才生效)
dsh plugin --profile desktop add "$(pwd)/packages/dsh-formatforge"

检查
dsh plugin --profile desktop ls # 依赖里应出现 @tianbuyu-wwx/dsh-formatforge
python scripts/verify-install.py --skip-inbox # 重启之后跑,应 ALL GREEN

add 成功后宿主会把本包自动追加进 profile package.json 的 dsh.profile.bundles(reconcileProfilePlugins),无需手工编辑。

不需要再跑 junction 修复脚本:0.2.0 的 launcher 为 linked profile 包内建了
peer-aware 解析(dsh-app-boot::routeLinked()),@deepseek-ai/dsh-tools 等 peer
直接命中宿主运行时那一份。scripts/rebuild-plugin-junctions.py 只对 0.1.x 宿主有意义。

方式二:从 npm 安装

npx @deepseek-ai/dsh plugin --profile desktop add @tianbuyu-wwx/dsh-formatforge
重启 DeepSeek Harness 生效

npm 包只是插件壳,仍需一份可运行的 Python 内核(本仓库)并告知位置:

给法 A:clone 本仓库并安装内核(推荐,含全部解析器依赖)
git clone https://github.com/Tianbuyu-wwx/DSH-FormatForge.git DSH-FormatForgebash
cd DSH-FormatForge && pip install -e .
setx FF_REPO_ROOT "D:\DSH-FormatForge"

给法 B:已有环境?只要它 import 得到 core/parsers:

setx FF_PYTHON "C:\path\to\your\python.exe"

生效验证

重启后在启动日志里找这一行:

[dsh-formatforge v2.0.0] tools registered: ff_translate, ff_formats, ff_result, ff_batch, ff_diff

再跑 python scripts/verify-install.py(桌面端需 --token,见脚本 --help)。

使用

1. 网页拖拽(零学习成本)

把任何支持的文件拖进 dsh 网页任意位置:

- 非图片文件 → 自动上传并锻造,右下角 toast 显示进度与结果
- 图片 → 走 dsh 原生图片附件通道(不受影响)
- 转换完成后活跃会话收到轻量通知,附产物路径

产物落在收件箱目录(默认 ~/.dsh/formatforge/inbox/): <名字>.ff.md(可直接阅读)+ <名字>.ff.json(完整协议数据)。

1.5 面板(v3.0.0)

入口在左侧栏导航里(「插件 / 自动化任务」下面的 FormatForge,点击后主区域打开库页面);同时也注册了宿主右侧栏页签(同一套组件,窄栏自适应)与侧栏底部按钮,随手可用。

- 搜索产物(中文子串可用:FTS5 trigram,短词自动走 LIKE 兜底)
- 列表显示来源 / 解析器 / 体积 / 时间 / 失败标记,点开看正文预览与元数据
- 行内动作:复制路径 · 重新锻造(删产物并让 watcher 重跑)· 从列表移除(软删,磁盘文件保留)
- 每页条数偏好持久化(与索引库同库:settings 表)

面板是增强项:宿主契约变化(插槽未声明、拿不到 react、没有 sidebarRightTabs)时只记日志,拖拽与工具完全不受影响。数据来自 /formatforge/api/(同源请求;非浏览器访问需 ~/.dsh/formatforge/api-token)。

1.6 收件箱索引库(v3.0.0)

.ff.md / .ff.json 仍是真相源;index.db(SQLite,与收件箱同级)只存元数据 + 全文索引,删库可重建: bash
python -m formatforge inbox stats # 库状态(条数/体积/格式分布/分词器)
python -m formatforge inbox query --q 付款条款 --limit 20
python -m formatforge inbox reindex # 从 .ff.json 幂等回填(首次升级用)
python -m formatforge inbox backup --out ~/ff-backup.db

工具侧新增 ff_result {search:"付款条款"}、ff_result {stats:true}、ff_result {list:true, limit:20}。

2. 对话内工具

| 工具 | 用途 |
| ff_translate | 转换:path(单个)/ paths(多个,支持 glob)/ text 三选一;format json·markdown·html·text;max_chars/offset 分页;quality 附质量报告 |
| ff_formats | 列出支持的输入格式矩阵 |

3. 命令行(脱离 dsh 也能用) bash
pip install -e . # 之后
python -m formatforge translate document.pdf --format markdown
python -m formatforge translate --stdin-text < notes.txt
python -m formatforge formats # 支持格式矩阵

stdout 输出单行协议 JSON({ok, code, data:{content, meta, quality?, enhance?}}),日志走 stderr,退出码 0/2/3/4。

enhance 协议 —— 模型增强交给会话本身

插件不带任何 AI 客户端。当纯规则转换不足以产出高质量结果时,返回里会出现: jsonc
"enhance": {
"needed": true,
"reason": "image_only", // image_only | low_confidence | table_sparse
"hint": "4/4 页无文字层(疑似扫描件)..." }

SKILL.md 会指导当前会话模型:按 hint 直接用自己的能力完成增强(如基于 OCR 文本重建表格),不调用任何外部 API。模型永远与 dsh 会话一致,零密钥配置。

配置

| 变量 | 默认 | 说明 |
| FF_REPO_ROOT | 自动探测 | 含 formatforge/ core/ parsers/ 的仓库根 |
| FF_PYTHON | 探测链兜底 PATH | 指定解释器(≥3.10) |
| FF_MAX_BYTES | 104857600 | 单文件上限(100MB) |
| FF_TIMEOUT_S | 120 | 单次转换超时(秒) |
| FF_INBOX_NOTIFY | false | 是否把「已锻好」推进会话 transcript。v2.0.1 起默认关:宿主没有临时通知通道,推送只能落成 user/message,会被当成用户发言、永久留在上下文里、且只有运行中的会话收得到。收件箱是共享目录,改用 ff_result {list:true} 拉取 |
| FF_HOME | $DSH_HOME/formatforge → ~/.dsh/formatforge | 收件箱根目录(优先于 DSH_HOME) |
| FF_DB | 不设置(=开) | v3.0.0:off 时整体停用索引库,回到纯文件路径(list/search 自动退回文件扫描) |
| FF_DB_PATH | $FF_HOME/index.db | v3.0.0:索引库文件位置(设置/偏好同库) |
| OCR_ENABLED | true | 启用本地 OCR(tesseract/paddleocr/easyocr 任一) |

架构

浏览器拖拽 ──POST /formatforge/upload──┐ ▼ ~/.dsh/formatforge/inbox/ │ fs watcher(去重·稳定检测)对话路径 ── ff_translate ──┐ ▼ CLI ── python -m … ──► formatforge 内核(30+ 解析器 × 7 策略) │ ▼ │ 协议 JSON {content, meta, quality?, enhance?} ▼ 会话轻量通知(仅元数据,不注全文)

- Python 内核(core/ + parsers/ + formatforge/):格式检测、管线编排、策略选择、质量评分
- Node bundle(packages/dsh-formatforge/):工具注册(cordis)、client 拖拽模块、inbox watcher、HTTP 上传路由

开发 bash
pip install -e ".[dev]"
pytest test/ # 本地 575 passed(CI 为权威门禁)
ruff check . && ruff format --check .
mypy core/ parsers/ formatforge/
node packages/dsh-formatforge/test-manifest.mjs # bundle 清单/契约自检
node packages/dsh-formatforge/test-client-bundle.mjs # client bundle 按宿主方式执行自检
node packages/dsh-formatforge/test-client-drag.mjs # 拖拽行为回放(文件夹放行 / × 逃生 / 宿主计数复位)
node packages/dsh-formatforge/test-client-panel.mjs # 右侧栏面板:注册/渲染/降级路径/400 可诊断性(v3.0.0)
node packages/dsh-formatforge/test-plugin-boot.mjs # 无宿主启动冒烟 + 8 条路由两参调用(v3.0.0)
node packages/dsh-formatforge/test-api.mjs # /formatforge/api/:路由 + 宿主 prefix 匹配仿真 + 鉴权 + 读写 + SSE(v3.0.0)
node packages/dsh-formatforge/test-host-http.mjs # 真实 node:http + 宿主语义复刻(两参调用 / prefix 匹配 / 注入突变自检)
node packages/dsh-formatforge/scripts/verify-live.mjs # 对运行中的宿主做端到端校验(改完 Node 侧代码、重启后先跑这个)
node packages/dsh-formatforge/test-result-contract.mjs # ff_result 字段口径 + search/stats/limit(v3.0.0)
node packages/dsh-formatforge/test-local.mjs # stub 环境 e2e(需本机 Python)
node packages/dsh-formatforge/test-inbox.mjs # inbox watcher e2e
node packages/dsh-formatforge/test/test-truncate-consistency.mjs # JS↔Python 分页一致性

设计文档:PLUGIN_PLAN.md(插件化实施)· EVOLUTION_PLAN.md(v0.4–v0.7 演进)· ROADMAP.md(后续计划)· UI_DB_PLAN.md(v3.0.0 的库 + 面板,含取舍与实测证据)

已知限制

- 扫描件 PDF 无文字层时依赖本机 OCR 引擎;都没有则返回 enhance=image_only 提示由会话模型兜底
- 拖拽模块挂在宿主未导出的内部实现上(dsh-client-ui-attachment 的 document 冒泡期 drop 监听):插件用捕获期先手 stopPropagation 完成分流。宿主若改到捕获期或 window 级,分流会静默失效(插件不会报错,只是拖拽不再被接管)——已用特征检测思路保守实现,但无公开契约。
宿主的 DropOverlay 同样只用它自己的 dragDepth 计数复位,所以插件在吞掉终止事件时会补发一次宿主复位(合成 dragend + 视口边缘 dragleave),并常驻 ×/Esc 逃生入口。
- 文件夹不参与锻造(v2.0.2 起整条拖拽放行给宿主):一个拖拽里只要出现目录,同批文件也不会被接管——要么单独拖文件,要么先打包成 zip。目录判定以 webkitGetAsEntry() 为准(与宿主同一判据),该 API 在 dragenter 阶段可能无应答(MDN:只在 dragstart/drop 可读),此时按「无 MIME + 0/4096 字节」特征保守放行:目录名带扩展名(报告.pdf)也算目录;代价是一个 0 字节且无 MIME 的真实文件在拖动中会被当成疑似目录,松手时再以 entry API 复核并照常锻造。
- ×/Esc 逃生入口只管当前这一段拖拽(清遮罩 + 补发宿主复位 + 把剩余事件交回宿主),
- 不影响下一次拖拽;拖拽结束后残留的 × 再点一次也只是清场。
- 混合粘贴(剪贴板同时含文件与文本)只取文件,文本不会进入输入框——与宿主自身分支的差异是有意为之。
- 面板插槽是对等插件私有契约(v3.0.0):sidebar.right.pane.tab 等插槽名由声明它的插件拥有,注册到未声明的插槽会在加载期抛错。面板已做探测 + try/catch + 降级(失败只记日志),但宿主大版本升级后可能需要对插槽名做适配。
- 面板与 API 的路由不在宿主鉴权围栏内(围栏只保护 GET / 与 /api):/formatforge/api/ 自建 loopback Host 校验 + 同源判定,非浏览器访问需 ~/.dsh/formatforge/api-token;请勿把宿主绑定到 0.0.0.0。
- 索引库需要 node:sqlite(Node ≥22.5;实测宿主 Node 24.18.1 免 flag 可用):拿不到时 API/面板自动降级为 "Node 走 CLI 查询",功能仍可用但每次查询多一次 Python 冷启动(约 0.5s)。
- 检索分词:FTS5 trigram 只索引 3 字序列,1–2 字的查询(中文里很常见)走 LIKE 兜底,库很大时会退化为扫描(几千条无感;上万条建议用 ≥3 字的检索词)。
- 上传地址用根绝对路径 /formatforge/upload,与宿主注册的 exact 路由一致;若将来宿主支持子路径托管,客户端与服务端需要同时改。
- Windows 下以中文路径 link: 安装时,profile 里的 package.json/pnpm-lock.yaml 均为正确 UTF-8,但用 PowerShell Get-Content 查看可能显示为乱码(控制台编码问题,非文件问题)。

License

MIT
预览分享卡片长什么样 ↓Tianbuyu-wwx/DSH-FormatForge
分享 · DeepSeek Harness Hub
Tianbuyu-wwx/DSH-FormatForge

把任意文件拖进 DeepSeek Harness,立刻变成 AI 能读懂的数据。

这是你的仓库?认领条目

回到顶部