- Home
- Plugins
- Dev & Plugin Tools
- dsh-settings-sync
dsh-settings-sync
elfgzp/dsh-settings-sync
Backs up and restores the whole DSH profile through a private Git repository: profile configuration, per-plugin settings, skills, installed plugins and MCP servers, with one-click backup and restore and a restore point written before anything is overwritten.
Install
dsh plugin --profile web add github:elfgzp/dsh-settings-syncREADME
dsh-settings-sync
Back up and restore every DeepSeek Harness setting through a private Git repository you own — profile configuration, per-plugin settings, skills, installed plugins, and MCP servers. Push from one machine, restore on another, with a restore point written before anything is overwritten.
Why this exists
DSH keeps its configuration in a few places, and they are all easy to lose:
| What | Where |
|---|---|
| The plugin inventory and the profile's bundle list | ~/.dsh/profiles/<profile>/package.json |
| Per-plugin configuration (models, MCP servers, panel settings) | ~/.dsh/profiles/<profile>/cordis.patch.yml |
| Skills | ~/.dsh/skills/, ~/.agents/skills/ |
| Per-plugin state (starred items, default projects, tasks) | ~/.dsh/<plugin>/ |
dsh export copies that once, to a file you then have to move around yourself.
This plugin makes it continuous: one private repository, one button in each
direction, and a diff you can read before you commit to either.
What gets synced
manifest.json what produced this snapshot, and when
profile/package.json the plugin inventory and bundle list
profile/cordis.patch.yml every plugin's configuration
profile/cordis.yml the profile's root entry list
profile/pnpm-workspace.yaml workspace and release-age settings
homes/dsh/<plugin>/*.json per-plugin settings (starred, defaults, tasks)
homes/dsh/skills/<name>/… user-level skills under $DSH_HOME
homes/agents/skills/<name>/… shared agent skills under $DSH_AGENTS_HOME
And what deliberately does not:
-
Credentials.
~/.dsh/.credentials.yaml,tencent-intranet-auth.json,.env,*.pem,*.token,pairing.json— excluded by name and by content. On top of that, live values that appear inside otherwise-synced config — anapiKey:line incordis.patch.yml, say — are replaced with<redacted by dsh-settings-sync>before the snapshot leaves the machine. The configuration's shape is preserved, so restoring still tells you a key belongs there; you just fill it in again.Redaction is one-way. A pull never writes the placeholder over a real local value: the restorer matches credential lines by key name and keeps the machine's own secret, so restoring on the machine the snapshot came from leaves its API keys working. Only a machine that has no value yet receives the placeholder, where it is exactly the right message — "a key is owed here".
-
Dependencies and caches.
node_modules,pnpm-lock.yaml,__pycache__,.plugin-managerlogs, attachment blobs. -
Sessions and history.
~/.dsh/sessions,~/.dsh/storages— history is not a setting, and it is by far the largest thing in a DSH home. -
Backups of previous edits. Anything matching
.bakin the profile.
A real machine — 21 installed plugins, 66 skills, six per-plugin state directories — collects to 263 files and 1.4 MB.
Install
git clone https://github.com/elfgzp/dsh-settings-sync.git ~/Work/Github/dsh-settings-sync
Then, in DSH, either use Settings → Plugins → Install, or from a terminal:
dsh plugin install ~/Work/Github/dsh-settings-sync
The plugin adds a Settings Sync page under Settings, and on first launch asks one question: pull the remote settings down, or push these ones up.
Requirements: git on PATH (or set its absolute path in the plugin config),
and — only if you want the one-click "create repository" button — the
GitHub CLI logged in with gh auth login.
Use
- Set a repository. Paste the URL of an empty private repository
(
git@github.com:you/dsh-settings.gitandhttps://github.com/you/dsh-settings.gitboth work), or press Create private repo to haveghmake one. - Push to upload this machine's settings.
- On the other machine, install the plugin, paste the same URL, and Pull.
The diff panel shows exactly what a push would change — +added, ~modified,
-removed — before you press anything.
Automatic sync
| Setting | Meaning |
|---|---|
| Redact secrets | Replace live keys with a placeholder before upload. On by default. |
| Auto push | Push after a settings change, on the check interval. |
| Remote policy | never — ignore the remote; ask — offer a choice (default); always — pull when the remote is ahead and local is clean. |
| Check interval | Off, 5 minutes, 30 minutes, or 1 hour. |
Safety model
- Nothing is overwritten without a restore point. Pull writes a full copy of
the current state under
<stateDir>/backups/<timestamp>-snapshot/first, and that path is reported when the pull finishes. The restore points are listed on the settings page and can be restored in one click. - A push that collects nothing is refused. A mis-resolved
HOMElooks like "the user deleted everything", and silently replacing a good backup with an empty snapshot is unrecoverable. The same guard refuses a push that would delete most of the tracked files unless you explicitly force it. - The snapshot is a real Git repository, in
<stateDir>/mirror. History is the backup:git login there shows every sync, andgit show <commit>:profile/cordis.patch.ymlgets you any earlier file. That is also why this plugin never needs its own format for undo.
Everything lives under $DSH_HOME/settings-sync by default:
config.json repository, branch, policies, commit identity
mirror/ the local clone whose history is the snapshot history
backups/ restore points
last-run.json the most recent push or pull result
Toolchain
pnpm test # or: node --test test/*.test.js
pnpm check # node --check over every module
The tests run the real code against the real git binary and a scratch home
directory with a fake profile, two skill roots, a per-plugin state directory, a
credential that must not be synced, and a bare repository standing in for the
remote. Nothing about git is mocked, because git is the part most likely to be
wrong.
Design notes
The mirror is a clone, not a database. Keeping the snapshot as a normal
repository means the entire undo mechanism is "check out an earlier commit", the
diff is git status, and the user can inspect or repair a sync from a terminal
without this plugin's help.
Push and pull are serialized. One operation at a time, one busy flag. A
second click waits rather than interleaving, and an automatic timer that fires
mid-pull is skipped.
Authentication is the user's own. Nothing in this plugin stores a token: it
runs the same git the user runs, so osxkeychain, an SSH agent, or a
credential.helper applies unchanged. The corollary is honest — if git push
does not work in a terminal for that repository, it will not work here either,
and the error says so.
The browser half holds no state that matters. It renders and it calls; the Host owns the filesystem, the repository, and the restore points.
Layout
| Path | Role |
|---|---|
index.js | Host entry: routes, the automatic-sync timer |
lib/config.js | Path resolution and deployment config |
lib/state.js | User configuration and last-run state |
lib/paths.js | What counts as a setting; secret and cache exclusion |
lib/snapshot.js | Collect, materialize, diff, restore, restore points |
lib/git.js | The one place that runs git |
lib/engine.js | Serialized sync engine: push, pull, preview, recover |
client.js | Settings page and the first-run dialog |
License
MIT
Related plugins
deepseek-harness
deepseek-ai/deepseek-harness
dsh-market
dsh-market/dsh-market
dsh-web (dsh-plugin-manager)
zhu1090093659/dsh-web
dsh-web
zhu1090093659/dsh-web