dsh-git-branch-manage 1.1.0 → 1.3.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/CHANGELOG.md CHANGED
@@ -2,6 +2,40 @@
2
2
 
3
3
  本文件记录 `dsh-git-branch-manage` 的重要变更。版本号遵循语义化版本。
4
4
 
5
+ ## [1.3.0] - 2026-11-04
6
+
7
+ ### 新增
8
+
9
+ - **网络备用通道与代理配置**(决策 0042、0043):直连发生可重试网络故障时,对 GitHub 地址使用运行时 DoH 查询和真实 Git 请求竞速选择可用 IP;支持显式配置 HTTP(S)/SOCKS 代理,并提供只读系统代理探测建议与「检测网络」诊断。
10
+ - **网络操作状态诊断**(决策 0041):按探测、读取、写入和超时核验分别设置预算;推送前探测远端,推送超时后核验远端 ref,并按失败类型给出建议。
11
+
12
+ ### 变更
13
+
14
+ - **镜像仅作为读取加速**(决策 0044):镜像 fetch/update 失败后自动恢复原始 URL 回直连,再按备用通道链尝试;推送不走镜像,避免将写入交给第三方。
15
+ - SSH 远端不再进行无效的 HTTP 代理/IP 通道探测,错误信息改为提示可执行的 HTTPS 方案。
16
+ - 加速前缀改为拼接完整 GitHub URL(决策 0040),并用真实 Git URL 解析与提取覆盖回归场景。
17
+
18
+ ### 修复
19
+
20
+ - **CI 在非 Windows 上失败**(决策 0046):系统代理探测的用例此前无条件假设 Windows 注册表,在 ubuntu-latest 上必然失败;现按平台分派,Windows 侧保留注册表行为覆盖,非 Windows 侧断言不读取注册表。
21
+
22
+ ## [1.2.0] - 2026-11-02
23
+
24
+ ### 新增
25
+
26
+ - **凭据管理直接读写本机 `~/.git-credentials`**(决策 0037):面板里存一次,任何 shell、任何子进程的 `git push/fetch/pull` 都用同一账号;文件里不属于本插件的行(注释、`ssh://` 等)原样保留,卸载插件后凭据仍属于用户。
27
+ - **从本机导入凭据,启动时默认执行**(决策 0038):按用户 gitconfig 里声明过的 credential 域名逐个探测(`git credential fill`,串行 + 20 秒超时),答出令牌即导入面板;面板另有手动「从本机导入」入口(`POST /credentials-import`),toast 汇总导入/跳过/失败数量。`gh`、GCM、`store` 里的账号一次导入即可接管。
28
+
29
+ ### 变更
30
+
31
+ - 凭据生效通道改用 git 原生机制:`~/.gitconfig` 幂等追加一行 `[include]` 指向生成的 URL 作用域配置(每个已存域名重置 helper 链后装内置 `store`),不再向进程环境注入(废弃 0032/0034 的 `GIT_CONFIG_*` / `GIT_CONFIG_GLOBAL` 通道),令牌也不再进入命令行参数。
32
+ - 未管理域名的机器原有 helper(gh、GCM 等)保持原样,不再被全局清空;未存域名依旧 fail-fast 不弹窗。
33
+ - 令牌明文落在 `~/.git-credentials`(0600)——与 git 自带 `store` helper 一致的安全取舍;面板列表元数据(`method`/`updatedAt`)另存不含 secret 的旁文件。
34
+
35
+ ### 移除
36
+
37
+ - 删除自研 `git-credential-bridge` helper,改用 git 内置 `credential-store`,不再为每次 git 调用拉起 node 进程。
38
+
5
39
  ## [1.1.0] - 2026-11-02
6
40
 
7
41
  ### 修复
package/lib/client.js CHANGED
@@ -65,8 +65,8 @@ window.__ModuleLoader__.load({
65
65
  }
66
66
  /**
67
67
  * Normalize an accepted accelerate URL: trim, and append the trailing `/`
68
- * the prefix concatenation requires (`https://gh-proxy.com/` + `o/r.git`
69
- * must stay one valid URL). Callers pass the result to `url.<base>.insteadOf`.
68
+ * the prefix concatenation requires (`https://gh-proxy.com/` + the complete
69
+ * `https://github.com/o/r.git` URL). accelArgs builds the Git rewrite base.
70
70
  * @param value - an accepted (non-empty, http(s), whitespace-free) URL.
71
71
  * @returns the prefix ending in `/`.
72
72
  */
@@ -74,6 +74,37 @@ window.__ModuleLoader__.load({
74
74
  const trimmed = value.trim();
75
75
  return trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
76
76
  }
77
+ /** Schemes git's libcurl accepts for an HTTP(S) remote. */
78
+ const PROXY_SCHEMES = [
79
+ "http",
80
+ "https",
81
+ "socks5",
82
+ "socks5h"
83
+ ];
84
+ /**
85
+ * Why this proxy value cannot be used, or undefined when it can.
86
+ *
87
+ * Credentials are rejected on purpose: the value is stored in the plugin
88
+ * config and passed as one `-c` argument, so `user:pass@` would be readable
89
+ * in the settings UI and in any diagnostics (0043).
90
+ * @param value - candidate proxy URL.
91
+ * @returns the user-facing reason, or undefined.
92
+ */
93
+ function proxyUrlError(value) {
94
+ let url;
95
+ try {
96
+ url = new URL(value);
97
+ } catch {
98
+ return "must be an absolute proxy URL such as http://127.0.0.1:7890";
99
+ }
100
+ if (!PROXY_SCHEMES.includes(url.protocol.replace(":", ""))) return `scheme must be one of ${PROXY_SCHEMES.join(", ")}`;
101
+ if (url.hostname === "") return "must name a proxy host";
102
+ if (url.port === "") return "must name a proxy port";
103
+ const port = Number(url.port);
104
+ if (!Number.isInteger(port) || port < 1 || port > 65535) return "proxy port must be 1-65535";
105
+ if (url.username !== "" || url.password !== "") return "credentials in the proxy URL are not supported";
106
+ if (url.pathname !== "" && url.pathname !== "/" || url.search !== "" || url.hash !== "") return "must not carry a path, query, or fragment";
107
+ }
77
108
  //#endregion
78
109
  //#region src/wire.ts
79
110
  /**
@@ -131,6 +162,8 @@ window.__ModuleLoader__.load({
131
162
  const ROUTE_TAG = `${ROUTE_PREFIX}/tag`;
132
163
  /** POST ROUTE_PREFIX/tag-push — push ONE existing local tag to its remote. */
133
164
  const ROUTE_TAG_PUSH = `${ROUTE_PREFIX}/tag-push`;
165
+ /** POST ROUTE_PREFIX/network-probe — read the endpoint through every configured channel (diagnostics; no state change). */
166
+ const ROUTE_NETWORK_PROBE = `${ROUTE_PREFIX}/network-probe`;
134
167
  /** POST ROUTE_PREFIX/credentials — the global per-host credentials, secrets
135
168
  * masked (the stored secret never leaves the host). */
136
169
  const ROUTE_CREDENTIALS = `${ROUTE_PREFIX}/credentials`;
@@ -144,6 +177,9 @@ window.__ModuleLoader__.load({
144
177
  /** POST ROUTE_PREFIX/credentials-oauth-poll — poll a started browser sign-in;
145
178
  * stores the token and answers the fresh masked list once the user approves. */
146
179
  const ROUTE_CREDENTIALS_OAUTH_POLL = `${ROUTE_PREFIX}/credentials-oauth-poll`;
180
+ /** POST ROUTE_PREFIX/credentials-import — copy the credentials the machine's
181
+ * own helpers already answer for into the managed store (decision 0038). */
182
+ const ROUTE_CREDENTIALS_IMPORT = `${ROUTE_PREFIX}/credentials-import`;
147
183
  /** POST ROUTE_PREFIX/remotes — the repository's configured remotes. */
148
184
  const ROUTE_REMOTES = `${ROUTE_PREFIX}/remotes`;
149
185
  /** POST ROUTE_PREFIX/remote — add, re-point or remove ONE remote. */
@@ -220,6 +256,15 @@ window.__ModuleLoader__.load({
220
256
  }
221
257
  }
222
258
  /**
259
+ * Probe every network channel (direct, proxy, pinned IP) against the
260
+ * repository's push URL; failures come back per channel rather than as one
261
+ * error, so the popover can show which route works right now (0043).
262
+ * @param repoPath - absolute directory inside the repository.
263
+ */
264
+ function requestNetworkProbe(repoPath) {
265
+ return post(ROUTE_NETWORK_PROBE, { repoPath });
266
+ }
267
+ /**
223
268
  * Create or reuse the worktree for a branch.
224
269
  * @param repoPath - absolute directory inside the repository.
225
270
  * @param branch - branch display name.
@@ -508,6 +553,16 @@ window.__ModuleLoader__.load({
508
553
  return post(ROUTE_CREDENTIALS_OAUTH_POLL, body);
509
554
  }
510
555
  /**
556
+ * Adopt the credentials this machine's own git helpers already answer for
557
+ * (gh, Windows Credential Manager, Keychain) into the plugin's store. Hosts
558
+ * the panel already manages are skipped; a host whose helper had nothing is
559
+ * reported per host, never as a request failure.
560
+ * @param body - `{}` (probe what the gitconfig names) or `{ hosts }`.
561
+ */
562
+ function requestImportCredentials(body = {}) {
563
+ return post(ROUTE_CREDENTIALS_IMPORT, body);
564
+ }
565
+ /**
511
566
  * The repository's configured remotes (fetch/push URLs).
512
567
  * @param repoPath - absolute directory inside the repository.
513
568
  */
@@ -3545,6 +3600,8 @@ window.__ModuleLoader__.load({
3545
3600
  /** The fields this form edits. */
3546
3601
  const ACCEL_ENABLED_FIELD = "cnAccelerate";
3547
3602
  const ACCEL_URL_FIELD = "cnAccelerateUrl";
3603
+ /** The proxy channel's config key (0043); blank = no proxy channel. */
3604
+ const NETWORK_PROXY_FIELD = "networkProxy";
3548
3605
  /**
3549
3606
  * Stages the cn-accelerate edits over the `dsh-git-branch` Host entry.
3550
3607
  *
@@ -3558,6 +3615,7 @@ window.__ModuleLoader__.load({
3558
3615
  listeners = /* @__PURE__ */ new Set();
3559
3616
  enabledDraft;
3560
3617
  urlDraft;
3618
+ proxyDraft;
3561
3619
  saving = false;
3562
3620
  failed = false;
3563
3621
  /**
@@ -3598,6 +3656,11 @@ window.__ModuleLoader__.load({
3598
3656
  this.failed = false;
3599
3657
  this.publish();
3600
3658
  },
3659
+ editNetworkProxy: (text) => {
3660
+ this.proxyDraft = text;
3661
+ this.failed = false;
3662
+ this.publish();
3663
+ },
3601
3664
  restoreAccelDefault: () => {
3602
3665
  this.urlDraft = ACCEL_DEFAULT_URL;
3603
3666
  this.failed = false;
@@ -3605,9 +3668,10 @@ window.__ModuleLoader__.load({
3605
3668
  },
3606
3669
  saveAccel: () => this.save(),
3607
3670
  discardAccel: () => {
3608
- if (this.enabledDraft === void 0 && this.urlDraft === void 0 && !this.failed) return;
3671
+ if (this.enabledDraft === void 0 && this.urlDraft === void 0 && this.proxyDraft === void 0 && !this.failed) return;
3609
3672
  this.enabledDraft = void 0;
3610
3673
  this.urlDraft = void 0;
3674
+ this.proxyDraft = void 0;
3611
3675
  this.failed = false;
3612
3676
  this.publish();
3613
3677
  }
@@ -3619,6 +3683,11 @@ window.__ModuleLoader__.load({
3619
3683
  const value = this.scope.getSnapshot().value?.[ACCEL_URL_FIELD];
3620
3684
  return this.urlDraft ?? value ?? "https://gh-proxy.com/";
3621
3685
  }
3686
+ /** The proxy as the user sees it: the live draft, else the stored value. */
3687
+ effectiveProxyText() {
3688
+ const value = this.scope.getSnapshot().value?.[NETWORK_PROXY_FIELD];
3689
+ return this.proxyDraft ?? (typeof value === "string" ? value : "");
3690
+ }
3622
3691
  /**
3623
3692
  * Write the switch and the normalized URL TOGETHER, then re-seed from what
3624
3693
  * the Host accepted. The URL is validated first — an unusable draft blocks
@@ -3627,11 +3696,14 @@ window.__ModuleLoader__.load({
3627
3696
  * A save that did not land keeps its drafts so the user can correct them.
3628
3697
  */
3629
3698
  async save() {
3630
- if (this.enabledDraft === void 0 && this.urlDraft === void 0 || this.saving) return;
3699
+ if (this.enabledDraft === void 0 && this.urlDraft === void 0 && this.proxyDraft === void 0 || this.saving) return;
3631
3700
  const urlText = this.effectiveUrlText();
3632
3701
  if (accelerateUrlError(urlText) !== void 0) return;
3702
+ const proxyText = this.effectiveProxyText().trim();
3703
+ if (proxyText !== "" && proxyUrlError(proxyText) !== void 0) return;
3633
3704
  const intendedEnabled = this.enabledDraft ?? this.scope.getSnapshot().value?.["cnAccelerate"] ?? false;
3634
3705
  const intendedUrl = normalizeAccelerateUrl(urlText);
3706
+ const writeProxy = this.proxyDraft !== void 0;
3635
3707
  this.saving = true;
3636
3708
  this.failed = false;
3637
3709
  this.publish();
@@ -3639,14 +3711,16 @@ window.__ModuleLoader__.load({
3639
3711
  try {
3640
3712
  await this.scope.set(ACCEL_ENABLED_FIELD, intendedEnabled);
3641
3713
  await this.scope.set(ACCEL_URL_FIELD, intendedUrl);
3714
+ if (writeProxy) await this.scope.set(NETWORK_PROXY_FIELD, proxyText);
3642
3715
  const user = this.userLayer();
3643
- if (user?.["cnAccelerate"] !== intendedEnabled || user?.["cnAccelerateUrl"] !== intendedUrl) landed = false;
3716
+ if (user?.["cnAccelerate"] !== intendedEnabled || user?.["cnAccelerateUrl"] !== intendedUrl || writeProxy && (user?.["networkProxy"] ?? "") !== proxyText) landed = false;
3644
3717
  } catch (_settingsWriteFailure) {
3645
3718
  landed = false;
3646
3719
  }
3647
3720
  if (landed) {
3648
3721
  this.enabledDraft = void 0;
3649
3722
  this.urlDraft = void 0;
3723
+ this.proxyDraft = void 0;
3650
3724
  }
3651
3725
  this.saving = false;
3652
3726
  this.failed = !landed;
@@ -3662,17 +3736,22 @@ window.__ModuleLoader__.load({
3662
3736
  const storedEnabled = snapshot.value?.["cnAccelerate"] ?? false;
3663
3737
  const storedUrl = snapshot.value?.["cnAccelerateUrl"] ?? "https://gh-proxy.com/";
3664
3738
  const urlText = this.effectiveUrlText();
3739
+ const storedProxy = typeof snapshot.value?.["networkProxy"] === "string" ? snapshot.value[NETWORK_PROXY_FIELD] : "";
3740
+ const proxyText = this.effectiveProxyText();
3665
3741
  const enabledDirty = this.enabledDraft !== void 0 && this.enabledDraft !== storedEnabled;
3666
3742
  const urlDirty = this.urlDraft !== void 0 && this.urlDraft !== storedUrl;
3743
+ const proxyDirty = this.proxyDraft !== void 0 && this.proxyDraft !== storedProxy;
3667
3744
  return {
3668
3745
  available: snapshot.status === "ready",
3669
3746
  writable: snapshot.writable,
3670
3747
  enabled: this.enabledDraft ?? storedEnabled,
3671
3748
  urlText,
3672
- dirty: enabledDirty || urlDirty,
3749
+ dirty: enabledDirty || urlDirty || proxyDirty,
3673
3750
  saving: this.saving,
3674
3751
  failed: this.failed,
3675
- urlError: accelerateUrlError(urlText) ?? null
3752
+ urlError: accelerateUrlError(urlText) ?? null,
3753
+ proxyText,
3754
+ proxyError: proxyText.trim() === "" ? null : proxyUrlError(proxyText.trim()) ?? null
3676
3755
  };
3677
3756
  }
3678
3757
  /** Replace the snapshot reference and notify, only when the fact moved. */
@@ -4607,8 +4686,17 @@ window.__ModuleLoader__.load({
4607
4686
  *
4608
4687
  * @module git-worktree/client/panel/CnAcceleratePopover
4609
4688
  */
4689
+ /** Channel id → its display name; the probe rows stay in host order. */
4690
+ const CHANNEL_LABELS = {
4691
+ direct: (t) => t("accel.channel.direct"),
4692
+ proxy: (t) => t("accel.channel.proxy"),
4693
+ ip: (t) => t("accel.channel.ip")
4694
+ };
4610
4695
  function CnAcceleratePopover(props) {
4611
- const { accel, t, pushToast, onClose } = props;
4696
+ const { accel, t, pushToast, cwd, onClose } = props;
4697
+ const [probe, setProbe] = (0, react.useState)(null);
4698
+ const [probing, setProbing] = (0, react.useState)(false);
4699
+ const [probeError, setProbeError] = (0, react.useState)(null);
4612
4700
  const rootRef = (0, react.useRef)(null);
4613
4701
  const state = (0, react.useSyncExternalStore)(accel.store.subscribe, accel.store.getSnapshot);
4614
4702
  (0, react.useEffect)(() => {
@@ -4627,7 +4715,7 @@ window.__ModuleLoader__.load({
4627
4715
  }, [onClose]);
4628
4716
  const locked = state.saving || !state.writable;
4629
4717
  const onSave = (0, react.useCallback)(() => {
4630
- if (state.saving || state.urlError !== null || !state.writable) return;
4718
+ if (state.saving || state.urlError !== null || state.proxyError !== null || !state.writable) return;
4631
4719
  (async () => {
4632
4720
  await accel.saveAccel();
4633
4721
  const after = accel.store.getSnapshot();
@@ -4647,6 +4735,25 @@ window.__ModuleLoader__.load({
4647
4735
  state.writable,
4648
4736
  t
4649
4737
  ]);
4738
+ const onProbe = (0, react.useCallback)(() => {
4739
+ if (cwd === void 0 || probing) return;
4740
+ setProbing(true);
4741
+ setProbeError(null);
4742
+ (async () => {
4743
+ const result = await requestNetworkProbe(cwd);
4744
+ setProbing(false);
4745
+ if (!result.ok) {
4746
+ setProbe(null);
4747
+ setProbeError(result.error);
4748
+ return;
4749
+ }
4750
+ setProbe({
4751
+ url: result.url,
4752
+ channels: result.channels,
4753
+ ...result.systemProxy === void 0 ? {} : { systemProxy: result.systemProxy }
4754
+ });
4755
+ })();
4756
+ }, [cwd, probing]);
4650
4757
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4651
4758
  className: GitHistoryPanel_module_default.cnPop,
4652
4759
  ref: rootRef,
@@ -4702,6 +4809,46 @@ window.__ModuleLoader__.load({
4702
4809
  className: GitHistoryPanel_module_default.cnError,
4703
4810
  children: state.urlError === "empty" ? t("accel.urlEmpty") : t("accel.urlInvalid")
4704
4811
  }) : null,
4812
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4813
+ className: GitHistoryPanel_module_default.cnFieldHead,
4814
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4815
+ className: GitHistoryPanel_module_default.cnFieldLabel,
4816
+ children: t("accel.proxyLabel")
4817
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4818
+ type: "button",
4819
+ className: GitHistoryPanel_module_default.cnLink,
4820
+ disabled: locked || cwd === void 0 || probing,
4821
+ onClick: onProbe,
4822
+ children: probing ? t("accel.probing") : t("accel.probe")
4823
+ })]
4824
+ }),
4825
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4826
+ type: "text",
4827
+ className: state.proxyError !== null ? GitHistoryPanel_module_default.cnInputBad : GitHistoryPanel_module_default.cnInput,
4828
+ value: state.proxyText,
4829
+ spellCheck: false,
4830
+ disabled: locked,
4831
+ placeholder: "http://127.0.0.1:7890",
4832
+ "aria-invalid": state.proxyError !== null,
4833
+ onChange: (event) => accel.editNetworkProxy(event.target.value)
4834
+ }),
4835
+ state.proxyError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4836
+ className: GitHistoryPanel_module_default.cnError,
4837
+ children: t("accel.proxyInvalid")
4838
+ }) : null,
4839
+ probe !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4840
+ className: GitHistoryPanel_module_default.cnHint,
4841
+ children: probe.channels.map((channel) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
4842
+ channel.ok ? "✓ " : "✗ ",
4843
+ CHANNEL_LABELS[channel.channel](t),
4844
+ ` \u00b7 ${String(channel.elapsedMs)} ms`,
4845
+ channel.detail === void 0 ? null : ` \u00b7 ${channel.detail}`
4846
+ ] }, channel.channel))
4847
+ }) : null,
4848
+ probeError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4849
+ className: GitHistoryPanel_module_default.cnError,
4850
+ children: probeError
4851
+ }) : null,
4705
4852
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4706
4853
  className: GitHistoryPanel_module_default.cnFooter,
4707
4854
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -4712,7 +4859,7 @@ window.__ModuleLoader__.load({
4712
4859
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4713
4860
  type: "button",
4714
4861
  className: GitHistoryPanel_module_default.cnSave,
4715
- disabled: state.saving || state.urlError !== null || !state.writable,
4862
+ disabled: state.saving || state.urlError !== null || state.proxyError !== null || !state.writable,
4716
4863
  onClick: onSave,
4717
4864
  children: t("cardSave")
4718
4865
  })]
@@ -4784,6 +4931,10 @@ window.__ModuleLoader__.load({
4784
4931
  const [seed, setSeed] = (0, react.useState)();
4785
4932
  const [formError, setFormError] = (0, react.useState)();
4786
4933
  const [busy, setBusy] = (0, react.useState)(false);
4934
+ /** The one-click machine import runs on its own flag: it is read-only on the
4935
+ * form, so it must not disable editing, and a slow helper (a cold keychain
4936
+ * prompt) must not look like a hung save. */
4937
+ const [importing, setImporting] = (0, react.useState)(false);
4787
4938
  /** Two-step delete: the first click only arms the row. */
4788
4939
  const [confirmHost, setConfirmHost] = (0, react.useState)();
4789
4940
  /** The editor starts collapsed; the "+" icon in the header row opens it,
@@ -5006,6 +5157,34 @@ window.__ModuleLoader__.load({
5006
5157
  seed,
5007
5158
  t
5008
5159
  ]);
5160
+ /** Adopt what this machine's own git helpers already answer for. The host
5161
+ * decides which hosts to probe (the user's gitconfig names them) and skips
5162
+ * the ones the panel already manages, so this is always safe to click. */
5163
+ const importLocal = (0, react.useCallback)(async () => {
5164
+ if (busy || importing) return;
5165
+ setImporting(true);
5166
+ const result = await requestImportCredentials();
5167
+ setImporting(false);
5168
+ if (!result.ok) {
5169
+ pushToast(result.error, "error");
5170
+ return;
5171
+ }
5172
+ setEntries(result.credentials);
5173
+ if (result.imported.length === 0) {
5174
+ pushToast(t("cred.importNone"), result.failed.length === 0 ? "success" : "error");
5175
+ return;
5176
+ }
5177
+ pushToast(t("cred.importDone", {
5178
+ count: result.imported.length,
5179
+ skipped: result.skipped.length,
5180
+ failed: result.failed.length
5181
+ }), "success");
5182
+ }, [
5183
+ busy,
5184
+ importing,
5185
+ pushToast,
5186
+ t
5187
+ ]);
5009
5188
  const title = seed === void 0 ? t("cred.add") : t("cred.edit");
5010
5189
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
5011
5190
  open: true,
@@ -5021,21 +5200,34 @@ window.__ModuleLoader__.load({
5021
5200
  children: [
5022
5201
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5023
5202
  className: GitHistoryPanel_module_default.credTop,
5024
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5025
- className: GitHistoryPanel_module_default.credHint,
5026
- children: t("cred.hint")
5027
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5028
- type: "button",
5029
- className: GitHistoryPanel_module_default.credAdd,
5030
- title: t("cred.add"),
5031
- "aria-label": t("cred.add"),
5032
- "aria-expanded": editorOpen,
5033
- onClick: openNew,
5034
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5035
- "aria-hidden": "true",
5036
- children: "+"
5203
+ children: [
5204
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5205
+ className: GitHistoryPanel_module_default.credHint,
5206
+ children: t("cred.hint")
5207
+ }),
5208
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5209
+ size: "sm",
5210
+ variant: "ghost",
5211
+ disabled: busy || importing,
5212
+ title: t("cred.import"),
5213
+ onClick: () => {
5214
+ importLocal();
5215
+ },
5216
+ children: importing ? t("cred.importing") : t("cred.import")
5217
+ }),
5218
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5219
+ type: "button",
5220
+ className: GitHistoryPanel_module_default.credAdd,
5221
+ title: t("cred.add"),
5222
+ "aria-label": t("cred.add"),
5223
+ "aria-expanded": editorOpen,
5224
+ onClick: openNew,
5225
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5226
+ "aria-hidden": "true",
5227
+ children: "+"
5228
+ })
5037
5229
  })
5038
- })]
5230
+ ]
5039
5231
  }),
5040
5232
  loadError !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5041
5233
  className: GitHistoryPanel_module_default.formError,
@@ -7067,6 +7259,7 @@ window.__ModuleLoader__.load({
7067
7259
  accel,
7068
7260
  t,
7069
7261
  pushToast,
7262
+ cwd,
7070
7263
  onClose: closeAccel
7071
7264
  }) : null]
7072
7265
  }),
@@ -7888,7 +8081,7 @@ window.__ModuleLoader__.load({
7888
8081
  children: t("cardDescription")
7889
8082
  });
7890
8083
  const lockInput = !state.writable;
7891
- const lockActions = !state.dirty && !accelState.dirty || state.saving || accelState.saving || !state.keepWorktreesValid || accelState.urlError !== null;
8084
+ const lockActions = !state.dirty && !accelState.dirty || state.saving || accelState.saving || !state.keepWorktreesValid || accelState.urlError !== null || accelState.proxyError !== null;
7892
8085
  const legacyRootText = storage?.legacyRoot ?? (state.rootDir === "" ? "—" : state.rootDir);
7893
8086
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7894
8087
  className: GitWorktreeCard_module_default.page,
@@ -8112,6 +8305,33 @@ window.__ModuleLoader__.load({
8112
8305
  }) : null
8113
8306
  ]
8114
8307
  }),
8308
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
8309
+ className: `${GitWorktreeCard_module_default.field} ${GitWorktreeCard_module_default.copyFilesRow}`,
8310
+ htmlFor: "git-worktree-card-network-proxy",
8311
+ children: [
8312
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8313
+ className: GitWorktreeCard_module_default.fieldLabel,
8314
+ children: t("accel.proxyLabel")
8315
+ }),
8316
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
8317
+ id: "git-worktree-card-network-proxy",
8318
+ className: GitWorktreeCard_module_default.accelInput,
8319
+ type: "text",
8320
+ spellCheck: false,
8321
+ placeholder: "http://127.0.0.1:7890",
8322
+ disabled: lockInput || accelState.saving,
8323
+ value: accelState.proxyText,
8324
+ onChange: (event) => {
8325
+ props.editNetworkProxy(event.target.value);
8326
+ }
8327
+ }),
8328
+ accelState.proxyError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8329
+ className: GitWorktreeCard_module_default.keepBad,
8330
+ role: "alert",
8331
+ children: t("accel.proxyInvalid")
8332
+ }) : null
8333
+ ]
8334
+ }),
8115
8335
  history.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8116
8336
  className: `${GitWorktreeCard_module_default.field} ${GitWorktreeCard_module_default.historyRow}`,
8117
8337
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -8421,18 +8641,25 @@ window.__ModuleLoader__.load({
8421
8641
  "panel.stUnmerged": "conflict",
8422
8642
  "panel.cnAccelerate": "China acceleration",
8423
8643
  "accel.enable": "Enable acceleration",
8424
- "accel.hint": "Accelerates https github.com remotes for fetch/update only; push is never proxied. The mirror sees repository content — do not use it for private or sensitive repositories.",
8644
+ "accel.hint": "Enter a proxy prefix (e.g. https://gh-proxy.com/); fetch/update appends the complete GitHub URL. Push stays direct. The mirror sees repository content — do not use it for private or sensitive repositories.",
8425
8645
  "accel.urlLabel": "Acceleration URL",
8426
8646
  "accel.restoreDefault": "Restore default",
8427
8647
  "accel.urlInvalid": "Enter an http(s):// URL",
8428
8648
  "accel.urlEmpty": "Enter an acceleration URL",
8649
+ "accel.proxyLabel": "Proxy (optional)",
8650
+ "accel.proxyInvalid": "Enter an http(s)/socks5 proxy URL as host:port, without credentials",
8651
+ "accel.probe": "Test network",
8652
+ "accel.probing": "Testing...",
8653
+ "accel.channel.direct": "Direct",
8654
+ "accel.channel.proxy": "Proxy",
8655
+ "accel.channel.ip": "Pinned IP",
8429
8656
  "accel.saveFailed": "The change did not save. Try again.",
8430
8657
  "accel.savedOn": "China acceleration enabled: {url}",
8431
8658
  "accel.savedOff": "China acceleration disabled",
8432
8659
  "accel.mirrorDownHint": "If the mirror is down, disable acceleration or change the URL",
8433
8660
  "panel.credentials": "Credentials",
8434
8661
  "cred.title": "Git credentials",
8435
- "cred.hint": "Global, one entry per host: a token saved for github.com serves every repository. Stored only on this machine in git-credentials.json.",
8662
+ "cred.hint": "One entry per host, shared by all repos. Stored in ~/.git-credentials, so plain git uses it too.",
8436
8663
  "cred.empty": "No credentials saved yet",
8437
8664
  "cred.hostLabel": "Host",
8438
8665
  "cred.hostPlaceholder": "github.com",
@@ -8447,6 +8674,10 @@ window.__ModuleLoader__.load({
8447
8674
  "cred.methodPassword": "Password",
8448
8675
  "cred.getToken": "Get a token",
8449
8676
  "cred.add": "Add credential",
8677
+ "cred.import": "Import from this machine",
8678
+ "cred.importing": "Importing…",
8679
+ "cred.importDone": "Imported {count} host(s) — skipped {skipped}, failed {failed}",
8680
+ "cred.importNone": "Nothing new to import from this machine",
8450
8681
  "cred.edit": "Edit",
8451
8682
  "cred.save": "Save",
8452
8683
  "cred.saving": "Saving…",
@@ -8707,18 +8938,25 @@ window.__ModuleLoader__.load({
8707
8938
  "panel.stUnmerged": "冲突",
8708
8939
  "panel.cnAccelerate": "国内加速",
8709
8940
  "accel.enable": "启用加速",
8710
- "accel.hint": "仅加速 https 的 github.com 仓库地址,作用于提取/更新;推送不走加速。第三方加速站可看到仓库内容,请勿用于私有/敏感仓库。",
8941
+ "accel.hint": "填写加速站前缀(如 https://gh-proxy.com/),提取/更新时会在其后拼接完整 GitHub 地址;推送不走加速。第三方加速站可看到仓库内容,请勿用于私有/敏感仓库。",
8711
8942
  "accel.urlLabel": "加速地址",
8712
8943
  "accel.restoreDefault": "恢复默认",
8713
8944
  "accel.urlInvalid": "请输入以 http(s):// 开头的地址",
8714
8945
  "accel.urlEmpty": "请输入加速地址",
8946
+ "accel.proxyLabel": "代理(可选)",
8947
+ "accel.proxyInvalid": "请输入 主机:端口 形式的 http(s)/socks5 代理地址,且不带账号密码",
8948
+ "accel.probe": "检测网络",
8949
+ "accel.probing": "检测中...",
8950
+ "accel.channel.direct": "直连",
8951
+ "accel.channel.proxy": "代理",
8952
+ "accel.channel.ip": "固定 IP",
8715
8953
  "accel.saveFailed": "保存未生效,请重试。",
8716
8954
  "accel.savedOn": "已启用国内加速:{url}",
8717
8955
  "accel.savedOff": "已关闭国内加速",
8718
8956
  "accel.mirrorDownHint": "若加速地址不可用,可停用国内加速或更换地址",
8719
8957
  "panel.credentials": "密钥配置",
8720
8958
  "cred.title": "Git 密钥配置",
8721
- "cred.hint": "全局生效:同一域名只需配置一次,该域名的所有仓库共用。密钥只保存在本机 git-credentials.json 中。",
8959
+ "cred.hint": "按域名保存,所有仓库共用;密钥写入本机 ~/.git-credentials,命令行 git 同样生效。",
8722
8960
  "cred.empty": "还没有配置任何密钥",
8723
8961
  "cred.hostLabel": "域名",
8724
8962
  "cred.hostPlaceholder": "github.com",
@@ -8733,6 +8971,10 @@ window.__ModuleLoader__.load({
8733
8971
  "cred.methodPassword": "密码",
8734
8972
  "cred.getToken": "获取令牌",
8735
8973
  "cred.add": "新增密钥",
8974
+ "cred.import": "从本机导入",
8975
+ "cred.importing": "导入中…",
8976
+ "cred.importDone": "已导入 {count} 个域名(跳过 {skipped},失败 {failed})",
8977
+ "cred.importNone": "本机没有新的凭据可导入",
8736
8978
  "cred.edit": "编辑",
8737
8979
  "cred.save": "保存",
8738
8980
  "cred.saving": "保存中…",