Preface¶
DSH local deployment lacks cloud storage: sessions are stored locally in $DSH_HOME/sessions/, and you can’t pick them up on a different computer. Copying session files to a colleague results in incorrect attachments and workspace paths, requiring manual format changes. dsh-github-sync solves this problem: it pushes DSH sessions along with referenced attachments to your own private GitHub repository, so you can pull them down on another machine and continue chatting. The prerequisite is that you have a GitHub account and a private repository; it does not provide a server or an account system.
Below is an introduction to the positioning, installation method, and daily usage of this plugin.
What is this¶
One-sentence positioning: A DSH plugin that synchronizes local sessions (and optional workspace folders) to a private GitHub repository for cross-device handoff.
- Name:
dsh-github-sync, Version 0.5.1, MIT License - Category: Memory
- Directory attribution: lifeng4127; Author field in package.json is mount9527, repository field points to mount9527/dsh-github-sync. Both point to the same package name and the same README; the repository search result takes precedence during installation.
- Syncing is purely manual: it does nothing unless clicked; there are no background scheduled tasks.
Core Features¶
| Capability | Description |
|---|---|
| Upload Sessions | Check local sessions to push them up, along with referenced images and other attachments; multiple files are combined into a single atomic commit. |
| Download Sessions | Pull from remote, rewrite the cwd in the first frame to the local workspace path, and register it into the project list so you can continue chatting without restarting. |
| Upload Folder | Disabled by default; can be enabled in settings to push <Project>/files/. |
| Download Files | Disabled by default; enabled to write back to the workspace. Conflicts with the same name are saved as copies; path traversal is prevented. |
| Agent Tools | Provides 3 tools triggered via natural language; previews the list before executing. |
Several mechanisms worth noting come from the “Key Implementation” section of the README:
- A session on disk is simply a file
session.v4.jsonl.zstd, so no server is needed. - When rewriting
cwd, only the first frame is rewritten; subsequent frames retain their bytes unchanged. Otherwise, it would break the append-only byte chain, causing prefix merging to fail. - Conflict determination compares only the body after the header frame: if one is a prefix of the other, the lagging side appends; if neither is a prefix, it diverges (fork), and the remote is saved as
*.remote-fork-*. - Prefix determination does not download remote files; it relies on the body length and hash recorded in
manifest.jsonfor local slicing (the example in the README shows a preview of a 2MB session dropping from 134 seconds to 5 seconds).
Repository structure:
README.md Auto-generated project/session index (rewritten on every upload)
manifest.json Authoritative index: Session ID → Path, original cwd, sha, attachment list
<project>/sessions/<id>.jsonl.zstd The session file itself
<project>/attachments/<sha>.<ext> Attachments referenced by the session
<project>/files/<relative_path> Content uploaded from folders
The original cwd in manifest.json is the key to restoring sessions across machines: it will be rewritten to the target machine’s workspace path during download.
Installation and Activation¶
Install from the Plugin Market: In DSH’s “Plugins”, search for dsh-github-sync, install it, and enable it.
Install from a Local Directory (for development or personal use): Open DSH → “Plugins” → Add Plugin → Local Directory, select this folder; or have the agent call plugin_manager install_bundle with the target filled with an absolute path; or add "dsh-github-sync": "link:<path>" in the profile’s package.json and add it to dsh.profile.bundles.
After installation, a “GitHub Sync” icon appears in the left sidebar; clicking it opens the panel.
Under China’s network, if the official npm source is very slow, use a mirror. Note that mirror syncs have a delay of a few minutes to a few hours:
npm install dsh-github-sync --registry=https://registry.npmmirror.com
Changes to the client take effect by refreshing the page (F5); changes to the host code require reloading the plugin (restart DSH, or close and re-enable it in the plugin manager). The plugin has no native modules; the same code runs on Windows and macOS.
Typical Usage¶
First Configuration¶
- Create an empty private repository. Create a new repository on GitHub, do not check README, do not check .gitignore, and select Private. The README emphasizes that an empty repository is important: if the repository already has files, the first commit will result in an extra merge.
- Create a fine-grained PAT. Open https://github.com/settings/tokens?type=beta → Generate new token. For “Repository access”, select “Only select repositories” and check only this repository. For “Contents”, give “Read and write” (Metadata Read-only will be automatically included after checking). Set all other permissions to “No access”. The token is only shown once.
- Fill in the panel. Top right ⚙ → Repository address (either
owner/repoor full URL) + Access Token → Test connection. Success displaysConnection OK · owner/repo. If it reports “Repository is public”, go to Settings and change it to Private. 401 is likely because the token wasn’t pasted fully; 404 means the repository name is wrong or the token isn’t authorized for this repo.
When not connected to a repository, a guide bar appears at the top of the panel. Click “Start Configuration” to enter a three-step wizard, each step with a link to the official page. For more detailed step-by-step instructions (screenshot locations, common error checks, token rotation), see TUTORIAL.md in the repository.
Upload and Download¶
The panel is divided into two columns: left is the remote repository, right is the local sessions. All write operations must be previewed first; the preview annotates each file with upload / skip / blocked / copy and the reason, and only executes after confirmation. Lines blocked by blocked 远端更新 have a “Download first” button at the end of the line.
There is a sync badge to the right of each session, derived from comparing the local body hash with the remote index:
| Badge | Meaning |
|---|---|
| Synced | Content matches on both sides |
| Local Update | Local has been updated with more rounds of chat, ready to push |
| Remote Update | Remote has been updated, should be downloaded first |
| Not Uploaded | Has not been uploaded yet |
| Active | Actively being written within the last 5 minutes; what is pushed is a snapshot |
You can also use natural language directly; the plugin calls github_sync_transfer:
"Push this session to GitHub"
"What sessions are on the remote?"
"Pull [some session] from the remote into this workspace"
The former first runs phase=preview to show the list, and after confirmation, runs phase=execute with the same token.
There is a file-level progress bar during transfer (e.g., 12/30 · Uploading file · f12.bin). Clicking the left ▸ expands it to show the status of each file: Waiting / Transferring / Complete / Skipped. Each file that hasn’t started has a “Skip” button; skipping attachments automatically removes the corresponding reference from manifest.json. Cancellation takes effect between files, ref is not updated, and no half-commit is left in the repository. Transfer state is stored in the host process; if you switch away and come back, the progress bar automatically reconnects, and unconfirmed previews are restored.
Delete and Conflict¶
Deletion on both sides is independent: the 🗑 at the end of the local line only deletes the local session and recycles local attachments that are no longer referenced; the 🗑 at the end of the remote line only deletes the session in the repository and recycles remote attachments. Both sides require confirmation twice (valid within 4 seconds).
When both sides have modified the same session, the preview will be marked blocked and offer three choices: “Download first”, “Use local to override remote”, “Use remote to override local”. Choosing an override executes it directly and re-previews.
Suitable Scenarios and Notes¶
Suitable for: Handing off a task between two or more devices, or handing over a session from one machine to a colleague to continue; the premise is that GitHub is reachable and a private repository exists.
Not suitable for: Scenarios requiring automatic cloud backup, multi-device real-time sync, or sessions needing cross-organization permission management—it is purely manual, and each machine merges the remote index before uploading, so records do not overwrite each other, but it also means it does not perform real-time automatic conflict merging.
A few things to note:
- The plugin runs with the permissions of the current DSH process; it can write to
$DSH_HOME, access the current workspace, and access the GitHub API. Before installing, read the source code once to ensure the license (MIT) and dependencies meet your requirements. - By default, it writes to four locations only:
$DSH_HOME/github-sync/,$DSH_HOME/sessions/,$DSH_HOME/attachments/v1/objects/, and the current workspace (folder/file sync is disabled by default; related interfaces return 403 when not enabled). - It only allows private repositories; public repositories are rejected outright. Sensitive files like
.env*,.key,.pem,id_rsa,.npmrc,.netrc, etc., are not uploaded even with double confirmation. - The token is only stored in
$DSH_HOME/.credentials.yamlin the host process; the panel does not echo it. The README states the plugin does not spawn child processes, execute external commands, or access domains other than GitHub; these invariants are statically checked bytools/audit-safety.mjsand must be run before publishing. - The repository will grow and GitHub will not automatically clean up old blobs. Sessions have fixed paths in the repository; historical versions only take up space and don’t affect usage. To actually reclaim space, you need to
git clone --mirrorand then usegit filter-repoto rewrite history, or rebuild an empty repository. As suggested in the README, if the main purpose is handoff, having one repository per project is easier to clean up than mixing them together. - Attachments are uploaded as referenced; unreferenced attachments are not uploaded. If a single attachment exceeds 25MB, it will be skipped and warned about in the preview. The plugin only handles session format
version: 4.
Conclusion¶
The value of dsh-github-sync is clear: it converts DSH local sessions into ordinary files that can be moved across devices, completing the handoff via a private repository and a string of fine-grained tokens, without any extra server. The functionality isn’t extensive, but the fact that it previews before uploading, allows file-level skipping, cancellation doesn’t affect ref, and tokens are only stored in the host process, shows that the author has considered the potential for errors in manual operations quite carefully. If you already use GitHub to manage private repositories and are also accepting of the rhythm of manual sync, you can give it a try.
Directory page: https://www.skillhub.cn/plugins/lifeng4127/dsh-github-sync
GitHub: https://github.com/lifeng4127/dsh-github-sync