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

用户配置同步 PandaBlessing/dsh-user-sync

DeepSeek Harness
PandaBlessing9 天前有提交已提供中文文档扫描:含敏感能力

跨设备同步技能、插件清单、配置与记忆

29.8/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分

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

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add PandaBlessing/dsh-user-sync
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:仅索引
是什么[目录]—
dsh 原生插件 · other
暂无时间戳·判据 →
装得上吗[实装验证]—
本站尚未做安装检查
暂无时间戳·判据 →
安全吗[自动扫描]—
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
数据截至 2026/11/04·判据 →
还在维护吗[仓库]✓
活跃:最近一次提交在 9 天前
暂无时间戳·判据 →
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成

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

验证判据与等级说明 →

可信度证据

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

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

可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 110 处,展示前 68 条)
  • 执行外部命令来自配置/变量
    lib/index.js:44
    const child = spawn(file, args, { cwd: options.cwd, stdio: ['ignore', 'pipe', 'pipe'] });
  • 读取本地文件
    lib/index.js:71
    for (const dir of readdirSync(root, { withFileTypes: true })) {
  • 读取本地文件
    lib/index.js:78
    if (existsSync(candidate))
  • 读取本地文件
    lib/index.js:90
    const pkg = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json'), 'utf8'));
  • 写入本地文件
    lib/index.js:101
    mkdirSync(dir, { recursive: true });
  • 写入本地文件
    lib/index.js:102
    appendFileSync(join(dir, 'plugin-error.log'), line);
  • 读取本地文件
    lib/index.js:130
    const raw = readFileSync(yamlPath, 'utf8');
  • 写入本地文件
    lib/index.js:140
    writeFileSync(yamlPath, yamlLines.join('\n'), 'utf8');
  • 写入本地文件
    lib/index.js:143
    writeFileSync(yamlPath, `${raw.trimEnd()}\nminimumReleaseAgeExclude:\n - '${entry}'\n`, 'utf8');
  • 读取本地文件
    lib/index.js:199
    const pkg = JSON.parse(readFileSync(join(paths.profileDir, 'package.json'), 'utf8'));
  • 读取本地文件
    lib/index.js:254
    return existsSync(pnpmCjs) ? { node: runtime, pnpmCjs } : null;
  • 写入本地文件
    lib/index.js:294
    writeFileSync(workspacePath, kept.join('\n'), 'utf8');
  • 写入本地文件
    lib/index.js:327
    writeFileSync(pkgPath, `${JSON.stringify(patched, null, 2)}\n`, 'utf8');
  • 发起网络请求
    lib/index.js:420
    const response = await fetch(`${registry}/-/package/${encoded}/dist-tags`, { signal: controller.signal });
  • 发起网络请求
    lib/index.js:464
    const res = await fetch(`${effectiveBaseUrl()}/v1/feedback`, {
  • 发起网络请求
    lib/client.js:118
    const response = await fetch(url, { cache: 'no-store', ...options });
  • 读取本地文件
    lib/scan.js:43
    dirents = readdirSync(root, { withFileTypes: true });
  • 读取本地文件
    lib/scan.js:87
    const stat = lstatSync(abs);
  • 读取本地文件
    lib/scan.js:105
    dirents = readdirSync(skillsRoot, { withFileTypes: true });
  • 读取本地文件
    lib/scan.js:116
    return readdirSync(skillDir).includes('SKILL.md');
  • 读取本地文件
    lib/scan.js:133
    const stat = lstatSync(join(profileDir, name));
  • 读取本地文件
    lib/scan.js:162
    patchFiles = readdirSync(join(profileDir, 'patches'), { withFileTypes: true })
  • 读取本地文件
    lib/state.js:11
    const raw = JSON.parse(readFileSync(statePath, 'utf8'));
  • 写入本地文件
    lib/state.js:19
    mkdirSync(dirname(statePath), { recursive: true });
  • 写入本地文件
    lib/state.js:21
    writeFileSync(tmp, `${JSON.stringify(state, null, 2)}\n`);
  • 写入本地文件
    lib/state.js:22
    renameSync(tmp, statePath);
  • 写入本地文件
    lib/state.js:26
    mkdirSync(dirname(historyPath), { recursive: true });
  • 写入本地文件
    lib/state.js:27
    appendFileSync(historyPath, `${JSON.stringify(entry)}\n`);
  • 读取本地文件
    lib/state.js:28
    const lines = readFileSync(historyPath, 'utf8').split('\n').filter(Boolean);
  • 写入本地文件
    lib/state.js:30
    writeFileSync(historyPath, `${lines.slice(-HISTORY_LIMIT).join('\n')}\n`);
  • 读取本地文件
    lib/state.js:39
    if (existsSync(lockPath)) {
  • 读取本地文件
    lib/state.js:42
    holder = JSON.parse(readFileSync(lockPath, 'utf8'));
  • 读取本地文件
    lib/state.js:59
    const holder = JSON.parse(readFileSync(lockPath, 'utf8'));
  • 读取本地文件
    lib/state.js:71
    const holder = JSON.parse(readFileSync(lockPath, 'utf8'));
  • 删除本地文件
    lib/state.js:74
    rmSync(lockPath, { force: true });
  • 删除本地文件
    lib/state.js:95
    rmSync(join(dir, name), { force: true });
  • 读取本地文件
    lib/sync.js:77
    map.set(entry.path, hashBytes(readFileSync(entry.absPath)));
  • 读取本地文件
    lib/sync.js:93
    const data = readFileSync(entry.absPath);
  • 读取本地文件
    lib/sync.js:103
    const pkg = JSON.parse(readFileSync(join(dshHome, 'profiles', profileName, 'package.json'), 'utf8'));
  • 读取本地文件
    lib/sync.js:128
    tarEntries.push({ path: entry.path, data: readFileSync(entry.absPath), mode: entry.mode });
  • 读取本地文件
    lib/transport/api.js:26
    if (!token && authPath && existsSync(authPath)) {
  • 读取本地文件
    lib/transport/api.js:28
    token = JSON.parse(readFileSync(authPath, 'utf8')).token ?? null;
  • 写入本地文件
    lib/transport/api.js:38
    mkdirSync(dirname(authPath), { recursive: true });
  • 写入本地文件
    lib/transport/api.js:39
    writeFileSync(authPath, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
  • 删除本地文件
    lib/transport/api.js:44
    rmSync(authPath, { force: true });
  • 写入本地文件
    lib/transport/folder.js:10
    mkdirSync(dir, { recursive: true });
  • 写入本地文件
    lib/transport/folder.js:22
    writeFileSync(probe, 'ok');
  • 删除本地文件
    lib/transport/folder.js:23
    rmSync(probe, { force: true });
  • 读取本地文件
    lib/transport/folder.js:32
    return readdirSync(dir)
  • 读取本地文件
    lib/transport/folder.js:36
    size: statSync(join(dir, name)).size,
  • 读取本地文件
    lib/transport/folder.js:37
    modifiedAt: statSync(join(dir, name)).mtime.toISOString(),
  • 读取本地文件
    lib/transport/folder.js:44
    if (!existsSync(abs))
  • 读取本地文件
    lib/transport/folder.js:47
    const entries = parseTarGz(readFileSync(abs));
  • 读取本地文件
    lib/transport/folder.js:59
    return readFileSync(join(dir, name));
  • 写入本地文件
    lib/transport/folder.js:64
    writeFileSync(join(dir, name), data);
  • 删除本地文件
    lib/transport/folder.js:73
    rmSync(join(dir, name), { force: true });
  • 写入本地文件
    lib/apply.js:35
    mkdirSync(p, { recursive: true });
  • 写入本地文件
    lib/apply.js:40
    copyFileSync(abs, backup);
  • 读取本地文件
    lib/apply.js:70
    if (existsSync(abs)) {
  • 删除本地文件
    lib/apply.js:73
    rmSync(abs, { force: true });
  • 读取本地文件
    lib/apply.js:79
    const existing = existsSync(abs) ? readFileSync(abs) : null;
  • 读取本地文件
    lib/apply.js:79
    const existing = existsSync(abs) ? readFileSync(abs) : null;
  • 写入本地文件
    lib/apply.js:89
    writeFileSync(tmp, entry.data);
  • 写入本地文件
    lib/apply.js:90
    renameSync(tmp, abs);
  • 读取本地文件
    lib/apply.js:97
    if (existsSync(backup))
  • 写入本地文件
    lib/apply.js:98
    copyFileSync(backup, toAbs(rel));
  • 删除本地文件
    lib/apply.js:102
    rmSync(toAbs(rel), { force: true });
  • 读取本地文件
    lib/hash.js:20
    return hashBytes(readFileSync(absPath));
扫描于 2026/11/4(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

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

社区实装还没有人打卡 · 装过的第一个就是你
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

DSH 插件生态速览:安装、配置、技能、子代理,以及怎么把这几样串起来(附常见坑和排查思路)

README

由 DeepSeek 最新模型翻译生成
dsh-user-sync 换机器、多台电脑用 DSH,不用再手动拷 ~/.dsh。 DeepSeek Harness(DSH)的用户级配置同步插件:把技能、插件清单、配置、记忆在设备间对齐——新机器装一次、点一下,环境和老机器一致。 30 秒上手 cd ~/.dsh/profiles/desktop pnpm add @pandablessing/dsh-user-sync 重启 DSH → 设置 → 设置同步 → 同意并启用 → 上传用户配置 换到新机器,同样装一次 → 设置同步 → 从远端同步。 它能解决什么 | 痛点 | 以前 | 现在 | | 换台电脑,DSH 环境全空 | 手动 scp -r ~/.dsh,漏文件、带错配置 | 点「从远端同步」,覆盖前弹窗确认 | | 插件装不回来 | 一个个翻 package.json 重新 pnpm add | 按远端清单自动装回,失败项单独列出 | | 配置改乱了想回滚 | 无备份 | 每次应用前生成 pre-restore 快照 | | 担心密钥被传走 | —— | 文件名黑名单 + 内容模式扫描,命中直接拒绝打包 |
展开完整文档(README 剩余 1 节)
dsh-user-sync

换机器、多台电脑用 DSH,不用再手动拷 ~/.dsh。

DeepSeek Harness(DSH)的用户级配置同步插件:把技能、插件清单、配置、记忆在设备间对齐——新机器装一次、点一下,环境和老机器一致。

30 秒上手

cd ~/.dsh/profiles/desktop
pnpm add @pandablessing/dsh-user-sync
重启 DSH → 设置 → 设置同步 → 同意并启用 → 上传用户配置

换到新机器,同样装一次 → 设置同步 → 从远端同步。

它能解决什么

| 痛点 | 以前 | 现在 |
| 换台电脑,DSH 环境全空 | 手动 scp -r ~/.dsh,漏文件、带错配置 | 点「从远端同步」,覆盖前弹窗确认 |
| 插件装不回来 | 一个个翻 package.json 重新 pnpm add | 按远端清单自动装回,失败项单独列出 |
| 配置改乱了想回滚 | 无备份 | 每次应用前生成 pre-restore 快照 |
| 担心密钥被传走 | —— | 文件名黑名单 + 内容模式扫描,命中直接拒绝打包 |

同步范围(默认四类用户级): 技能 ~/.agents/skills、插件清单(profile 声明与锁文件)、配置 cordis.patch.yml(含自定义 agent 预设)、用户记忆 AGENTS.md + memories/。

截图

面板截图放在 docs/assets/,欢迎 PR 补充(设置同步面板 / 恢复结果 / 运营监控页)。

和「手动拷 .dsh」/ 其他同类的差别

| | 手动拷 .dsh | 本插件 |
| 跨机恢复 | 整目录搬,容易带错机器专属状态 | 只同步四类用户级资产,会话/项目记忆不参与 |
| 插件 | 手动重装 | 自动装回,失败清单可见 |
| 覆盖安全 | 无确认 | 弹窗确认 + 快照回滚 + 远端新包拦截(E_REMOTE_NEWER) |
| 隐私 | 全量拷贝 | 敏感文件硬拒绝(黑名单 + 内容模式) |
| 登录 | —— | 用官方账号 id 自动派生 key,没有运营方登录入口 |

同类项目还有 dsh-sync 等;本插件侧重「自动装回插件 + 敏感数据硬拒绝 + 可自建服务端」。

安全与数据

- 存储凭据只在运营方服务端,插件包内不含任何存储口令;
- 上传内容经 HTTPS 到运营方 API,再落到坚果云 WebDAV;远端只保留 1 份包,上传即覆盖;
- 想自建:服务端是纯 node:http 零依赖,见 docs/deploy.md,在 state.json 写 baseUrl 即可指向自己的服务。

功能细节

- 四类用户级资产:技能、插件清单、配置(cordis.patch.yml,含自定义 agent 预设)、用户记忆;项目记忆默认不选,会话不参与同步。
- 新覆盖旧:包级 createdAt 比较,不做合并;覆盖前弹窗确认,先快照再写盘。
- 上传前查远端:远端已被别的设备更新时先拦下(E_REMOTE_NEWER),确认后才用本机覆盖。
- 敏感数据硬拒绝:文件名黑名单 + 内容模式,命中即拒绝打包并列出文件与行号。
- 官方账号自动配对:用 deepseekAccount 派生 accountKey 自动换 token,无需运营方登录。
- 缺插件自动装回:应用远端包后按 manifest.dependencies 自动安装,结果列出已装 / 失败 / 需手动放回(link: 依赖)。
- 自动检查:启动 + 每 6 小时(可配)只读远端元数据,发现更新只提示、不自动应用。
- 更新提示:发现 npm 上有新版本时面板顶部给出「更新插件」按钮,完成后弹窗提示重启。

安装

从 npm

cd ~/.dsh/profiles/desktop
pnpm add @pandablessing/dsh-user-sync

profile 的 package.json 与 cordis.patch.yml 里必须使用 npm 包名 @pandablessing/dsh-user-sync;若此前用旧名 dsh-user-sync 装过,先删掉旧条目(junction / dependencies / bundles)再装,否则会重复注册面板。

安装后重启 DSH,在设置页启用「设置同步」。

开发期(junction 到本仓库)

powershell -NoProfile -ExecutionPolicy Bypass -File scripts\install-dev.ps1 # 装进 desktop profile scripts\uninstall-dev.cmd # 回滚

使用

1. 设置 → 设置同步 → 首次点「同意并启用上传」;
2. 勾选同步范围(默认四类用户级);
3. 「上传用户配置」→ 打包并上传(远端更新时先确认再用本机覆盖);
4. 「从远端同步」→ 检查远端,覆盖前弹窗确认。

服务地址内置(https://dsh-user-sync-test.wenzhi.info),传输固定走运营方 API;需要自建服务时在 state.json 写 baseUrl 覆盖。

架构

插件(HTTP 客户端) ──HTTPS──▶ 运营方 API ──WebDAV──▶ 坚果云 │ │ └─ 本地 state.json └─ 鉴权 / 配额 / 隔离 / 审计

- 插件侧只实现 api 传输;folder 仅用于本机调试。
- 存储凭据只放在服务端,插件包里不含任何存储口令。
- 身份:官方账号 id → accountKey(sha256 前 16 位)→ 服务端分区。
- 远端只保留 1 份包,上传即覆盖;每次应用在本地生成 pre-restore 快照。

构建与测试

源码是 TypeScript,构建产物 lib/ 与 server/dist/ 已提交(junction 安装与部署直接用)。

pnpm install
pnpm run build # 插件:src/.ts → lib/.js
pnpm run build:server # 服务端:server/src/.ts → server/dist/.js
pnpm test # 单元 / 组件 / 时序(直接跑源码)
node scripts/audit-cases.mjs # 用例清单与结果记录一致性审计
node scripts/e2e-clone.mjs # 替代换机演练(本地模式,不写真实 WebDAV)
node scripts/verify-npm-install.mjs # npm 安装冒烟(临时 profile,用完即删)
powershell -File scripts/smoke.ps1 # 真实 DSH 路由冒烟

开发与真实环境

- 日常开发(junction):install-dev.ps1 把仓库 link 进 profile,改源码 → pnpm run build → 热重载生效。
- 验证真实安装:verify-npm-install.mjs 在临时 profile 里 pnpm add 已发布版本,校验 patch 名、宿主/client 入口与资源文件。
- 两者互不干扰:npm 安装的是编译产物副本,改源码不会生效,所以开发用 link、验证用临时 profile。

发布

发布时机由维护者决定(scripts/publish.mjs 只手动执行,agent 不自动发布)。

cp .env.local.example .env.local && chmod 600 .env.local # 填 NPM_TOKEN
node scripts/publish.mjs --dry-run # 先看打包内容
node scripts/publish.mjs --otp=123456 # 2FA 时带当次验证码

token 只写进 0600 临时 npmrc、发布后删除,不进命令行与 git;CI 可用 NPM_TOKEN 环境变量。发布前先 pnpm run build && pnpm run build:server。

已知限制

- 桌面端整机验证以 Windows / macOS 为主,Linux 未验证。
- 服务端单节点,未做限流与多副本;坚果云单账号有流量配额。
- link: 本地依赖需要手工放回目标机。
- 远端只保留 1 份包,回滚依赖本地 pre-restore 快照。

赞赏

插件免费开源(MulanPSL-2.0)。如果它帮你省过换机时间,欢迎小额赞赏,用于服务端坚果云空间与持续维护。赞赏自愿,不影响功能与权益。

微信赞赏码

仓库结构

docs/ 设计 / 用例 / 部署 / 测试结果 lib/ 插件(宿主 + client + 传输 + 同步编排) server/ 运营方 API(纯 node:http) scripts/ 安装 / 卸载 / 冒烟 / 换机演练 / 发布 / npm 安装冒烟 test/ 单元、组件、时序测试与 fixtures legacy/ 需求稿与原型脚本

License

Mulan PSL v2
预览分享卡片长什么样 ↓PandaBlessing/dsh-user-sync
分享 · DeepSeek Harness Hub
PandaBlessing/dsh-user-sync

跨设备同步技能、插件清单、配置与记忆

这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。
认领条目 →
回到顶部