Introduction¶
The philosophy of DSH is “everything is a plugin.” After using a few profiles, the problem slowly shifts to a second category: plugins travel with the machine they are installed on. Switching to a new machine means finding the repository again, cloning again, and filling out a configuration again; plugins installed from local directories (link: / file:) don’t even have the “finding” step because there is no registry version to reinstall.
dsh-plugin-sync offers a solution: write the plugin manifest, version, enabled status, and configuration into your own private GitHub repository, bypassing any third-party clouds. The plugin is maintained by Jfreey and is listed under the “Online Tools” category in the community directory. The repository currently has 0 stars and is an early-stage project.
Below is an introduction to what it does, how to install it, how to use it, and the boundaries you must know.
What Is It¶
One-sentence positioning: It uses your own private GitHub repository as the DSH plugin manifest and source code backup. Cross-machine synchronization relies on “push/pull” confirmation at both ends.
It does not replace the plugin manager. Installation, uninstallation, and upgrades are still done in DSH’s plugin manager; this plugin is only responsible for recording “what is currently installed, what version, what configuration, and where the source is,” pushing it to the repository, and then reinstalling it on other machines according to the same records.
The maintainer is Jfreey, and the license is MIT. The source code is hosted on GitHub at Jfreey/dsh-plugin-sync, and the directory page is the plugin index page on skillhub.cn (community directory, no official affiliation with DeepSeek / Huafang).
Core Capabilities Verified¶
According to the “What it does” section in the README, the following capabilities can be verified in the repository.
Manifest: Records all non-installed bundles, including name, version, enabled status, installation source, and declared row ID.
Source Tracing: For each plugin, it shows where it comes from. Priority is given to the .git remote of the local checkout, followed by the repository field of the package itself; for monorepos, it continues to show the subdirectory. If not found, it is marked as “Unknown Source” and does not guess.
Configuration Backup: Backs up the override layers of these rows in the profile. Before leaving the local machine, values in token form are uniformly replaced with <redacted>; fields like apiKeyEnv (which point to environment variable names for keys) are kept as-is because they are not the keys themselves.
Source Code Backup: Plugins installed via link: / file: do not have a registry version. The source code is committed as real files to the same private repository, one push equals one commit, and can be read directly, viewed for diffs, or searched in the repository. To restore, download, verify SHA-256, write to disk, and then link/install.
Restore: Missing installations, version replacements, writing configurations, and toggling enabled status. Grouped by checkboxes. The README clearly states “you don’t click, it doesn’t move”; each result reports what the host actually did.
Update Check: Gives a prompt when the source releases a version newer than the local machine; npm packages can be updated with one click, while local directories are left for you to handle.
Cleanup: Each line has a “Sync” switch (only affects this machine); “Clean Backups” can delete manifest entries and also delete the source code files in the repository.
Push and Pull: Push is automatically triggered after plugin changes (30-second debounce) plus manual; Pull is timed plus manual, calculates differences only, and does not act on its own.
Installation and Activation¶
The installation method provided by the README is to point the plugin manager to the repository directory. This repository itself is an installable bundle (as declared by dsh.bundle in package.json):
dsh plugin --profile desktop add /absolute/path/plugin-sync
Note that the path in the command is a local checkout or extracted directory, not the github: format. After installation, refresh the page, and the “Plugin Sync” entry will appear in Settings.
Requires DeepSeek Harness 0.2.x and depends on the following contracts: settings.section, ctx.locale, ctx.pluginManager, ctx.configEditor, ctx.credentials. Versions below this will not work.
Binding Repository and Login¶
The plugin requires a private GitHub repository to host the data. The default data repository name is dsh-plugin-sync-data, leaving the name dsh-plugin-sync for the plugin’s own public repository; they do not conflict.
- Create a private repository: Open
github.com/new?name=dsh-plugin-sync-data&visibility=private, click Create. The README specifically reminds you to create an empty repository and not check the README box. - Generate a fine-grained token: The page provides a pre-filled link. Token name, description, 90-day expiration, and
Contents: writeare already set. For “Repository access,” select Only select repositories, check the repository you just created, click Generate, and copy thegithub_pat_…. - Login: Paste the token into the first input box, click “Select from my repositories” — at this step, the token is only used to list repositories and is not written to disk — select a repository, then click “Verify and Save.” This is when the actual login and token storage occur.
There are two behaviors easily ignored in the README. First, after logging in, it pulls first and does not push the local state up, so the risk of the new machine using an empty manifest or an old version to overwrite a shared manifest is avoided; to populate the repository for the first time, click “Push” once after logging in. Second, the repository must be private, as the configuration leaves the local machine; a public repository is equivalent to publishing it. Public repositories are rejected during login and before every push, unless the confirmation item allowing public repositories is explicitly checked.
Selecting a repository that is not the sync repository will be blocked: a non-empty repository without a manifest belongs to someone else’s project. The page will list a few filenames; you confirm before writing. If the filenames are the same but the format is wrong, it will be rejected directly. Classic tokens (with repo scope) can also be used, but the permissions are wider, and the README does not recommend them. The plugin does not read git credential helpers or keychains.
What Is Actually in the Repository¶
The root directory is a dsh-plugin-sync.json, recording device and plugin entries. The structure is as follows, with field names and examples coming from the README:
{
"schema": 1,
"updatedAt": "2026-11-05T05:10:00.000Z",
"devices": {
"3f2a91c0d4e8": { "name": "workstation", "updatedAt": "…" }
},
"plugins": {
"dsh-searxng-search": {
"version": "0.5.0",
"enabled": true,
"source": "local:dsh-searxng-search",
"repository": "https://github.com/octocat/dsh-searxng-search",
"repositoryFrom": "git-remote",
"config": { "searxng-search": { "proxy": "http://127.0.0.1:7890" } },
"snapshot": {
"format": "tree",
"path": "snapshots/dsh-searxng-search/0.5.0",
"commit": "9c33e2c…",
"files": 18,
"bytes": 292546,
"sha256": "…"
},
"redacted": 1,
"updatedAt": "…",
"by": "3f2a91c0d4e8"
}
}
}
The screenshots in the README also use fictitious example data like octocat / workstation and do not contain real account or machine information.
Source code backups are placed under snapshots/<name>/<version>/. They are submitted via the Git Data API, so one push equals one commit, and the repository remains readable. Files are content-addressed; unchanged files are not uploaded again; deleted files are deleted in the same commit. Executable bits are preserved. Which files to collect depends on the files whitelist in package.json (if not written, collect all), plus package.json, cordis.patch.yml, locale/, assets/, scripts/, icons, and README. node_modules, .git, .env*, logs, and symlinks are never collected. If a single file exceeds 1 MiB, or the entire snapshot exceeds maxSnapshotBytes, the entire snapshot is rejected; no partial uploads occur.
When two machines write the same file, the push first reads the current version, merges the local entry into it, and then writes back using the read version number. A return of 409 indicates someone else wrote first, so it re-reads and merges again, up to three times. Remote entries that the local machine does not have are retained as-is. Files are a union; pushing from an empty machine will not delete anything.
Main Configuration Items¶
All fields and default values are written in the configuration table in the README. Below are the most commonly used ones:
| Field | Default Value | Meaning |
|---|---|---|
repo |
Empty | owner/name of the private repository. |
apiBase |
https://api.github.com |
Modify for GitHub Enterprise. |
manifestPath |
dsh-plugin-sync.json |
Location of the manifest file. |
snapshotsPath |
snapshots |
Location of source code backups. |
snapshotFormat |
tree |
tree stores real files; json falls back to a single-file base64 snapshot. |
deviceName |
Hostname | Device name written to the manifest. |
deviceId |
Auto-derived | Stable identity; derived from host, platform, and home when empty. |
autoPush |
true |
Automatically push after plugin changes (30-second debounce). |
autoPull |
true |
Periodically pull the manifest. |
intervalMinutes |
15 |
Pull interval, limited to 1–1440. |
includeSnapshots |
true |
Upload source code of local plugins. |
maxSnapshotBytes |
4194304 |
Upper limit on the total source code size of a single plugin. |
maxManifestBytes |
2097152 |
Manifest limit; rejected directly if exceeded. |
excludedRows |
[] |
Row IDs that this machine never publishes. |
excludedPlugins |
[] |
Plugins that this machine does not push and does not participate in diffs for. |
allowPublicRepo |
false |
Allow public sync repository. |
Settings changed in the page exist in ~/.dsh-plugin-sync/settings.json, outside of the profile, and do not overwrite any profile file; row configurations in cordis.patch.yml provide default values.
Security Boundaries¶
The README has a “Security Note” section with four points worth reading before getting started.
- Listening Scope: The plugin only accepts requests from the loopback browser,
/stateand/apirequire custom headers, and require the peer to be127.0.0.1or::1. The README directly prompts “Please let Harness listen only on loopback.” - Token Binding to Host: When logging in, it calls
GET /userand writes both the base and the token into the credentials record; all subsequent requests use this base. Changing settings cannot send the token elsewhere, and changing the API host requires re-pasting the token. - Desensitization is Two Layers: Sensitive key names and sensitive values (token form, private keys, etc.) all become
<redacted>, while references likeapiKeyEnvare retained. If token-form values appear in the source code, the entire item is rejected and the file is named. The manifest does not contain local absolute paths; paths for local plugins are replaced withlocal:<name>, and restoration relies on the source code backup. - Token Storage: It exists in the credentials record
dsh-plugin-sync/github(file permission 0600), and the plugin’s own settings file is also 0600. No route returns the token.
Typical Workflow¶
Combining the capabilities above, changing machines can follow this order.
- On the new machine, install this plugin, point it to the checkout directory as per the command above, refresh the page, and go to Settings → Plugin Sync.
- Login: Create or select a private repository, paste the fine-grained token, select the repository, and verify and save. This step is only a pull and does not push the new machine’s empty state out.
- Click “Push” to seed the repository; thereafter, plugin changes on this machine will be pushed automatically (30-second debounce), or you can manually push at any time.
- Go back to the original machine or any new machine. The Plugin Sync page will display differences. Check the entries to restore (missing installation, version replacement, writing configuration, toggling enabled status) and click Apply once. Each result reports what the host actually did.
- Plugins installed from local directories will also restore the source code: download, verify SHA-256, write to disk, and link-install. You don’t need to manually clone again.
Suitable Scenarios and Notes¶
Suitable scenarios: DSH has multiple profiles or machines, plugins are mainly installed from GitHub repositories, you want to control the backup location yourself, and you can accept the constraint of “private repository + manual confirmation.” Two independent storage locations equal two DSH profiles, suitable for profile-level isolation.
Things to note are also clear: The plugin runs with the permissions of the current dsh process and can read/write credential records and local files. You should check the source code and license before installing (this project is MIT). It requires certain contracts from Harness 0.2.x, and it will not work if the version does not match. The sync repository must remain private, as the configuration leaves the local machine. Additionally, it is a personal backup tool, not a distribution channel — the author’s research conclusion states that publishing and distribution is a task for the next version, and market PRs still need to be manual.
Conclusion¶
dsh-plugin-sync solves a very specific problem: putting “what plugins DSH has installed, what version, what configuration, and where the source is” into your own private GitHub repository, so you don’t have to find them all over again when changing machines. Manifest, version, configuration, source code backup, restoration, update hints, and cleanup are all completed in one settings page. Keys are not sent out, and public repositories or unrecognizable manifests are rejected.
Directory page: https://www.skillhub.cn/plugins/Jfreey/dsh-plugin-sync
GitHub: https://github.com/Jfreey/dsh-plugin-sync