DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

Jfreey /

Jfreey/dsh-plugin-sync

Verified

Back up and restore DSH plugins through your own private GitHub repository: list, versions, configuration and local sources.

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHubProject homepage
READMESource: main@770cde78

Plugin sync

English | 中文

Your plugins follow you. Nothing to re-find on the next machine.

The profile's plugin list, versions and configuration live in your own private GitHub repository — not someone's cloud. Backups happen as you change plugins; a new machine pulls the manifest, shows you the difference, and restores what you pick. Plugins installed from a local directory come back as source, secrets never travel, and every download is checksummed before it is written.

Plugin sync settings, English

Plugin sync settings, Chinese

Screenshots use fictional sample data (octocat, workstation); nothing from a real account or machine is shown.

What it does

  • Inventory — every profile bundle that is not shipped with the installation: name, version, enablement, installation source, and the row ids it declares.
  • Where it came from — the repository each plugin was really installed from: the git remote of a local checkout first, the package's own repository field second, including the subdirectory of a monorepo. Unknown stays unknown.
  • Configuration — the profile's override layer for those rows, with credential-shaped values replaced by <redacted> before anything leaves the machine. Environment references (apiKeyEnv) are kept, because they name a secret instead of containing one.
  • Source backup — a bundle installed from a local directory (link:/file:) has no registry version to reinstall from, so its sources are committed to the same private repository as real files, one commit per push, browsable on GitHub. Restore downloads, verifies the SHA-256, writes the files and links the directory.
  • Restore — missing bundles, version replacement, configuration writes and enablement toggles, grouped and opt-in. Nothing is applied without your click, and each result reports what the Host actually did.
  • Updates — know when a plugin's origin publishes something newer than what is installed. One click upgrades it here; local checkouts are left to you.
  • Cleanup — per-plugin sync switch (this machine only), and a Prune backup action that removes entries — optionally their source files — from the shared manifest.
  • Push — after a plugin change (30 s debounce) and on demand.
  • Pull — on a timer and on demand; the difference is shown, never applied by itself.

Install

The repository is an installable bundle (package.json declares dsh.bundle). Point the Harness plugin manager at this directory or the GitHub repository, then reload the page: Settings → Plugin sync appears.

# from a checkout
dsh plugin --profile desktop add /absolute/path/to/plugin-sync

Requires DeepSeek Harness 0.2.x (the settings.section, ctx.locale, ctx.pluginManager, ctx.configEditor and ctx.credentials contracts).

Sign in

The page offers both GitHub steps as pre-filled links, so nothing has to be typed from memory. The default data repository is dsh-plugin-sync-data; the bare name dsh-plugin-sync stays reserved for the plugin's own public repository.

  1. ① Create the private repository → github.com/new?name=dsh-plugin-sync-data&visibility=private; click Create (an empty repository, no README).
  2. ② Generate the token (pre-filled) → the fine-grained token form opens with the name, description, a 90-day expiry and Contents: write already set. Pick Only select repositories → the repository you just created, Generate, copy github_pat_….
  3. Paste it, press Pick from my repositories (the token is used for that listing only and is not stored), choose the repository, then press Validate and save — that is when the token is stored. Signing in pulls first and never pushes this machine's state — a fresh machine only reads, so an empty or older local state cannot overwrite the shared manifest. Press Push once when the repository should be seeded.

After signing in, Switch repository → Pick from my repositories lists every repository the token can write to; choosing one re-checks visibility and write access with the stored token, so no re-paste is needed. One repository is active per profile; two independent stores means two DSH profiles.

Choosing a repository that is not a sync store is refused with a confirmation: a non-empty repository without a manifest is somebody's project, so the page lists a few of its files and asks before anything is written. A dsh-plugin-sync.json in an unexpected shape is refused outright.

A classic token with the repo scope also works but is broader, and is not the recommendation. The plugin never reads your git credential helper or the keychain.

The repository must be private: configuration leaves the machine, and a public repository would publish it. A public repository is refused unless you tick Allow a public repository explicitly.

What lands in the repository

dsh-plugin-sync.json at the repository root:

{
  "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"
    }
  }
}

Local sources are stored as real files under snapshots/<name>/<version>/, written through the Git Data API so a push is one commit and the repository stays readable: you can open a plugin's source on GitHub, see the diff between two backups, and search it. Files are content-addressed, so an unchanged file costs no upload. Removed files are deleted in the same commit; the executable bit is preserved.

Which files: those listed by the package's files field, plus package.json, cordis.patch.yml, locale/, assets/, scripts/, the icon and the READMEs. node_modules, .git, .env*, logs and symlinks never are. A file above 1 MiB or a snapshot above maxSnapshotBytes refuses the whole snapshot instead of uploading a truncated one.

Merging

Two devices write the same file. Each push reads the current revision, merges this device's entries into it, and writes with the revision it read; a 409 means another writer won, so it reads and merges again (three attempts). Remote entries this device does not have stay untouched — the file is the union, and an empty device pushing never deletes anything.

Configuration

Field Default Meaning
repo empty owner/name of the private repository.
apiBase https://api.github.com Change for GitHub Enterprise.
manifestPath dsh-plugin-sync.json Where the manifest lives.
snapshotsPath snapshots Where source backups live.
snapshotFormat tree tree stores real files; json stores one base64 snapshot file.
deviceName host name Shown in the manifest.
deviceId derived Stable identity; derived from host, platform and home when empty.
autoPush true Push after a plugin change, 30 s debounce.
autoPull true Pull the manifest on a timer.
intervalMinutes 15 Timer period, clamped to 1–1440.
includeSnapshots true Upload sources for linked bundles.
maxSnapshotBytes 4194304 Total source cap per plugin.
maxManifestBytes 2097152 Manifest cap; larger documents are refused.
excludedRows [] Loader row ids this device never publishes.
excludedPlugins [] Plugins this device neither pushes nor diffs.
allowPublicRepo false Permit a public sync repository.

Settings changed in the page are persisted in ~/.dsh-plugin-sync/settings.json (outside the profile, so no profile file is rewritten); the row configuration in cordis.patch.yml provides the defaults.

Security notes

  • Routes accept a loopback browser only. /state and /api require the custom header x-dsh-plugin-sync: 1 (which forces a CORS preflight the route never answers, so another origin cannot send it), refuse any peer that is not 127.0.0.1/::1, and reject an Origin outside loopback or a browser Sec-Fetch-Site of cross-site. A plain curl still needs the header, and a web page cannot add it. They are not behind the Harness session login: keep Harness on loopback.
  • The token is bound to the host that validated it. Login calls GET /user against an HTTPS base (plain HTTP only for loopback), and the base is stored in the credential record beside the token. Every later request uses that stored base, so changing a setting can never route the token elsewhere; switching API hosts requires pasting the token again.
  • Nothing secret is published. Redaction is two-layered: credential-shaped key names and credential-shaped values (ghp_…, github_pat_…, sk-…, xox…, AKIA…, PEM private keys) become <redacted>. Environment references such as apiKeyEnv stay, because they name a secret instead of containing one.
  • Sources are scanned and refused. A file that matches a credential shape stops the upload and names the file, so a token pasted into a plugin can never reach the repository.
  • The manifest carries no local paths. A linked bundle's absolute path is replaced by local:<name>; restore uses the source backup. Repository paths (manifestPath, snapshotsPath, a snapshot's own path) are validated against .., absolute forms and backslashes, and every restored file is checksummed and path-checked before it is written.
  • Provenance never carries credentials. A git remote such as https://user:token@github.com/o/r.git is normalized to a plain URL before it is stored.
  • Only expected sources install. Apply accepts a github: spec, a https://github.com/… spec, or the exact name@version the plan generated; anything else is refused with INSTALL_REFUSED / SOURCE_MISSING. Updates are pinned to the exact version that was detected.
  • A public repository is refused at login and again before every push. The only way through is the red ⚠ Allow a public repository option: ticking it opens a confirmation that spells out that the list, the redacted configuration and the sources become public and that a pushed commit stays in Git history; the Host additionally rejects the saved setting unless the request carries acknowledgePublic (ACK_REQUIRED). The sync repository must stay private.
  • The token lives in the credential record dsh-plugin-sync/github (the store file is mode 0600); the plugin's own settings file is written 0600 as well. No route returns the token, and the plugin never reads git credentials or the keychain.

Storage vs publishing — the research note

DSH installs a GitHub source as the root package of a repository (installBundle('github:owner/repo#v1.0.0'), checked with git ls-remote before pnpm runs). pnpm has no way to install a subdirectory of a git repository, so a single repository holding plugins/<name>/… cannot be installed directly.

That splits the two ideas:

  • Storage (this build) — one private repository holds the manifest and the sources. It is enough for multi-device sync, and restoring a snapshot is this plugin's job: download, verify, write to ~/.dsh-plugin-sync/plugins/…, install dependencies if the package declares any, then installBundle(<that directory>).
  • Publishing (next version) — one public repository per plugin, with package.json, cordis.patch.yml, a v<version> tag and the dsh topics. After that, any Harness can install it by github:owner/repo#v<version> without this plugin. That is distribution, not synchronization, so it stays a separate explicit action; a marketplace pull request remains manual.

Development

node --check index.js && node --check client.js
node --test test/*.test.js

No build step, no runtime dependencies.

License

MIT — see LICENSE.

—/ 5

No ratings yet

Verified DSH bundle

Commit 770cde78c9e0

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout