Back to home

ZhenHuangLab

dsh-sync

No description

Stars
0
Language
TypeScript
Created
Aug 15, 2026
Updated
Aug 15, 2026

Introduction

dsh-sync

Policy-driven DeepSeek Harness config sync. This is not a thin git pull of $DSH_HOME.

A dual-face DSH plugin:

  • Host — sidecar Git under $DSH_HOME/.dsh-sync/, namespace-projected settings, secret scan, journaled apply, /sync command, fenced HTTP/SSE API
  • Web — theme-independent settings.section ("Git Sync") plus a /sync popupSelect decoration

Pinned to DSH 0.1.0-rc.6.

Install

dsh plugin --profile web add dsh-sync
# optional, if /sync should exist in headless too:
dsh plugin --profile headless add dsh-sync

Then restart dsh web. Open Settings → Git Sync.

From a checkout:

dsh plugin --profile web add /absolute/path/to/dsh-sync

The package declares dsh.bundle.patch so the CLI appends it to dsh.profile.bundles. The client bundle is exports["./client"] (lib/client.js) in the ModuleLoader factory format.

What v1 syncs

ClassItemsApply
Safe / liveSelected settings namespaces as payload/settings.namespaces.yaml (user layer, secrets and local-only paths stripped)ctx.settings.mutate with expectedRevision
Restartprofiles/{web,headless}/package.jsonExplicit only; shows install/restart follow-up. Does not run pnpm.
Executableprofile/home cordis.patch.yml, user .agent-presets/*/{preset.yml,agent.cordis.yml,tool-bootstrap.mjs}Explicit risk confirmation naming files + remote

Never: .credentials.yaml, sessions, storages, node_modules, generated cordis.yml, .dsh-sync itself, .env*, shipped/system presets, symlinks, gitlinks.

baseURL is not treated as machine-local unless you add it under local-only paths. Tailscale CPA endpoints are portable on purpose.

A checkout that still looks like my-dsh-config (files at repo root) can set Payload root to “repository root”.

Cloning a remote that has no .gitattributes is supported. Push writes sidecar hygiene files if they are missing and never git adds a path that is neither on disk nor already tracked. Under systemd, plugin-triggered relaunch does not spawn a second process — Restart=always brings the unit back.

Commands

/sync status
/sync check
/sync diff
/sync pull      # safe settings only; executable stays pending
/sync push      # fast-forward only
/sync doctor
/sync recover

Bare /sync in the Web client opens the shared popup (status / check / safe pull / push). Full executable review is on the settings page.

Operator CLI (no compose): dsh-sync status|check|doctor|recover.

Security

  • Allowlist by construction. Deny names in .gitignore are defense in depth only.
  • execFile only. GIT_TERMINAL_PROMPT=0. core.hooksPath is an empty plugin-owned directory.
  • Origin URL must match local config before fetch/push. No force-push. No Git secret in plugin config.
  • HTTPS remotes with userinfo are refused. Scanner findings name path + rule, never the matched value.
  • Web API is loopback + same-origin by default. Mutations require X-Dsh-Sync-Request: 1 and JSON. Non-loopback needs explicit trustedHosts.
  • Applying patch/preset files is a supply-chain action (they may contain !!js). v1 never auto-applies them.

If a secret was already pushed: rotate first, then rewrite history yourself. This plugin will not.

Theme contract

The settings section binds only --dsw-alias-* tokens and official primitives (Button, Input, StateDot, DisclosureRow, DiffBlock, RiskConfirmation). It does not import ui-theme, read data-ds-dark-theme, or use color literals. pnpm run lint:theme enforces that.

Develop

pnpm install
pnpm test
pnpm run lint:theme
pnpm run build

Architecture notes live in docs/.