dsh-git-sync
Verified@fish-under-sea/dsh-git-sync · v0.2.5 · MIT · Web UI
DeepSeek Harness 一键 GitHub 同步:把插件清单、插件启用状态、插件配置、本地设置、Skills、看板/用量账本与设置导航顺序偏好采集进你自己的私有 git 仓库并推送,换机可一键还原;采集前自动 fetch 远端并合并,密钥永不搬运。0.2.5 起不再同步 agent 预设 / 桌宠存档 / 工作区映射。仓库目录在「设置 → Git 同步」里改。
Install
dsh plugin add @fish-under-sea/dsh-git-sync Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Published to npm without a public repository. Inspect the package contents before installing.
Tags
Readme
新机器请不必动 API 设置:
DEEPSEEK_API_KEY、BAILIAN_API_KEY、BAILIAN_HE_API_KEY。
解决什么问题
DSH 把所有用户状态收敛在单个 home 目录($DSH_HOME,默认 ~/.dsh)。手动同步要记住一串路径、还要小心别把密钥推上去。本插件把这件事变成「设置 → Git 同步」里的几个按钮——把你的 DSH 环境(已安装的插件、每个插件是否启用、插件配置、本地设置、Skill、看板账本)采集进你自己的私有 Git 仓库并推送,或在新机器上一键还原。
功能
- 白名单采集:只搬运配置面,密钥文件(
.credentials.yaml)在硬黑名单里,永不搬运。 - 三道安全闸:文件级硬黑名单 → 提交前复查暂存区 → 密钥体检(通用正则 +
.credentials.yaml精确匹配)。 - 远端合并:采集 / 推送之前先
git fetch;远端领先且本机无提交时快进合并;两边都有提交时自动rebase;冲突则回滚并如实报错,绝不留半合并状态。 - 补推语义:推送与否按「本地是否领先远端」判断,而不是「本次是否产生了新提交」——推送失败后再点一次「一键同步」即可重试。
- 单文件容错:单个文件读不到(被锁定、权限不足、或被文件策略拒绝)只会跳过并在日志里列出,不中断整次同步。
- 零 npm 依赖:只用
node:内置模块。
安装
# 方式一(推荐):从 npm 安装
# --profile 后跟本机实际的 profile 名:桌面版是 desktop,Web 版是 web
dsh plugin --profile <profile> add @fish-under-sea/dsh-git-sync
# 方式二:link 安装(改源码开发时用)—— 仓库源码即安装源,改完重启 DSH 即生效
dsh plugin --profile <profile> add "link:<仓库路径>/packages/dsh-git-sync"
# 方式三:拷贝安装(file:)—— 改完源码必须重新 add 才生效
dsh plugin --profile <profile> add "file:<仓库路径>/packages/dsh-git-sync"
怎么选:日常使用走方式一(npm,版本可追溯、可 update);要改源码时用方式二(link: 建的是目录联接,仓库源码即安装源,改完重启 DSH 即可)。file: 是 pnpm 的目录拷贝——装完之后改 lib/*.js 不会反映到已安装的那份,重启也没用,必须重新 add 一次,只适合一次性试用。
装完重启 DSH(插件行与设置页都是下次启动生效)。
换机提醒:安装会把本机绝对路径写进
profiles/<profile>/package.json("@fish-under-sea/dsh-git-sync": "link:D:/…/plugin")。这个文件会被同步到另一台机器,而那边没有这个目录,dsh plugin install会在这一项上失败。要么两台机器 clone 到同一路径,要么在新机器上先dsh plugin --profile web remove @fish-under-sea/dsh-git-sync再按本机路径add回去。
使用
打开 设置 → Git 同步:
| 按钮 | 作用 |
|---|---|
| 一键同步 | 先 git fetch 远端并自动合并,再采集 → 提交 → 推送,日常用这一个就够 |
| 仅采集并提交 | ~/.dsh 的白名单内容 → 仓库目录,然后 git add / commit(不推送) |
| 从仓库还原到本机 | 仓库目录 → ~/.dsh,覆盖前备份到仓库的 _backup/ |
| 密钥体检 | 扫描将上传的文本文件 + 用 .credentials.yaml 里的真实密钥值做精确匹配 |
| 刷新 | 重新读取仓库状态 |
关键语义
- 「一键同步」会补推之前没推上去的提交。 推送失败(TLS 拦截、断网、凭据过期)后不用做别的,再点一次「一键同步」即可——它判断的是「本地是否领先远端」,而不是「本次是否产生了新提交」。曾经有个隐蔽的 bug:旧代码只在本次产生新提交时才推送,于是推送失败后再点一键同步会因为「本机已无变更」而永不重试,还回报成功。已修,并加了回归测试。
- 「仅采集并提交」是「先看后推」的闸口。 它只提交、不推送,方便你先看差异再决定要不要送上 GitHub。(
pushgit动作仍在 API 上保留,作为应急通道。) - 跨机同步不再需要手工救火。 采集与推送之前会先
git fetch远端:远端领先且本机没有提交时快进合并;两边都有提交时自动rebase(保持线性历史);遇到冲突则回滚到操作前并如实报错,绝不留半合并状态。此前只会 push,后果是远端一有新提交就永远卡在! [rejected] main -> main (fetch first);更隐蔽的是本机没有提交时@{u}引用陈旧,会谎报「本地与远端完全一致」而什么都不做。
状态卡怎么看
| 卡片 | 含义 |
|---|---|
| 待同步 | 本机 ↔ 仓库的内容差异(新增 / 变更 / 仓库多出)。为 0 表示两边一致 |
| 仓库内文件 | 仓库里已同步的载荷规模(文件数 · 体积)。这不是待办量 |
| 工作区 | 仓库的 git 工作区是否干净、是否领先远端 |
| 分支 / 远端 | 当前分支与 origin URL |
| 上次运行 | 上一次动作、时间与成败 |
「待同步」与「工作区」要一起看:待同步 = 0 且工作区干净 ⇒ 本机配置已完整提交并推送到远端,没有什么可同步的。早先版本把「仓库内文件数」标成了「待同步文件」,会让人误以为有几十个文件排队等着上传——已改。
配置
仓库目录在面板里直接改、点「保存设置」即可。它存在 $DSH_HOME/dsh-git-sync/config.json——故意放在同步范围之外,所以每台机器可以指向自己的克隆路径,不会被互相覆盖。
同步范围(白名单)
| 路径 | 含义 |
|---|---|
profiles/<profile>/package.json |
装了什么插件 + bundle 层顺序 |
profiles/<profile>/cordis.patch.yml |
每个插件是否启用(disabled: 行)+ 配置覆盖 |
profiles/<profile>/pnpm-lock.yaml / pnpm-workspace.yaml |
精确版本与 pnpm 配置 |
settings.yaml |
0.1.x 口径的全局设置;0.2.0 起 home 下的它已被 patch layer 改名 settings.yaml.imported,实际设置落在 profiles/<profile>/cordis.patch.yml |
skills/ |
Skill 目录(~/.dsh/skills) |
task-board/、dsh-session-archive/、dsh-usage/ |
看板账本、归档状态、用量账本 |
dsh-settings-nav-order/state.json |
设置导航顺序偏好(顺序 / 隐藏项)——由 dsh-settings-nav-order 的宿主半区在用户保存时写入(同源路由);浏览器真正的 localStorage 本插件够不着,所以跨机复原靠这一个文件 |
skin-center-active.json |
皮肤 |
<profile>按目录动态枚举:本机profiles/下每个 profile 目录都会被覆盖,桌面版是desktop、Web 版是web。写死 profile 名会漏掉「装了什么插件 / 每个插件是否启用 / 精确版本」这最要紧的三样——0.2.0 桌面版踩过这个坑:profile 改名后白名单一条都命中不了,仓库里只剩 0.1.x 的profiles/web快照。
Skill 位置很关键。 DSH 会从多个根目录读 Skill:
<项目根>/.dsh/skills、<项目根>/.agents/skills、<DSH_HOME>/skills、~/.agents/skills。本插件只覆盖<DSH_HOME>/skills(即~/.dsh/skills)——所以 Skill 必须放在用户级目录才会被同步。放在项目级.dsh/skills的不在同步范围内。
不在范围内:
sessions/、attachments/(0.2.0 起永久排除)、.agent-presets/、pet.json、storages/workspace.json(0.2.5 起撤下)、node_modules、.credentials.yaml等密钥文件、派生物(cordis.yml、storages/session_projcache)、本机状态(.anonymous-user-id等)。
0.2.5 撤下三条,理由各自独立:
.agent-presets/—— 不用自定义 agent 预设;pet.json—— 桌宠插件没启用,纯死文件;storages/workspace.json—— 里面是机器相关的绝对路径,搬到另一台机器本来也要手工改(见下方「兼容与边界」),同步它只会带来「换机后工作区指向不存在的位置」的噪声。
撤下的内容仍留在本仓历史里(git log -- <路径> 可取回);想恢复同步,把对应的行加回 WHITE_LIST 即可。
同步范围 0.2.0 起收缩为「配置面」:会话记录与附件不再上传。sessions/ 是 zstd 二进制,只增不减、git 无法 diff 也无法行级合并,两台机器同时改必然冲突;attachments/ 同理且属本机隐私数据。配置面换机后靠这一份清单 + 各自的会话副本复原即可。已提交到仓库的旧会话文件不会被删除(只停新增),历史保留可查。
永不搬运
.credentials.yaml · .env · *.pem · *.key · node_modules/ · .pnpm/ · profiles/*/cordis.yml(派生的空根)· storages/session_projcache/ · .anonymous-user-id · .dshw-size.json · .dshw-usage.json · workspace-local-paths.json · 各类 *.bak* / *.pre-sync-*
唯一例外:
profiles/<profile>/cordis.patch.yml.bak-plugin-manager(插件管理器写的配置备份,换机复原时有用)按精确整路径放行,其它*.bak*仍一律拒绝。
安全设计(三道闸)
- 文件级硬黑名单:路径任一段命中即拒绝。这是唯一不依赖内容判断的闸,
.credentials.yaml因此在结构上就不可能被搬运。 - 提交前复查暂存区:
git add后检查git diff --cached --name-only,一旦出现密钥类文件名 →git reset并中止提交。 - 密钥体检:读
.credentials.yaml提取真实密钥值做精确匹配,叠加通用正则(sk-…、GitHub token、AWS key、私钥块、Bearer、api_key:赋值)。
其他约束:不做 force push、不重写历史;git 以非交互方式运行(GIT_TERMINAL_PROMPT=0),不会弹凭据窗口卡住宿主;API 路由只允许同源请求(拒绝 sec-fetch-site: cross-site)。
兼容与边界
- 工作区映射不再同步(0.2.5 起):
storages/workspace.json里是绝对路径(如D:\Fish-code\DSH),换机后本来就该由那台机器自己生成;本插件既不采集它、也不在还原时覆盖它。 - 会话是 zstd 压缩二进制,git 无法 diff / 行级合并。两台机器同时改同一会话会二进制冲突,本插件不做内容级合并。
- 仓库只增不减:会话是追加型数据,删本地不会缩小仓库。
- 装完插件后需要重启 DSH 才能在设置页看到它。
- 依赖
git在 PATH 中。若你的机器用 HTTPS 拦截式加速器(如 Watt Toolkit / SteamTools),git 可能报 TLS 错误——解决办法见仓库的AGENT-GUIDE.md§9.1。 - 本机没有 git 身份也能同步:提交前会探一次
user.name/user.email,没有就临时用 origin 的 GitHub 主人名提交(详见下方「三个已修的坑」)。想固定成你自己的身份,在仓库里执行git config user.name/user.email即可。 - 主机半边的
ctx.webServer路由是 loopback + 同源保护,没有额外的鉴权层。 - 只覆盖用户级 Skill(
~/.dsh/skills)。项目级.dsh/skills、.agents/skills与~/.agents/skills不在同步范围内。 - 刻意不同步:
skin-center/(只有可再生的壁纸令牌缓存)、*.bak*、*.lock。
三个已修的坑
本机没有 git 身份会让整次同步失败。 全新机器上 user.name / user.email 都没有时,git commit 直接拒绝(Author identity unknown … unable to auto-detect email address)—— 而全新机器恰恰是「换机复原」最需要同步成功的一刻。0.2.3 及以前把这句报错笼统报成「推送失败」,面板上只显示「推送不行」,完全看不出真正原因。现在:提交前先探一次身份(git var GIT_AUTHOR_IDENT,与 git commit 同一套解析),没有就按 origin 的 GitHub 主人名提交(配成 <主人>@users.noreply.github.com,与仓库既有提交一致),拿不到 origin 就退到「本机登录名 @ 主机名」,并在面板日志里写明用了谁、怎么固定下来。配了身份的机器完全不受影响(连 -c 都不注入,作者就是你配置里的那个)。
Windows 只读目标会让覆盖失败。 copyFileSync 会把源文件的只读属性带到目标,而 Windows 的 CopyFileW 在目标已存在且带 ReadOnly 时直接返回 ERROR_ACCESS_DENIED。两者叠加的结果是「第一次采集成功,之后每次都失败」。处理:拷前清掉目标只读位;仍失败则删掉目标重来;拷后保持可写。(实测:修复前仓库里有 108 个只读文件,修复后为 0,采集 0 跳过。)
单个文件失败不该炸掉整次同步。 插件跑在 DSH 宿主进程内,而会话与附件正在被该进程写入,出现 EBUSY / EPERM 是常态。现在每个文件独立 try/catch,失败记入 skipped 并在面板日志里列出,其余文件照常同步。
开发与测试
- 零 npm 依赖:只用
node:内置模块。宿主 loader 在插件未导出Configschema 时会把config原样透传(if (!runtime.Config) return config),所以这里不引入 schemastery。 - Web 半边用
react.createElement手写,只require('react'),不依赖任何@deepseek-ai/*客户端包。 - 宿主导出:
name/inject = ['webServer']/apply(ctx, config)。 - Web 半导出:
inject = ['slots']/apply(ctx),向settings.section注册一页。
本包没有配置 scripts.test;测试入口为一个 node:test 文件、两个手写断言脚本,以及一个必须用真实 git 的身份回退测试:
node test/client.test.mjs # Web 半边装载冒烟(node:test)
node test/test-sync-engine.mjs # 同步引擎:白名单 / 采集 / 还原 / 差异 / 拒绝闸
node test/test-client.mjs # Web 半边渲染与文案断言
node test/commit-identity.test.mjs # 没有 git 身份时的提交回退(真实临时仓库 + 真实提交)
node test/test-push-retry.mjs # 补推语义(真实远端)
node test/test-remote-merge.mjs # 远端分叉时的快进 / rebase(真实远端)
直接
node <测试文件>即可,不要用node --test test/:测试运行器会派生子进程并捕获管道输出,在受限沙箱里会以EPERM失败。 后三个用例要起子进程跑git,而git()走的是管道 —— 在受限沙箱里同样会spawn EPERM(这是沙箱边界,不是代码问题);请在普通终端或放宽沙箱的环境里跑它们。
与聚合包的关系
本包是
@fish-under-sea/dsh-fish聚合包的成员之一;单独安装只影响这一项。
许可
MIT © Fish-under-sea