source-code-mgmt 1.10.1 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -1,219 +1,275 @@
1
- # source-code-mgmt — DSH Source Code Management Plugin
2
-
3
- > Version: **v1.10.1** | 中文版见 [README.md](README.md)
4
-
5
- > A source-code management plugin for the DSH Web GUI: it bundles「environment check → SSH setup → commit/push/upload code」into one「Code Management」panel with GitHub / Gitee dual-platform support.
6
-
7
- > **Bilingual UI, live**: the panel and host-side messages follow DSH's language setting (Settings → General → Language) — switching between 中文 and English takes effect instantly, no refresh or restart needed.
8
-
9
- > The entry point adapts automatically: when [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) is installed,「Code Management」appears as a new sidebar **Tab** of that sidebar; otherwise a「Code Management」button is registered in the **right-aligned header list beside "Session log"** (same pill style, 8px gap), opening a right-side integrated panel that pushes the main content. Both forms share the same panel UI and no longer use a left-rail bottom button.
10
-
11
- > **UI language:** the panel follows DSH's language setting (Settings → General → Language), live — Chinese (中文) or English, including all host-side messages. No restart needed when you switch.
12
-
13
- ## Features
14
-
15
- Entry points (auto-detected, no manual switching):
16
-
17
- - **dsh-better-sidebar installed:**「Code Management」registers as a new **Tab page** of its sidebar;
18
- - **dsh-better-sidebar not installed:** a「Code Management」button in the **right-aligned header list beside "Session log"** (registered through DSH's `conversation.session.header.utilities` slot), opening a right-side integrated panel that pushes the main content left.
19
-
20
- > Detection is a single in-memory read at activation time (`ctx.get('betterSidebar')`) — zero I/O, zero network, no impact on DSH startup.
21
-
22
- The panel has three steps:
23
-
24
- ### ① Environment Check
25
- - Shows the **OS** (friendly names `Windows` / `macOS` / `Linux`, from the underlying `win32` / `darwin` / `linux` platform ids)
26
- - Detects **Git** and **GitHub CLI** presence and versions (e.g. `git version 2.55.0`, `gh version 2.97.0`)
27
- - Detects whether an **SSH** client is available
28
- - **Missing tools → install guidance + one-click install**: when a tool is missing, the row shows「❌ 未安装」+「复制安装命令」(copy install command) +「安装」(install) — install picks the package manager automatically (winget / built-in features on Windows, brew on macOS, apt/dnf/pacman on Linux, may need admin rights) and re-detects afterwards
29
-
30
- ### ② SSH Key & Connectivity
31
- - **Platform selector**: GitHub (default) / Gitee — decides the SSH config target and connectivity test below
32
- - **Auto-detect ed25519 key**: scans `~/.ssh/*.pub` for existing ed25519 public keys — prefers `id_ed25519`, otherwise the first found (any name, e.g. `github_ed25519`); falls back to `id_ed25519` when none exist. The status line shows the actual key filename, and the SSH config `IdentityFile` uses it too
33
- - One-click **generate ed25519 key** (no passphrase; reuses an existing ed25519 key instead of duplicating)
34
- - One-click **write SSH config** (GitHub: `github.com → ssh.github.com:443`; Gitee: `gitee.com` port 443) — see [Do I need the 443 config?](#do-i-need-the-443-config) below
35
- - **Test connection** `ssh -T git@github.com` (GitHub) or `ssh -T git@gitee.com` (Gitee)
36
- - Shows the public key content for easy copy-upload to the platform
37
- - Detects whether `gh` is logged in and which account
38
-
39
- ### ③ Code Management
40
- - **Follows ②'s platform**: all「detect / create / visibility」logic switches with the platform selector (GitHub via `gh` CLI, Gitee via Gitee OpenAPI)
41
- - **Gitee token** (Gitee mode only): enter a Gitee personal access token (needs `projects` permission) → stored at `~/.dsh/storages/source-code-mgmt-gitee.json` on the machine (0600, **not inside the plugin dir**, never echoed to the browser/logs); one-click clear; invalid tokens are removed automatically
42
- - **Workspace selector**: dropdown of DSH-registered workspace folders
43
- - **Select folder →**: paste an absolute path or click「Browse…」for a native folder picker; confirmed folders are **persisted into a custom folder list** (`~/.dsh/storages/source-code-mgmt-dirs.json`, separate from the plugin dir — no personal paths leak), shown with a custom-folder badge and a ✕ to remove the entry (record only, never deletes the actual folder)
44
- - Shows repo status: platform source, branch, remote, pending change count, ahead/behind remote, >100MB files
45
- - **View details**: when there are changes, a「查看」(view) button opens a dialog listing changed/added/deleted/renamed **files or folders**; tracked changes can be expanded to show an inline **side-by-side diff** (old left / new right, deletions red, additions green); untracked files are listed by name only. When local and remote diverge, a view button on the sync row lists the concrete commits you're ahead/behind
46
- - **Local Git workflow** (does not change the remote-sync logic):
47
- - **Stage / unstage** per file in the changes dialog (distinguishing staged/unstaged by `git status` XY codes), with「已暂存 / 未暂存」markers
48
- - **Commit message input + Commit button** above the repo name (git repos with changes only) — custom message, or auto-generated when left empty
49
- - **Branch「Switch」** button — dialog listing branches, click to `checkout`
50
- - **History button** — dialog listing commits (hash + subject + author + date), each with view (side-by-side diff), **revert**, **cherry-pick** (both with confirmation, as they rewrite history)
51
- - **Remote matching by platform + current account**: ③'s「remote / sync」only counts remotes of the current platform **whose owner equals the currently logged-in account** (GitHub platform = `gh` account, Gitee platform = Gitee token account). So switching to Gitee never reads the GitHub origin; someone else's / another org's repos (e.g. `deepseek-ai/deepseek-harness`) are never treated as "your own remote", and ahead/behind is not computed for them. With no account-owned remote, the panel shows「(无)」and offers the same-name repo check + create-and-push flow
52
- - **Init Git**: shown when the folder is **not** a git repo but a same-name repo already exists remotely — runs `git init` only (+ default identity), **no pull, no push**, you choose the next step
53
- - **Create repo & push**: repo name defaults to the **folder name** (read-only), optional private/public; the button is disabled with a hint when a same-name repo already exists. For a brand-new directory with no commits, it auto-stages an initial commit before creating the repo to avoid "no commits found"
54
- - GitHub: `gh repo create --private|--public --source=. --push`
55
- - Gitee: Gitee OpenAPI `POST /user/repos` to create, then sets the SSH remote `git@gitee.com:<owner>/<name>.git` and `git push` (through ②'s SSH key)
56
-
57
- ### Data loading timing (fetch on open, "refreshing…" indicator)
58
- DSH does **not** sync repos on startup it only prefetches static env/SSH/workspace lists. Opening the plugin, switching workspace/folder, refreshing, and any push/pull/stage/commit operation fetch the latest repo status over the network and show a「⟳ 刷新中…」indicator no stale data (like an old "no changes") when opening/reopening/switching.
59
-
60
- ## Do I need the 443 config?
61
-
62
- Short answer for users outside mainland China / port-22-blocked networks: **usually no.**
63
-
64
- - GitHub's standard SSH endpoint is `git@github.com` on **port 22**, and it works out of the box on almost every network outside mainland China.
65
- - The plugin's「write SSH config」step writes GitHub's **officially supported** port-443 fallback (`Host github.comHostName ssh.github.com, Port 443`). It exists for networks that block port 22 (common in mainland China, plus some corporate/school/campus or otherwise firewalled networks).
66
- - The step is **fully optional and user-initiated** you can simply skip it: generate the key add the public key to GitHub test connection → push. Everything runs over port 22.
67
- - Writing it anyway is harmless (GitHub officially supports SSH over 443; the only edge case is networks that block 443 as well). Gitee is a China-hosted platform, so it's only relevant if you actually use Gitee.
68
-
69
- ## Installation
70
-
71
- > This plugin ships as a **Profile Bundle**: its `package.json` declares `dsh.bundle` (carrying a `cordis.patch.yml` config layer), so `dsh plugin --profile web add` **installs and activates it in one step** — no manual config editing.
72
-
73
- ### Option 1: npm package (recommended)
74
-
75
- Run this from **any directory** (the command locates/initializes the `web` profile itself):
76
-
77
- ```bash
78
- dsh plugin --profile web add source-code-mgmt
79
- ```
80
-
81
- > The command runs `pnpm add` in the web profile directory, then reconciles the plugin layer: because this package declares `dsh.bundle`, it is automatically appended to `dsh.profile.bundles` (see `~/.dsh/profiles/web/package.json`) and registered into the Cordis loader tree — **one command, done**.
82
-
83
- After installing, **fully restart dsh web** (stop the old process — not a page refresh), then **F5** in the browser. The「Code Management」entry appears (sidebar Tab with dsh-better-sidebar, otherwise the header button + right panel).
84
-
85
- ### Option 2: local directory (development / testing)
86
-
87
- **Windows (PowerShell):**
88
- ```powershell
89
- # install local source (link: protocol — a symlink, source edits take effect immediately)
90
- dsh plugin --profile web add link:C:/path/to/source-code-mgmt
91
- dsh web
92
- ```
93
-
94
- **Linux / macOS:**
95
- ```bash
96
- dsh plugin --profile web add link:/home/yourname/path/to/source-code-mgmt
97
- dsh web
98
- ```
99
-
100
- ### Option 3: from GitHub (distribution)
101
-
102
- ```bash
103
- dsh plugin --profile web add git+https://github.com/Zhucy123/source-code-mgmt.git
104
- dsh web
105
- ```
106
-
107
- > A git install **copies** the source into node_modules — to pick up source changes, re-run `dsh plugin --profile web add ...` (unlike `link:`, which is a symlink).
108
-
109
- ### Verifying the install
110
-
111
- After installing and restarting:
112
-
113
- 1. **Dependency written**: `source-code-mgmt` is in `dependencies` of `~/.dsh/profiles/web/package.json`.
114
- 2. **Added to the config layer**: `source-code-mgmt` is in the `dsh.profile.bundles` list of the same file (written automatically by `dsh plugin add` — no manual editing).
115
- 3. **Symlink created (`link:` only)**: `~/.dsh/profiles/web/node_modules/source-code-mgmt` points at your source dir (a Junction on Windows).
116
- 4. **Entry visible after restart**: sidebar「Code Management」Tab with dsh-better-sidebar, otherwise the header button beside "Session log" opening the right panel.
117
-
118
- ### Troubleshooting
119
-
120
- | Symptom | Cause / fix |
121
- |---------|-------------|
122
- | Installed with `dsh plugin add` and restarted, but no button | Most common: the process was **refreshed, not fully restarted**. Stop the old `dsh web` process (it may still hold port 3080) and start it again. |
123
- | Install prints「declares no dsh.bundle」 | The installed version lacks the bundle declaration (old version or a package missing `cordis.patch.yml`). Reinstall/update with version ≥ 1.9.0. |
124
- |「Failed to load plugins」 | The host-side `lib/index.js` failed to boot (usually a dependency resolution issue). Check the startup log and confirm `node_modules` dependencies are complete. |
125
-
126
- ## Usage
127
-
128
- 1. Restart dsh web and refresh the browser.
129
- 2. Click the「**Code Management**」entry (sidebar Tab with dsh-better-sidebar, otherwise the header button beside "Session log").
130
- 3. Confirm Git / GitHub CLI are installed ② generate a key and test connectivity → ③ pick a workspace, then push or create a new repo.
131
-
132
- ## Backend API routes
133
-
134
- | Route | Method | Description |
135
- |-------|--------|-------------|
136
- | `/api/source-code-mgmt/env` | GET | Environment check (git/gh versions) |
137
- | `/api/source-code-mgmt/install-tool` | POST | One-click install of a missing tool (body `tool`: `git`/`gh`/`ssh`, package manager picked per platform) |
138
- | `/api/source-code-mgmt/ssh` | GET | SSH key / config / gh login status |
139
- | `/api/source-code-mgmt/gen-key` | POST | Generate an ed25519 key |
140
- | `/api/source-code-mgmt/write-config` | POST | Write SSH config (body `provider`: `github` default / `gitee`) |
141
- | `/api/source-code-mgmt/ssh-test` | POST | Test SSH connectivity (body `provider`: `github` default / `gitee`) |
142
- | `/api/source-code-mgmt/default-dir` | GET | Current workspace folder |
143
- | `/api/source-code-mgmt/workspaces` | GET | List all workspace folders + custom folder set |
144
- | `/api/source-code-mgmt/pick-dir` | POST | Native folder picker on the host; returns the chosen path |
145
- | `/api/source-code-mgmt/add-workspace` | POST | Validate a folder and persist it to the custom list; returns the merged workspace list |
146
- | `/api/source-code-mgmt/remove-workspace` | POST | Remove a custom-folder dropdown record only (never deletes the folder) |
147
- | `/api/source-code-mgmt/align` | POST | Hard align: `git fetch` + `git reset --hard origin/<branch>` (full reset to remote) |
148
- | `/api/source-code-mgmt/init-git` | POST | `git init` + default identity only — no pull/push |
149
- | `/api/source-code-mgmt/repo-exists` | POST | Check whether a same-name repo exists (body `provider`: `github`/`gitee`) |
150
- | `/api/source-code-mgmt/repo?dir=` | GET | Repo status (query `provider`: `github`/`gitee`) |
151
- | `/api/source-code-mgmt/repo-diff?dir=&path=` | GET | Unified diff text for one changed file, on demand |
152
- | `/api/source-code-mgmt/stage` | POST | Stage changes (body `dir`, `path`; empty `path` = all) |
153
- | `/api/source-code-mgmt/unstage` | POST | Unstage changes (body `dir`, `path`; empty `path` = all) |
154
- | `/api/source-code-mgmt/commit` | POST | Commit with a custom message (body `dir`, `message`, `paths?`) |
155
- | `/api/source-code-mgmt/branches` | POST | List branches (current first) |
156
- | `/api/source-code-mgmt/checkout` | POST | Switch branch (body `dir`, `branch`) |
157
- | `/api/source-code-mgmt/log` | POST | Recent commit history (body `dir`, `count?`; returns hash/subject/author/date) |
158
- | `/api/source-code-mgmt/revert` | POST | Revert a commit (body `dir`, `hash`) |
159
- | `/api/source-code-mgmt/cherrypick` | POST | Cherry-pick a commit (body `dir`, `hash`) |
160
- | `/api/source-code-mgmt/commit-diff` | POST | Full patch of a commit (body `dir`, `hash`) |
161
- | `/api/source-code-mgmt/push` | POST | Commit and push (git operation, platform-agnostic) |
162
- | `/api/source-code-mgmt/push-staged` | POST | Commit only staged content with your message, then push |
163
- | `/api/source-code-mgmt/pull` | POST | `git pull --ff-only` (up-to-date / success / conflict feedback) |
164
- | `/api/source-code-mgmt/merge-push` | POST | Pull and push (`git pull --rebase` + `git push`) |
165
- | `/api/source-code-mgmt/force-push` | POST | Force push (`git push --force`) |
166
- | `/api/source-code-mgmt/force-pull` | POST | Force pull (`git pull --force`) |
167
- | `/api/source-code-mgmt/create` | POST | Create a repo and push (body `provider`; GitHub via `gh repo create`, Gitee via OpenAPI + SSH push) |
168
- | `/api/source-code-mgmt/set-visibility` | POST | Change repo visibility (body `provider`; GitHub via `gh repo edit`, Gitee via `PATCH /repos/{owner}/{repo}`) |
169
- | `/api/source-code-mgmt/gitee-token` | GET/POST | GET: token configured? + account; POST: save (`{token}`) or clear (`{clear:true}`) the Gitee token |
170
-
171
- ## Security
172
-
173
- All routes are **loopback-only** (`sec-fetch-site` + Origin checks) only the local machine's browser can call them; LAN/mobile sources get a 403, same policy as the control panel.
174
-
175
- ## Cross-platform
176
-
177
- - **Windows / Linux / macOS**
178
- - Platform detected via `process.platform`; `~/.ssh` resolved via `homedir()` (Windows: `C:\Users\<user>\.ssh`, Linux/macOS: `/home/<user>/.ssh` or `/Users/<user>/.ssh`)
179
- - SSH config gets 0600 permissions on Linux/macOS
180
- - **Automatic git / gh / ssh / ssh-keygen binary resolution** at startup: ① env overrides → ② PATH lookup (`.exe` added on Windows) (Windows only) Git's bundled dirs (`usr\bin` / `bin`), with the bare command name as a last resort. Git alone is enough even when `ssh` isn't on PATH — no per-machine config
181
- - Optional explicit binary paths via env vars: `DSH_SCM_GIT` / `DSH_SCM_GH` / `DSH_SCM_SSH` / `DSH_SCM_SSH_KEYGEN`
182
- - **SSH transport fix**: Git for Windows' bundled MSYS `ssh.exe` (`usr\bin\ssh.exe`) can fail with `couldn't create signal pipe, Win32 error 5` when spawned from a detached/agent process, breaking `git push`/`git pull`. The plugin injects `GIT_SSH` pointing at a working `ssh` (usually the system OpenSSH `C:\Windows\System32\OpenSSH\ssh.exe`) for git remote operations
183
- - **One-click missing-tool install across platforms**: winget / built-in features (fallback choco/scoop) on Windows, brew on macOS, apt-get / dnf / pacman on Linux (auto `sudo -n`; skipped when already root). The native folder picker is Windows-only; on macOS/Linux paste the path into the input box instead
184
-
185
- ## Development
186
-
187
- ```bash
188
- git clone https://github.com/Zhucy123/source-code-mgmt.git
189
- cd source-code-mgmt
190
- # test in your local DSH (installs to the web profile, auto-activated)
191
- dsh plugin --profile web add link:$(pwd)
192
- ```
193
-
194
- - Changes to `lib/client.js` (browser side) a page refresh is enough
195
- - Changes to `lib/index.js` (host/Node side) restart dsh web
196
-
197
- ## Version history
198
-
199
- ### v1.10.1 (current)
200
- - **Fix: the top-right「代码管理」button lingered even after dsh-better-sidebar was installed.** For the fallback entry (better-sidebar absent) the teardown handler was only wired for the ReactDOM fallback path — the slots path never stored it, so switching to the sidebar-Tab form left the header entry behind. The `slots.inject` disposer is now captured into `entryUnmount`, so switching to a Tab tears the header entry down correctly.
201
- - **Fix: when better-sidebar is absent, the entry only showed inside a conversation and vanished in the new-conversation / no-conversation empty state.** The old entry lived in `conversation.session.header.utilities` (`scope: 'session'`), and the whole session header is hidden via `hideChrome` in the empty state. It is now **always present at the top-right**: with an active session it sits beside「Session log」(the right-aligned session-header utilities); in the new/no-conversation empty state it becomes a fixed top-right button registered in the always-mounted `shell.overlay` slot (shown only when the session is blank or absent, so it never duplicates the header button).
202
- - **Fix: on refresh the「代码管理」button overlapped「Session log」.** `captureSessions()` takes ~1.2s to populate the session list, and before that the button wrongly believed there was no session and lit up early. The overlap button now renders nothing until the session list is captured.
203
- - **Corrected visibility signal:** the fixed button now keys off whether the current session is blank (`sessions.list.getSnapshot().byId[current].blank`) — show only for blank/no-session, hide for an active (non-blank) session — replacing the inaccurate `current === undefined` check.
204
- - **Robustness:** better-sidebar detection now uses a **bounded multi-tick retry** (fast start then slowing, ~44s, stopping on success and cleared on teardown) instead of a single 1.5s retry, covering slow client cold-start so the Tab-switch race no longer misses.
205
-
206
- > All changes are client-side (`lib/client.js`); refresh the page to pick them up. Host `/api` routes and the push/ignore logic are untouched.
207
-
208
- ### v1.10.0 (history)
209
- - **Bilingual UI, live (follows DSH's language setting)**: every piece of panel copy (steps ①②③, buttons, dialogs, status/result messages, confirm dialogs, the sidebar Tab title) plus host-side error/result messages is now driven by a bilingual dictionary. Language comes from DSH's Settings → General → Language and switches **instantly** — the panel re-renders via a `ctx.locale` subscription (the Tab title follows too), no refresh or restart. The host returns messages per-request based on `?lang=` (AsyncLocalStorage-scoped, so concurrent requests never cross languages). The Chinese UI is byte-identical to v1.9.0; English is a complete translation (including >100MB skip reasons, Gitee token hints, git command fallback messages). The `tools/` directory now holds the i18n extract/apply/test scripts for future maintenance.
210
-
211
- ### v1.9.0 (history)
212
- - **Profile Bundle distribution — install = activate**: `dsh.bundle` changed from the bare string `"./lib/index.js"` to the object form `{ "patch": "./cordis.patch.yml" }`, with a new `cordis.patch.yml` (inserts the `source-code-mgmt` row). `dsh plugin --profile web add source-code-mgmt` now appends the package to `dsh.profile.bundles` and registers it into the Cordis loader tree automatically — **no manual `cordis.patch.yml` editing**. The「install ≠ activate」warning and the PowerShell activation script were removed from the README. Behavior is otherwise unchanged (same `lib/index.js` host half + `lib/client.js` browser half).
213
-
214
- ### v1.8.0 and earlier (history)
215
- See the full Chinese changelog in [README.md](README.md#版本历史). Highlights of recent releases: push-staged button (v1.8.0), fetch-on-open with refreshing indicator (v1.7.0), header button + right panel when better-sidebar is absent (v1.6.0), one-click missing-tool install (v1.5.0), SSH key auto-detection (v1.4.0), local Git workflow — stage/unstage, custom commit message, branch switch, history with revert/cherry-pick, side-by-side diff (v1.3.0), adaptive entry + Gitee support (v1.1–1.2), first release (v1.0.0).
216
-
217
- ## License
218
-
219
- MIT
1
+ # source-code-mgmt — DSH Source Code Management Plugin
2
+
3
+ > Version: **v1.13.0** | 中文版见 [README.md](README.md)
4
+
5
+ > A source-code management plugin for the DSH Web GUI: it bundles「environment check → SSH setup → commit/push/upload code」into one「Code Management」panel with GitHub / Gitee dual-platform support.
6
+
7
+ > **Bilingual UI, live**: the panel and host-side messages follow DSH's language setting (Settings → General → Language) — switching between 中文 and English takes effect instantly, no refresh or restart needed.
8
+
9
+ > The entry point adapts automatically: when [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) is installed,「Code Management」appears as a new sidebar **Tab** of that sidebar; otherwise a「Code Management」button is registered in the **right-aligned header list beside "Session log"** (same pill style, 8px gap), opening a right-side integrated panel that pushes the main content. Both forms share the same panel UI and no longer use a left-rail bottom button.
10
+
11
+ > **UI language:** the panel follows DSH's language setting (Settings → General → Language), live — Chinese (中文) or English, including all host-side messages. No restart needed when you switch.
12
+
13
+ ## Features
14
+
15
+ Entry points (auto-detected, no manual switching):
16
+
17
+ - **dsh-better-sidebar installed:**「Code Management」registers as a new **Tab page** of its sidebar;
18
+ - **dsh-better-sidebar not installed:** a「Code Management」button in the **right-aligned header list beside "Session log"** (registered through DSH's `conversation.session.header.utilities` slot), opening a right-side integrated panel that pushes the main content left.
19
+
20
+ > Detection is a single in-memory read at activation time (`ctx.get('betterSidebar')`) — zero I/O, zero network, no impact on DSH startup.
21
+
22
+ The panel has six steps (①②③ are the core three; ④⑤⑥ are the newer extensions, collapsed by default):
23
+
24
+ ### ① Environment Check
25
+ - Shows the **OS** (friendly names `Windows` / `macOS` / `Linux`, from the underlying `win32` / `darwin` / `linux` platform ids)
26
+ - Detects **Git** and **GitHub CLI** presence and versions (e.g. `git version 2.55.0`, `gh version 2.97.0`)
27
+ - Detects whether an **SSH** client is available
28
+ - **Missing tools → install guidance + one-click install**: when a tool is missing, the row shows「❌ 未安装」+「复制安装命令」(copy install command) +「安装」(install) — install picks the package manager automatically (winget / built-in features on Windows, brew on macOS, apt/dnf/pacman on Linux, may need admin rights) and re-detects afterwards
29
+
30
+ ### ② SSH Key & Connectivity
31
+ - **Platform selector**: GitHub (default) / Gitee — decides the SSH config target and connectivity test below
32
+ - **Auto-detect ed25519 key**: scans `~/.ssh/*.pub` for existing ed25519 public keys — prefers `id_ed25519`, otherwise the first found (any name, e.g. `github_ed25519`); falls back to `id_ed25519` when none exist. The status line shows the actual key filename, and the SSH config `IdentityFile` uses it too
33
+ - One-click **generate ed25519 key** (no passphrase; reuses an existing ed25519 key instead of duplicating)
34
+ - One-click **write SSH config** (GitHub: `github.com → ssh.github.com:443`; Gitee: `gitee.com` port 443) — see [Do I need the 443 config?](#do-i-need-the-443-config) below
35
+ - **Test connection** `ssh -T git@github.com` (GitHub) or `ssh -T git@gitee.com` (Gitee)
36
+ - Shows the public key content for easy copy-upload to the platform
37
+ - Detects whether `gh` is logged in and which account
38
+
39
+ ### ③ Code Management
40
+ - **Follows ②'s platform**: all「detect / create / visibility」logic switches with the platform selector (GitHub via `gh` CLI, Gitee via Gitee OpenAPI)
41
+ - **Gitee token** (Gitee mode only): enter a Gitee personal access token (needs `projects` permission) → stored at `~/.dsh/storages/source-code-mgmt-gitee.json` on the machine (0600, **not inside the plugin dir**, never echoed to the browser/logs); one-click clear; invalid tokens are removed automatically
42
+ - **Workspace selector**: dropdown of DSH-registered workspace folders
43
+ - **Select folder →**: paste an absolute path or click「Browse…」for a native folder picker; confirmed folders are **persisted into a custom folder list** (`~/.dsh/storages/source-code-mgmt-dirs.json`, separate from the plugin dir — no personal paths leak), shown with a custom-folder badge and a ✕ to remove the entry (record only, never deletes the actual folder)
44
+ - Shows repo status: platform source, branch, remote, pending change count, ahead/behind remote, >100MB files
45
+ - **View details**: when there are changes, a「查看」(view) button opens a dialog listing changed/added/deleted/renamed **files or folders**. Every **text-previewable** file — including new/untracked files — can be expanded to show an inline **side-by-side diff** (old left / new right, deletions red, additions green); files with no old version (pure additions) show only the **「新版本」column**; **binary files show no「查看」button** (their content can't be previewed as text). When local and remote diverge, a view button on the sync row lists the concrete commits you're ahead/behind
46
+ - **Local Git workflow** (does not change the remote-sync logic):
47
+ - **Stage / unstage** per file in the changes dialog (distinguishing staged/unstaged by `git status` XY codes), with「已暂存 / 未暂存」markers
48
+ - **Commit message input + Commit button** above the repo name (git repos with changes only) — custom message, or auto-generated when left empty
49
+ - **Branch「Switch」** button — dialog listing branches, click to `checkout`
50
+ - **History button** — dialog listing commits (hash + subject + author + date), each with view (side-by-side diff), **revert**, **cherry-pick** (both with confirmation, as they rewrite history)
51
+ - **Remote matching by platform + current account**: ③'s「remote / sync」only counts remotes of the current platform **whose owner equals the currently logged-in account** (GitHub platform = `gh` account, Gitee platform = Gitee token account). So switching to Gitee never reads the GitHub origin; someone else's / another org's repos (e.g. `deepseek-ai/deepseek-harness`) are never treated as "your own remote", and ahead/behind is not computed for them. With no account-owned remote, the panel shows「(无)」and offers the same-name repo check + create-and-push flow
52
+ - **Init Git**: shown when the folder is **not** a git repo but a same-name repo already exists remotely — runs `git init` only (+ default identity), **no pull, no push**, you choose the next step
53
+ - **Create repo & push**: repo name defaults to the **folder name** (read-only), optional private/public; the button is disabled with a hint when a same-name repo already exists. For a brand-new directory with no commits, it auto-stages an initial commit before creating the repo to avoid "no commits found"
54
+ - GitHub: `gh repo create --private|--public --source=. --push`
55
+ - Gitee: Gitee OpenAPI `POST /user/repos` to create, then sets the SSH remote `git@gitee.com:<owner>/<name>.git` and `git push` (through ②'s SSH key)
56
+
57
+ ### Clone repos
58
+ - Follows ②'s platform: lists **every remote repo of the signed-in account** (GitHub via `gh repo list`, Gitee via OpenAPI `user/repos`) and flags which already exist locally (default = default workspace + registered workspaces + custom dirs)
59
+ - **Clone into**: optional target parent directory (default = DSH's default workspace; leave empty to clone to the default location); cloning uses the SSH URL and auto-adds the clone to the custom-directory list so ③ can select it directly
60
+ - Repos already present locally can't be re-cloned; every other repo has a「克隆」(clone) button
61
+
62
+ ### Pull Request
63
+ - **Target repo recorded locally**: paste the repo URL you want to PR to (`https://github.com/owner/repo`, `owner/repo`, …) → saved to `~/.dsh/storages/source-code-mgmt-pr-targets.json` (**not** the plugin directory); saved repos appear as removable chips and can be re-selected
64
+ - **AI PR-rule analysis**: fetches the target repo's README / CONTRIBUTING / PR template / package.json scripts and has the **DSH default model** figure out how to PR to that repo (e.g. awesome-dsh-plugin: create `data/plugins/<owner>__<repo>.yml`, regenerate the README, satisfy the `dsh.bundle`/commit-count/topic gates), producing a structured **step rule cached in the plugin directory `rules/<owner>__<repo>.json`** — the next PR to the same repo runs straight from cache, **no repeated AI spend**;「Re-analyze (ignore cache)」forces a fresh pass
65
+ - **AI PR-content generation**: fill in the plugin info (owner/repo, category, en/zh description)AI builds the title + description + entry-file content from the rule template **fully editable, never submitted directly**
66
+ - **Run PR**: executes the rule steps `fork clone fork add upstream fetchcreate branch write entry(regenerate README)commit → push create PR` (GitHub via `gh pr create`, Gitee via OpenAPI) with per-step logs and the PR link; working dir defaults to the default workspace
67
+
68
+ ### ⑥ Publish npm package
69
+ - Five-step wizard (run in the target directory): **① check registry** `npm config get registry` → **② view auth config** `npm config list` (auto-redacts token/auth/password) → **③ verify identity** `npm whoami` → **④ preview packed files** `npm pack --dry-run` (see exactly what would be published, nothing is packed or uploaded) → **⑤ publish** `npm publish`
70
+ - Publishing requires ticking「I've reviewed the above — confirm publishing to the npm registry」first, preventing accidental publishes
71
+
72
+ ### Data loading timing (fetch on open, "refreshing…" indicator)
73
+ DSH does **not** sync repos on startup — it only prefetches static env/SSH/workspace lists. Opening the plugin, switching workspace/folder, refreshing, and any push/pull/stage/commit operation fetch the latest repo status over the network and show a「⟳ 刷新中…」indicator — no stale data (like an old "no changes") when opening/reopening/switching.
74
+
75
+ ## Do I need the 443 config?
76
+
77
+ Short answer for users outside mainland China / port-22-blocked networks: **usually no.**
78
+
79
+ - GitHub's standard SSH endpoint is `git@github.com` on **port 22**, and it works out of the box on almost every network outside mainland China.
80
+ - The plugin's「write SSH config」step writes GitHub's **officially supported** port-443 fallback (`Host github.com → HostName ssh.github.com, Port 443`). It exists for networks that block port 22 (common in mainland China, plus some corporate/school/campus or otherwise firewalled networks).
81
+ - The step is **fully optional and user-initiated** you can simply skip it: generate the key add the public key to GitHub test connection push. Everything runs over port 22.
82
+ - Writing it anyway is harmless (GitHub officially supports SSH over 443; the only edge case is networks that block 443 as well). Gitee is a China-hosted platform, so it's only relevant if you actually use Gitee.
83
+
84
+ ## Installation
85
+
86
+ > This plugin ships as a **Profile Bundle**: its `package.json` declares `dsh.bundle` (carrying a `cordis.patch.yml` config layer), so `dsh plugin --profile web add` **installs and activates it in one step** — no manual config editing.
87
+
88
+ ### Option 1: npm package (recommended)
89
+
90
+ Run this from **any directory** (the command locates/initializes the `web` profile itself):
91
+
92
+ ```bash
93
+ dsh plugin --profile web add source-code-mgmt
94
+ ```
95
+
96
+ > The command runs `pnpm add` in the web profile directory, then reconciles the plugin layer: because this package declares `dsh.bundle`, it is automatically appended to `dsh.profile.bundles` (see `~/.dsh/profiles/web/package.json`) and registered into the Cordis loader tree — **one command, done**.
97
+
98
+ After installing, **fully restart dsh web** (stop the old process — not a page refresh), then **F5** in the browser. The「Code Management」entry appears (sidebar Tab with dsh-better-sidebar, otherwise the header button + right panel).
99
+
100
+ ### Option 2: local directory (development / testing)
101
+
102
+ **Windows (PowerShell):**
103
+ ```powershell
104
+ # install local source (link: protocol — a symlink, source edits take effect immediately)
105
+ dsh plugin --profile web add link:C:/path/to/source-code-mgmt
106
+ dsh web
107
+ ```
108
+
109
+ **Linux / macOS:**
110
+ ```bash
111
+ dsh plugin --profile web add link:/home/yourname/path/to/source-code-mgmt
112
+ dsh web
113
+ ```
114
+
115
+ ### Option 3: from GitHub (distribution)
116
+
117
+ ```bash
118
+ dsh plugin --profile web add git+https://github.com/Zhucy123/source-code-mgmt.git
119
+ dsh web
120
+ ```
121
+
122
+ > A git install **copies** the source into node_modules to pick up source changes, re-run `dsh plugin --profile web add ...` (unlike `link:`, which is a symlink).
123
+
124
+ ### Verifying the install
125
+
126
+ After installing and restarting:
127
+
128
+ 1. **Dependency written**: `source-code-mgmt` is in `dependencies` of `~/.dsh/profiles/web/package.json`.
129
+ 2. **Added to the config layer**: `source-code-mgmt` is in the `dsh.profile.bundles` list of the same file (written automatically by `dsh plugin add` — no manual editing).
130
+ 3. **Symlink created (`link:` only)**: `~/.dsh/profiles/web/node_modules/source-code-mgmt` points at your source dir (a Junction on Windows).
131
+ 4. **Entry visible after restart**: sidebar「Code Management」Tab with dsh-better-sidebar, otherwise the header button beside "Session log" opening the right panel.
132
+
133
+ ### Troubleshooting
134
+
135
+ | Symptom | Cause / fix |
136
+ |---------|-------------|
137
+ | Installed with `dsh plugin add` and restarted, but no button | Most common: the process was **refreshed, not fully restarted**. Stop the old `dsh web` process (it may still hold port 3080) and start it again. |
138
+ | Install prints「declares no dsh.bundle」 | The installed version lacks the bundle declaration (old version or a package missing `cordis.patch.yml`). Reinstall/update with version 1.9.0. |
139
+ |「Failed to load plugins」 | The host-side `lib/index.js` failed to boot (usually a dependency resolution issue). Check the startup log and confirm `node_modules` dependencies are complete. |
140
+
141
+ ## Usage
142
+
143
+ 1. Restart dsh web and refresh the browser.
144
+ 2. Click the「**Code Management**」entry (sidebar Tab with dsh-better-sidebar, otherwise the header button beside "Session log").
145
+ 3. ① Confirm Git / GitHub CLI are installed → ② generate a key and test connectivity pick a workspace, then push or create a new repo.
146
+
147
+ ## Backend API routes
148
+
149
+ | Route | Method | Description |
150
+ |-------|--------|-------------|
151
+ | `/api/source-code-mgmt/env` | GET | Environment check (git/gh versions) |
152
+ | `/api/source-code-mgmt/install-tool` | POST | One-click install of a missing tool (body `tool`: `git`/`gh`/`ssh`, package manager picked per platform) |
153
+ | `/api/source-code-mgmt/ssh` | GET | SSH key / config / gh login status |
154
+ | `/api/source-code-mgmt/gen-key` | POST | Generate an ed25519 key |
155
+ | `/api/source-code-mgmt/write-config` | POST | Write SSH config (body `provider`: `github` default / `gitee`) |
156
+ | `/api/source-code-mgmt/ssh-test` | POST | Test SSH connectivity (body `provider`: `github` default / `gitee`) |
157
+ | `/api/source-code-mgmt/default-dir` | GET | Current workspace folder |
158
+ | `/api/source-code-mgmt/workspaces` | GET | List all workspace folders + custom folder set |
159
+ | `/api/source-code-mgmt/pick-dir` | POST | Native folder picker on the host; returns the chosen path |
160
+ | `/api/source-code-mgmt/add-workspace` | POST | Validate a folder and persist it to the custom list; returns the merged workspace list |
161
+ | `/api/source-code-mgmt/remove-workspace` | POST | Remove a custom-folder dropdown record only (never deletes the folder) |
162
+ | `/api/source-code-mgmt/align` | POST | Hard align: `git fetch` + `git reset --hard origin/<branch>` (full reset to remote) |
163
+ | `/api/source-code-mgmt/init-git` | POST | `git init` + default identity only no pull/push |
164
+ | `/api/source-code-mgmt/repo-exists` | POST | Check whether a same-name repo exists (body `provider`: `github`/`gitee`) |
165
+ | `/api/source-code-mgmt/repo?dir=` | GET | Repo status (query `provider`: `github`/`gitee`) |
166
+ | `/api/source-code-mgmt/repo-diff?dir=&path=` | GET | Unified diff text for one changed file, on demand |
167
+ | `/api/source-code-mgmt/stage` | POST | Stage changes (body `dir`, `path`; empty `path` = all) |
168
+ | `/api/source-code-mgmt/unstage` | POST | Unstage changes (body `dir`, `path`; empty `path` = all) |
169
+ | `/api/source-code-mgmt/commit` | POST | Commit with a custom message (body `dir`, `message`, `paths?`) |
170
+ | `/api/source-code-mgmt/branches` | POST | List branches (current first) |
171
+ | `/api/source-code-mgmt/checkout` | POST | Switch branch (body `dir`, `branch`) |
172
+ | `/api/source-code-mgmt/log` | POST | Recent commit history (body `dir`, `count?`; returns hash/subject/author/date) |
173
+ | `/api/source-code-mgmt/revert` | POST | Revert a commit (body `dir`, `hash`) |
174
+ | `/api/source-code-mgmt/cherrypick` | POST | Cherry-pick a commit (body `dir`, `hash`) |
175
+ | `/api/source-code-mgmt/commit-diff` | POST | Full patch of a commit (body `dir`, `hash`) |
176
+ | `/api/source-code-mgmt/push` | POST | Commit and push (git operation, platform-agnostic) |
177
+ | `/api/source-code-mgmt/push-staged` | POST | Commit only staged content with your message, then push |
178
+ | `/api/source-code-mgmt/pull` | POST | `git pull --ff-only` (up-to-date / success / conflict feedback) |
179
+ | `/api/source-code-mgmt/merge-push` | POST | Pull and push (`git pull --rebase` + `git push`) |
180
+ | `/api/source-code-mgmt/force-push` | POST | Force push (`git push --force`) |
181
+ | `/api/source-code-mgmt/force-pull` | POST | Force pull (`git pull --force`) |
182
+ | `/api/source-code-mgmt/create` | POST | Create a repo and push (body `provider`; GitHub via `gh repo create`, Gitee via OpenAPI + SSH push) |
183
+ | `/api/source-code-mgmt/set-visibility` | POST | Change repo visibility (body `provider`; GitHub via `gh repo edit`, Gitee via `PATCH /repos/{owner}/{repo}`) |
184
+ | `/api/source-code-mgmt/gitee-token` | GET/POST | GET: token configured? + account; POST: save (`{token}`) or clear (`{clear:true}`) the Gitee token |
185
+
186
+ ## Security
187
+
188
+ All routes are **loopback-only** (`sec-fetch-site` + Origin checks) — only the local machine's browser can call them; LAN/mobile sources get a 403, same policy as the control panel.
189
+
190
+ ## Cross-platform
191
+
192
+ - **Windows / Linux / macOS**
193
+ - Platform detected via `process.platform`; `~/.ssh` resolved via `homedir()` (Windows: `C:\Users\<user>\.ssh`, Linux/macOS: `/home/<user>/.ssh` or `/Users/<user>/.ssh`)
194
+ - SSH config gets 0600 permissions on Linux/macOS
195
+ - **Automatic git / gh / ssh / ssh-keygen binary resolution** at startup: ① env overrides → ② PATH lookup (`.exe` added on Windows) → ③ (Windows only) Git's bundled dirs (`usr\bin` / `bin`), with the bare command name as a last resort. Git alone is enough even when `ssh` isn't on PATH — no per-machine config
196
+ - Optional explicit binary paths via env vars: `DSH_SCM_GIT` / `DSH_SCM_GH` / `DSH_SCM_SSH` / `DSH_SCM_SSH_KEYGEN`
197
+ - **SSH transport fix**: Git for Windows' bundled MSYS `ssh.exe` (`usr\bin\ssh.exe`) can fail with `couldn't create signal pipe, Win32 error 5` when spawned from a detached/agent process, breaking `git push`/`git pull`. The plugin injects `GIT_SSH` pointing at a working `ssh` (usually the system OpenSSH `C:\Windows\System32\OpenSSH\ssh.exe`) for git remote operations
198
+ - **Non-ASCII filename compatibility**: git's default `core.quotepath` prints paths with non-ASCII bytes as octal-escaped quoted strings (which display as garbled text). The plugin injects `-c core.quotepath=false` into every git invocation (raw UTF-8 output) and additionally unescapes any still-quoted paths via `parseGitPath()` — Chinese filenames display correctly in the changes list and in diff headers
199
+ - **One-click missing-tool install across platforms**: winget / built-in features (fallback choco/scoop) on Windows, brew on macOS, apt-get / dnf / pacman on Linux (auto `sudo -n`; skipped when already root). The native folder picker is Windows-only; on macOS/Linux paste the path into the input box instead
200
+
201
+ ## Development
202
+
203
+ ```bash
204
+ git clone https://github.com/Zhucy123/source-code-mgmt.git
205
+ cd source-code-mgmt
206
+ # test in your local DSH (installs to the web profile, auto-activated)
207
+ dsh plugin --profile web add link:$(pwd)
208
+ ```
209
+
210
+ - Changes to `lib/client.js` (browser side) → a page refresh is enough
211
+ - Changes to `lib/index.js` (host/Node side) → restart dsh web
212
+
213
+ ## Version history
214
+
215
+ ### v1.13.0 (current)
216
+ Second feedback pass (④⑤⑥ interaction tightening):
217
+
218
+ - **④ "Already local" detection widened to registered dirs**: previously the clone list only checked under the currently-selected clone dir; if you switched the clone target elsewhere (e.g. the `workspace` dir) where a repo already existed, it still showed "cloneable". Now a new `localRepoExistsAnywhere()` checks the selected dir **plus every workspace / favorite dir registered by step ③** (from `~/.dsh/storages/workspace.json` + `source-code-mgmt-dirs.json`) — a repo present in any registered location is marked "Already local". Since clone success already records the destination into favorite dirs, "this location already has the repo" knowledge persists automatically and the list hits on it next time.
219
+ - **④ New "clone any repo by URL into any location"**: the clone section gained a bottom row — paste any git URL (HTTPS or SSH; GitHub / Gitee / self-hosted), choose a destination dir, and the repo name is derived from the last URL segment (`.git` stripped) and sent to `POST /clone/run`. Reuses the "Choose directory…" button (absolute path or native folder picker, remembered into favorite dirs). The destination defaults to the top dropdown selection; a hint appears if none is chosen.
220
+ - **⑥ npm: target dir now defaults empty**: previously it pre-filled with the default workspace, risking publishing to an unexpected dir. Now the target dir **starts empty**, with a hint "left empty on purpose — it will NOT auto-use the default dir"; "Re-check / login / Run" buttons are disabled and guarded until the user picks a directory (via the step-③/④ "Choose directory…" button).
221
+ - **⑤ narrowed to "submit an awesome-dsh-plugin listing PR"**: keeping just the most-used path first — hidden the "target repo URL" input, the "record locally / recorded" row & chips, the "Analyze PR rules / Re-analyze (ignore cache)" buttons and the manual-rules panel, and the generic-PR-flow fallback box. The target repo is locked to `https://github.com/awesome-dsh-plugin/awesome-dsh-plugin`; on mount it loads that repo's **preset listing rules** once (no AI spent) with a loading state. The lower section stays: plugin info (name / category / description en/zh) → "Generate PR content (AI)" (preset-template fill without AI when complete, still editable) → editable title / description / entry-file content + workdir → "Run PR" (step-by-step logs + PR link). Host routes (`/pr/analyze`, `/pr/targets`, …) and the preset-rule file are unchanged — only the front-end narrowed.
222
+
223
+ ### v1.12.0 (history)
224
+ Three new panel sections (④ Clone / ⑤ Pull Request / ⑥ npm publish):
225
+
226
+ - **④ Clone repos**: lists every remote repo of the signed-in account (GitHub via `gh repo list`, Gitee via OpenAPI `user/repos`) and flags which already exist locally (default = default workspace + registered workspaces + custom dirs). **Default target directory = the user's home directory** (`os.homedir()`; `C:\Users\<name>` on Windows, `$HOME` on Linux/macOS) — leave empty to clone there. Every directory row has a 「选择目录…」(Choose directory…) button matching ③ Code Management (type an absolute path or open the native folder picker; confirmed paths are remembered as custom dirs). Cloning uses SSH URLs and adds the clone to the custom-directory list automatically. The "already exists" check also covers "a sibling dir whose basename equals the repo name and contains `.git`". Host routes: `GET /clone/default-dir`, `GET /clone/home`, `GET /clone/repos`, `POST /clone/run`.
227
+ - **⑤ Pull Request**: target repo URLs are recorded to `~/.dsh/storages/source-code-mgmt-pr-targets.json` (**not** the plugin directory). **Entering a URL automatically reads that repo's local rule** (`GET /pr/rule`, read-only cache, no AI spend): a preset/cached rule shows its specific PR flow, otherwise a generic flow is shown with a prompt to 「Analyze PR rules」. A **preset rule is seeded to `rules/awesome-dsh-plugin__awesome-dsh-plugin.json`** — enter `https://github.com/awesome-dsh-plugin/awesome-dsh-plugin` and its exact contribution flow is shown (fork → clone → write entry in `data/plugins/<owner>__<repo>.yml` → `npm ci && node scripts/generate-readme.mjs` to regenerate README → commit → push → PR; the rule lives in the plugin directory). 「Analyze PR rules」fetches the repo's README/CONTRIBUTING/PR template/package.json and has the **DSH default model** figure out how to PR to that repo, then caches the structured step rule to the plugin directory `rules/<owner>__<repo>.json` — next time it runs straight from cache with **no AI spend** (「Re-analyze (ignore cache)」forces a fresh pass). 「Generate PR content」produces title/description/entry-file content from the rule template (preset templates can be filled with **zero AI spend** and remain editable); **it is editable and never submitted directly**. 「Run PR」executes `fork → clone fork → add upstream → fetch → create branch → write entry → regenerate README → commit → push → gh pr create` (Gitee via OpenAPI) with step-by-step logs and a PR link. Host routes: `GET /pr/targets`, `POST /pr/targets`, `POST /pr/rule`, `POST /pr/analyze`, `POST /pr/generate`, `POST /pr/execute`.
228
+ - **⑥ Publish npm package**: a five-step wizard (`npm config get registry` → `npm config list` (token/auth/password redacted) → `npm whoami` → `npm pack --dry-run` → `npm publish`); the publish button needs an explicit confirmation checkbox. **The package directory is switchable** (with the same 「选择目录…」 button). **Not logged in / not configured is called out**: the panel shows the current registry and whoami; when logged out it offers 「Open a terminal to run npm login」 (host spawns a system terminal in the target dir, `POST /npm/login`) and 「Copy login command」; re-check after logging in. `GET /npm/status` also reports whether the directory exists and has a `package.json`, with matching warnings. Host routes: `GET /npm/status`, `POST /npm/step`, `POST /npm/login`.
229
+ - **Host AI plumbing**: `llmComplete()` calls the DSH LLM runtime through `ctx.get('llm')`, resolving the default model from `ctx.get('settings').get('agent-default-model')` (falling back to parsing `~/.dsh/settings.yaml`), assembling `text-delta` chunks into the final text.
230
+ - **Maintainability**: new i18n keys are merged via `Object.assign` into `EN_DICT` / `HOST_EN` without touching the original long dictionary lines; a shared `inputStyle()` helper was added; both halves pass `node --check`.
231
+ - **Adversarial-review fixes (same version)**:
232
+ - **Windows npm path with spaces broke ⑥**: the resolved npm path (e.g. `C:\Program Files\nodejs\npm.cmd`) was truncated to `'C:\Program' is not recognized` under shell mode. `run()` now quotes the executable path when it contains whitespace (path only; args still passed safely as an array); npm registry / whoami / version verified live.
233
+ - **「Re-analyze (ignore cache)」was a no-op**: `/pr/analyze` never forwarded `force` to the analysis flow, so the button always hit the cache. It now passes `body.force` through.
234
+ - **Dotted repo names mis-parsed**: `parseRepoUrl` truncated `owner/my.repo` to `my` (the regex excluded dots). It now allows dots and only strips a trailing `.git`.
235
+ - **Hardened PR rule execution**: rules come from AI/cache and are trusted by default. `safeEntryPath()` blocks entry-file path traversal (`..` / absolute paths) and `safeRegenerateCommand()` only allows a whitelist of build commands with no shell metacharacters — preventing a malicious repo's docs from steering the AI into dangerous auto-executed rules.
236
+ - **「Ensure fork」false-success log fixed**: a failed `gh repo fork` previously still showed ✅; it now reports success only on a real success or an "already exists" result.
237
+ - **Gitee repo list pagination**: `user/repos` defaults to 20 per page, so only the first 20 showed. It now pages at 100 per page (up to 10 pages ≈ 1000 repos, matching GitHub).
238
+ - `tools/smoke-test.mjs` extended to 31 cases (dotted repo names, path traversal, command whitelist, etc.) — all green; both halves pass `node --check`.
239
+ - **Feedback refinements (same version)**:
240
+ - **④ Default clone dir changed to the user's home**: it previously defaulted to the DSH workspace, and a workspace whose folder is itself a repo was wrongly flagged as cloneable. It now defaults to `os.homedir()` and the "already exists" check additionally covers "basename of the base dir == repo name AND contains `.git`".
241
+ - **④⑥ Directory buttons match ③**: both the clone target and the npm package directory offer a 「选择目录…」(Choose directory…) button identical to ③ Code Management — type an absolute path or pop the native folder picker, and confirmed paths are remembered as custom dirs (not limited to inside the workspace).
242
+ - **⑤ Per-repo PR features**: entering a target repo URL automatically runs `GET /pr/rule` (cache only, no AI spend) — a rule shows its specific flow, otherwise a generic flow appears with a prompt to analyze; the awesome-dsh-plugin contribution rule is preset into the plugin `rules/` dir, so entering its official repo URL hits it directly.
243
+ - **⑥ Login/configuration guidance**: when `npm whoami` is not logged in, the panel warns clearly and offers 「Open a terminal to run npm login」 (`POST /npm/login`; Windows spawns `cmd /k`, other platforms fall back to common terminal emulators) or 「Copy login command」 — then just re-check to continue the publish flow.
244
+ - **⑤ Rule analysis accepts manual input**: clicking 「Analyze PR rules」 now expands an input panel — you can type the repo's PR rules yourself, or point to a rules file (local path / URL, e.g. a repo README); leave empty and AI auto-fetches the repo's docs (README/CONTRIBUTING/PR template) to analyze. When content is supplied, a **lean prompt** is used (faithfully organize only the user input, no doc fetching, no inventing — saving tokens), and the result is still cached in a structured form to the plugin `rules/` dir. If the repo already has a local rule, clicking 「Analyze PR rules」 first asks whether to re-analyze (choosing "no" does nothing).
245
+
246
+ ### v1.11.0 (history)
247
+ This release focuses on the changed-files preview experience and Chinese-filename compatibility:
248
+
249
+ - **New/untracked files are now viewable**: an untracked (新增) file row now shows「▸ 查看」and expands to display the file's full content (rendered as an "all added" diff; oversized files show the first 2000 lines with a truncation note, capped at 1 MB so huge files are never slurped into memory); an empty new file shows「(空文件)」.
250
+ - **No「旧版本」column for new files**: the side-by-side diff shows only the「新版本」column when the diff has no deletion rows (pure additions, e.g. new/untracked files); diffs with deletions keep the two-column comparison.
251
+ - **Binary files show no「查看」button**: each changed file is sniffed (NUL-byte heuristic, the same one git uses) to decide whether its content can be previewed as text — binary files (images, executables, …) get no「查看」button (tooltip: "Binary file — text preview unavailable"); files without a working-tree copy (deleted, or staged-then-removed) are judged via `git diff --numstat` (`-\t-` marks binary).
252
+ - **Chinese filename compatibility fix**: git's default `core.quotepath` prints Chinese paths as octal-escaped quoted strings (e.g. `"\346\270\270…md"`), which showed up garbled in the changes list and diff headers. Every git invocation now gets `-c core.quotepath=false` (raw UTF-8 paths), plus a defensive `parseGitPath()` unescaper applied to every path-parsing site — `status --porcelain`, `ls-files -z`, `diff --name-only`, and `git diff`/`git show` headers.
253
+ - New regression test `tools/verify-cn-paths.test.mjs` (Chinese path parsing, text/binary viewability, new-file diff generation, deleted-file detection).
254
+
255
+ ### v1.10.1 (history)
256
+ - **Fix: the top-right「代码管理」button lingered even after dsh-better-sidebar was installed.** For the fallback entry (better-sidebar absent) the teardown handler was only wired for the ReactDOM fallback path — the slots path never stored it, so switching to the sidebar-Tab form left the header entry behind. The `slots.inject` disposer is now captured into `entryUnmount`, so switching to a Tab tears the header entry down correctly.
257
+ - **Fix: when better-sidebar is absent, the entry only showed inside a conversation and vanished in the new-conversation / no-conversation empty state.** The old entry lived in `conversation.session.header.utilities` (`scope: 'session'`), and the whole session header is hidden via `hideChrome` in the empty state. It is now **always present at the top-right**: with an active session it sits beside「Session log」(the right-aligned session-header utilities); in the new/no-conversation empty state it becomes a fixed top-right button registered in the always-mounted `shell.overlay` slot (shown only when the session is blank or absent, so it never duplicates the header button).
258
+ - **Fix: on refresh the「代码管理」button overlapped「Session log」.** `captureSessions()` takes ~1.2s to populate the session list, and before that the button wrongly believed there was no session and lit up early. The overlap button now renders nothing until the session list is captured.
259
+ - **Corrected visibility signal:** the fixed button now keys off whether the current session is blank (`sessions.list.getSnapshot().byId[current].blank`) — show only for blank/no-session, hide for an active (non-blank) session — replacing the inaccurate `current === undefined` check.
260
+ - **Robustness:** better-sidebar detection now uses a **bounded multi-tick retry** (fast start then slowing, ~44s, stopping on success and cleared on teardown) instead of a single 1.5s retry, covering slow client cold-start so the Tab-switch race no longer misses.
261
+
262
+ > All changes are client-side (`lib/client.js`); refresh the page to pick them up. Host `/api` routes and the push/ignore logic are untouched.
263
+
264
+ ### v1.10.0 (history)
265
+ - **Bilingual UI, live (follows DSH's language setting)**: every piece of panel copy (steps ①②③, buttons, dialogs, status/result messages, confirm dialogs, the sidebar Tab title) plus host-side error/result messages is now driven by a bilingual dictionary. Language comes from DSH's Settings → General → Language and switches **instantly** — the panel re-renders via a `ctx.locale` subscription (the Tab title follows too), no refresh or restart. The host returns messages per-request based on `?lang=` (AsyncLocalStorage-scoped, so concurrent requests never cross languages). The Chinese UI is byte-identical to v1.9.0; English is a complete translation (including >100MB skip reasons, Gitee token hints, git command fallback messages). The `tools/` directory now holds the i18n extract/apply/test scripts for future maintenance.
266
+
267
+ ### v1.9.0 (history)
268
+ - **Profile Bundle distribution — install = activate**: `dsh.bundle` changed from the bare string `"./lib/index.js"` to the object form `{ "patch": "./cordis.patch.yml" }`, with a new `cordis.patch.yml` (inserts the `source-code-mgmt` row). `dsh plugin --profile web add source-code-mgmt` now appends the package to `dsh.profile.bundles` and registers it into the Cordis loader tree automatically — **no manual `cordis.patch.yml` editing**. The「install ≠ activate」warning and the PowerShell activation script were removed from the README. Behavior is otherwise unchanged (same `lib/index.js` host half + `lib/client.js` browser half).
269
+
270
+ ### v1.8.0 and earlier (history)
271
+ See the full Chinese changelog in [README.md](README.md#版本历史). Highlights of recent releases: push-staged button (v1.8.0), fetch-on-open with refreshing indicator (v1.7.0), header button + right panel when better-sidebar is absent (v1.6.0), one-click missing-tool install (v1.5.0), SSH key auto-detection (v1.4.0), local Git workflow — stage/unstage, custom commit message, branch switch, history with revert/cherry-pick, side-by-side diff (v1.3.0), adaptive entry + Gitee support (v1.1–1.2), first release (v1.0.0).
272
+
273
+ ## License
274
+
275
+ MIT