@hwfee/dsh-git-bash 0.0.0-stage → 0.1.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.md CHANGED
@@ -1,3 +1,65 @@
1
- # Temporary Holding Version
1
+ # dsh-git-bash · Git Bash 默认 shell
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 在 Windows 上把 DeepSeek Harness 的命令解释器从 PowerShell 换成 **Git Bash**。
4
+
5
+ 装好之后:模型跑的 `bash` 命令走 Git Bash,`grep`、`sed`、`find`、`&&`、`$VAR`、shebang 脚本都按 POSIX 语义工作,路径就是你项目所在的那个文件系统。
6
+
7
+ ## 安装
8
+
9
+ ```bash
10
+ dsh plugin --profile desktop add @hwfee/dsh-git-bash
11
+ ```
12
+
13
+ **装完必须彻底退出并重开 DSH。**
14
+
15
+ 卸载:
16
+
17
+ ```bash
18
+ dsh plugin --profile desktop remove @hwfee/dsh-git-bash
19
+ ```
20
+
21
+ ## 它怎么找到 Git Bash
22
+
23
+ 按顺序试四个来源,第一个成功的就算数:
24
+
25
+ 1. **`DSH_GIT_BASH` 环境变量** —— 指向 `bash.exe` 的完整路径。非标准安装位置用这个。
26
+ 2. **`git --exec-path`** —— 得到 `…/Git/mingw64/libexec/git-core`,往上两级就是 Git 根目录,取它自带的 `bin/bash.exe`。
27
+ 3. **常见安装位置** —— `C:\Program Files\Git\bin\bash.exe` 等。
28
+ 4. POSIX 上直接用 PATH 里的 `bash`。
29
+
30
+ **为什么不用 `where bash`。** Windows 上 `bash` 常常解析到 `C:\Windows\System32\bash.exe` —— WSL 的替身。它确实是 bash,但它是**另一个文件系统**(`/mnt/c/...`)、另一个用户、而且没有 Windows 那份 `git`。选错它之后每条命令都以一种看起来像"插件坏了"的方式失败,所以这里宁可找不到也不误选。
31
+
32
+ 找不到时插件**不会消失**:设置里的那一行会显示「没找到 Git Bash」并告诉你怎么指定路径。
33
+
34
+ ## 设置
35
+
36
+ 设置 → 通用设置,多出一行 **默认 shell**:
37
+
38
+ - 上面显示**实际找到的路径**,一眼能看出用的是哪个 bash
39
+ - 下面一个开关 **保留系统 shell 工具**(默认开)—— 关掉后模型的工具列表里只剩 `run_shell`,不再有平台的 PowerShell 工具
40
+
41
+ ## 它会给你什么
42
+
43
+ **`run_shell` 工具。** 参数 `command`(必填)和 `workdir`(可选,绝对路径)。返回运行它的 shell 路径、退出码、stdout 和 stderr。
44
+
45
+ **非零退出不是错误。** `grep` 没匹配到会退出 1,那是一个结果而不是故障,工具照常返回退出码和输出。只有"shell 根本起不来"才算错误。
46
+
47
+ ## 与宿主 shell 的关系
48
+
49
+ 宿主的 shell 是 `ctx.shell` 能力接缝,**每个上下文只能有一个实现**。Windows 上基础 bundle 组装的是 `pwsh-sandbox`(PowerShell),POSIX 上是 `bash-sandbox`。
50
+
51
+ **`run_shell` 尽量走宿主的执行器,并在结果里说明走没走。** 选中的解释器作为命令交给 `ctx.shell`:PowerShell 模式下是 `& '<pwsh.exe>' -NoProfile -NonInteractive -Command '<command>'`,结果显示 `· host`,沙箱、取消、工作目录和进程生命周期都归宿主。**Git Bash 在 Windows 上是个例外**:沙箱里的 pwsh 无法创建 MSYS 进程(`NtCreateDirectoryObject 0xC0000022`,已实测),所以这一模式仍由插件直接启动,结果显示 `· direct` 并附一句「未经过沙箱」。这不是静默降级——每一条结果都会写清楚。代价是本工具**不能**替用户发起沙箱升级审批(那是原生 `pwsh` 工具的职责),被拒绝时会如实报错。
52
+
53
+ **模式的切换会真的到宿主。** 设置行在浏览器进程里,`run_shell` 在宿主进程里;两者之间有一条带同源头围栏的 `POST /dsh-git-bash/mode`(`GET` 可读回当前值)。只发 Cordis 事件是不够的——那个事件跨不过进程边界,宿主会永远停在 `auto`。
54
+
55
+ 因此"让 Git Bash 成为默认"是 **profile 层**的决定,不是运行时劫持:本包的 `cordis.patch.yml` 只插入自己的行;要让 Git Bash 接管,需要在 profile 的 `cordis.patch.yml` 里用 `disabled:` 关掉平台默认的那一行。这样做的后果是**宿主的 PowerShell 执行器不再可用**——如果你需要它,别关那一行,改用 `run_shell` 与 `pwsh` 并存(即保留开关,让两个工具都在)。
56
+
57
+ ## 开发者
58
+
59
+ ```bash
60
+ cd plugins/git-bash
61
+ npm test # 新鲜度 + 清单契约 + 解析 + 工具 + 状态,31 项
62
+ npm run build # 重新生成 client/client.js(产物提交进 git)
63
+ ```
64
+
65
+ 目录职责、改包名的四个位置、发布流程见仓库根 [AGENTS.md](../../AGENTS.md)。
@@ -0,0 +1,504 @@
1
+ /**
2
+ * The browser half of `@hwfee/dsh-git-bash`.
3
+ *
4
+ * It contributes ONE settings row: which shell is in use, and whether the
5
+ * platform's own shell tool stays exposed. No card, no tool view — the shell
6
+ * itself runs on the Host.
7
+ *
8
+ * React comes from the host's frozen module table; it is absent in a Node
9
+ * import (the unit tests load this file for its `name`), so the entry degrades
10
+ * to a no-op apply instead of throwing.
11
+ */
12
+
13
+ window.__ModuleLoader__.load({
14
+ id: "@hwfee/dsh-git-bash",
15
+ factory: (require) => {
16
+ var module = { exports: {} }
17
+ var exports = module.exports
18
+ var __defProp = Object.defineProperty;
19
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
20
+ var __getOwnPropNames = Object.getOwnPropertyNames;
21
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
22
+ var __export = (target, all) => {
23
+ for (var name2 in all)
24
+ __defProp(target, name2, { get: all[name2], enumerable: true });
25
+ };
26
+ var __copyProps = (to, from, except, desc) => {
27
+ if (from && typeof from === "object" || typeof from === "function") {
28
+ for (let key of __getOwnPropNames(from))
29
+ if (!__hasOwnProp.call(to, key) && key !== except)
30
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
31
+ }
32
+ return to;
33
+ };
34
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
35
+
36
+ // client/src/index.js
37
+ var index_exports = {};
38
+ __export(index_exports, {
39
+ apply: () => apply,
40
+ inject: () => inject,
41
+ name: () => name
42
+ });
43
+ module.exports = __toCommonJS(index_exports);
44
+
45
+ // client/src/constants.js
46
+ var PLUGIN_ID = "@hwfee/dsh-git-bash";
47
+ var LOCALE_NS = "dsh-git-bash";
48
+ var SETTINGS_ROW_ID = PLUGIN_ID + "-shell";
49
+
50
+ // client/src/locales.js
51
+ var en = {
52
+ "shell.title": "Default shell",
53
+ "shell.hint": "Which interpreter runs shell commands.",
54
+ "shell.mode": "Shell mode",
55
+ "shell.mode.auto": "Automatic",
56
+ // The user's own wording for the option. Which BUILD ran (7 vs 5.1) is a
57
+ // detail the row does not show; the tool result names it per call.
58
+ "shell.mode.pwsh": "Windows PowerShell",
59
+ "shell.mode.gitbash": "Git Bash",
60
+ "shell.current": "{kind} \xB7 {path}",
61
+ "shell.notFound": "No shell resolved",
62
+ "shell.kind.gitbash": "Git Bash",
63
+ "shell.kind.posix-bash": "bash",
64
+ "shell.kind.pwsh": "PowerShell 7",
65
+ "shell.kind.windows-powershell": "Windows PowerShell 5.1",
66
+ "shell.reason.gitbash-not-found": "Git Bash not found. Install Git for Windows, or set DSH_GIT_BASH.",
67
+ "shell.reason.pwsh-not-found": "PowerShell not found. Install PowerShell 7 or Windows PowerShell.",
68
+ "shell.reason.no-shell-for-platform": "Windows PowerShell is only available on Windows.",
69
+ "shell.reason.no-shell-found": "No usable shell was found. Install Git for Windows, or set DSH_GIT_BASH."
70
+ };
71
+ var zh = {
72
+ "shell.title": "\u9ED8\u8BA4 shell",
73
+ "shell.hint": "\u7528\u54EA\u4E2A\u89E3\u91CA\u5668\u6267\u884C shell \u547D\u4EE4\u3002",
74
+ "shell.mode": "Shell \u6A21\u5F0F",
75
+ "shell.mode.auto": "\u81EA\u52A8",
76
+ "shell.mode.pwsh": "Windows PowerShell",
77
+ "shell.mode.gitbash": "Git Bash",
78
+ "shell.current": "{kind} \xB7 {path}",
79
+ "shell.notFound": "\u6CA1\u6709\u89E3\u6790\u5230 shell",
80
+ "shell.kind.gitbash": "Git Bash",
81
+ "shell.kind.posix-bash": "bash",
82
+ "shell.kind.pwsh": "PowerShell 7",
83
+ "shell.kind.windows-powershell": "Windows PowerShell 5.1",
84
+ "shell.reason.gitbash-not-found": "\u6CA1\u627E\u5230 Git Bash\u3002\u8BF7\u5B89\u88C5 Git for Windows\uFF0C\u6216\u8BBE\u7F6E DSH_GIT_BASH\u3002",
85
+ "shell.reason.pwsh-not-found": "\u6CA1\u627E\u5230 PowerShell\u3002\u8BF7\u5B89\u88C5 PowerShell 7 \u6216 Windows PowerShell\u3002",
86
+ "shell.reason.no-shell-for-platform": "Windows PowerShell \u53EA\u5728 Windows \u4E0A\u53EF\u7528\u3002",
87
+ "shell.reason.no-shell-found": "\u6CA1\u6709\u53EF\u7528\u7684 shell\u3002\u8BF7\u5B89\u88C5 Git for Windows\uFF0C\u6216\u8BBE\u7F6E DSH_GIT_BASH\u3002"
88
+ };
89
+
90
+ // lib/mode.js
91
+ var MODES = ["auto", "pwsh", "gitbash"];
92
+ var DEFAULT_MODE = "auto";
93
+ var MODE_LABELS = {
94
+ auto: "shell.mode.auto",
95
+ pwsh: "shell.mode.pwsh",
96
+ gitbash: "shell.mode.gitbash"
97
+ };
98
+ var KIND_LABELS = {
99
+ gitbash: "shell.kind.gitbash",
100
+ "posix-bash": "shell.kind.posix-bash",
101
+ pwsh: "shell.kind.pwsh",
102
+ "windows-powershell": "shell.kind.windows-powershell"
103
+ };
104
+ var REASON_LABELS = {
105
+ "gitbash-not-found": "shell.reason.gitbash-not-found",
106
+ "pwsh-not-found": "shell.reason.pwsh-not-found",
107
+ "no-shell-for-platform": "shell.reason.no-shell-for-platform",
108
+ "no-shell-found": "shell.reason.no-shell-found"
109
+ };
110
+ var LEGACY_BOOLEAN_KEY = "@hwfee/dsh-git-bash:exposeNativeShell";
111
+ function isMode(value) {
112
+ return typeof value === "string" && MODES.includes(value);
113
+ }
114
+ function normalizeMode(value) {
115
+ return isMode(value) ? value : DEFAULT_MODE;
116
+ }
117
+ function readMode(storage, key) {
118
+ if (storage === null || storage === void 0) return DEFAULT_MODE;
119
+ let raw;
120
+ try {
121
+ raw = storage.getItem(key);
122
+ } catch {
123
+ return DEFAULT_MODE;
124
+ }
125
+ return normalizeMode(raw);
126
+ }
127
+ function writeMode(storage, key, mode) {
128
+ if (storage === null || storage === void 0) return false;
129
+ if (!isMode(mode)) return false;
130
+ try {
131
+ storage.setItem(key, mode);
132
+ return true;
133
+ } catch {
134
+ return false;
135
+ }
136
+ }
137
+ function migrateLegacyMode(storage, key) {
138
+ if (storage === null || storage === void 0) return null;
139
+ let current;
140
+ try {
141
+ current = storage.getItem(key);
142
+ } catch {
143
+ return null;
144
+ }
145
+ if (isMode(current)) return null;
146
+ let legacy;
147
+ try {
148
+ legacy = storage.getItem(LEGACY_BOOLEAN_KEY);
149
+ } catch {
150
+ return null;
151
+ }
152
+ const migrated = legacy === "false" ? "gitbash" : legacy === "true" ? DEFAULT_MODE : null;
153
+ if (migrated === null) return null;
154
+ try {
155
+ storage.setItem(key, migrated);
156
+ } catch {
157
+ }
158
+ return migrated;
159
+ }
160
+
161
+ // client/src/store.js
162
+ var MODE_KEY = PLUGIN_ID + ":mode";
163
+ var SHELL_GLOBAL = "__dshGitBash";
164
+ function createShellInfoStore(carrier) {
165
+ const raw = carrier === void 0 ? void 0 : carrier[SHELL_GLOBAL];
166
+ const valid = raw !== null && typeof raw === "object";
167
+ const normalize = (record) => {
168
+ const entry = record !== null && typeof record === "object" ? record : {};
169
+ return {
170
+ path: typeof entry.path === "string" ? entry.path : null,
171
+ kind: typeof entry.kind === "string" ? entry.kind : null,
172
+ source: typeof entry.source === "string" ? entry.source : null,
173
+ reason: typeof entry.reason === "string" ? entry.reason : null
174
+ };
175
+ };
176
+ const byMode = valid && raw.byMode !== null && typeof raw.byMode === "object" ? raw.byMode : null;
177
+ const active = valid && raw.active !== null && typeof raw.active === "object" ? raw.active : raw;
178
+ const single = normalize(active);
179
+ const snapshots = byMode === null ? null : Object.fromEntries(Object.entries(byMode).map(([key, value]) => [key, normalize(value)]));
180
+ return {
181
+ /**
182
+ * The resolution for one mode.
183
+ *
184
+ * Falls back to the active record when the mode is unknown, so a stale or
185
+ * future value still shows SOMETHING rather than an empty row.
186
+ *
187
+ * @param {string} mode one of the three modes
188
+ * @returns {object} the resolution
189
+ */
190
+ forMode(mode) {
191
+ if (snapshots === null) return single;
192
+ return snapshots[mode] ?? single;
193
+ },
194
+ getSnapshot: () => single,
195
+ subscribe: () => () => {
196
+ },
197
+ dispose: () => {
198
+ }
199
+ };
200
+ }
201
+ function createModeStore(storage) {
202
+ migrateLegacyMode(storage, MODE_KEY);
203
+ let current = readMode(storage, MODE_KEY);
204
+ const listeners = /* @__PURE__ */ new Set();
205
+ return {
206
+ getSnapshot: () => current,
207
+ subscribe(listener) {
208
+ listeners.add(listener);
209
+ return () => {
210
+ listeners.delete(listener);
211
+ };
212
+ },
213
+ set(value) {
214
+ if (!MODES.includes(value) || value === current) return;
215
+ current = value;
216
+ writeMode(storage, MODE_KEY, value);
217
+ for (const listener of listeners) listener();
218
+ },
219
+ dispose() {
220
+ listeners.clear();
221
+ }
222
+ };
223
+ }
224
+ function describeShell(info, t) {
225
+ if (info.path === null) {
226
+ return t(info.reason === null ? "shell.notFound" : REASON_LABELS[info.reason] ?? "shell.notFound");
227
+ }
228
+ const kind = info.kind === null ? "" : t(KIND_LABELS[info.kind] ?? info.kind);
229
+ return t("shell.current", { path: info.path, kind });
230
+ }
231
+
232
+ // client/src/settings-row.js
233
+ function createSettingsRow({ React: React2, primitives: primitives2 }) {
234
+ const h = React2.createElement;
235
+ const Menu = primitives2 === null ? null : primitives2.Menu;
236
+ const MenuItemButton = primitives2 === null ? null : primitives2.MenuItemButton;
237
+ const IconChevron = primitives2 === null ? null : primitives2.IconChevronDownOutlineRegular;
238
+ const IconCheck = primitives2 === null ? null : primitives2.IconCheckOutlineRegular;
239
+ function ShellRow(props) {
240
+ const t = props.t;
241
+ const mode = React2.useSyncExternalStore(props.modeStore.subscribe, props.modeStore.getSnapshot);
242
+ const info = props.shellInfoStore.forMode(mode);
243
+ const [open, setOpen] = React2.useState(false);
244
+ const title = h("div", { className: "dsh-git-bash-title" }, t("shell.title"));
245
+ const hint = h("div", { className: "dsh-git-bash-hint" }, t("shell.hint"));
246
+ const failure = info.path === null ? h("div", { className: "dsh-git-bash-status dsh-git-bash-missing" }, describeShell(info, t)) : null;
247
+ const label = t("shell.mode");
248
+ const choose = (value) => {
249
+ props.setMode(value);
250
+ setOpen(false);
251
+ };
252
+ if (Menu === null || MenuItemButton === null) {
253
+ return h(
254
+ "div",
255
+ { className: "dsh-git-bash-row", id: SETTINGS_ROW_ID },
256
+ h("div", { className: "dsh-git-bash-main" }, title, hint, failure),
257
+ h("select", {
258
+ className: "dsh-git-bash-select",
259
+ "aria-label": label,
260
+ value: mode,
261
+ onChange: (event) => props.setMode(event.target.value)
262
+ }, MODES.map((value) => h("option", { key: value, value }, t(MODE_LABELS[value]))))
263
+ );
264
+ }
265
+ const trigger = (isOpen) => h(
266
+ "button",
267
+ {
268
+ type: "button",
269
+ className: "dsh-git-bash-trigger",
270
+ "aria-label": label,
271
+ "aria-haspopup": "menu",
272
+ "aria-expanded": isOpen,
273
+ onClick: () => setOpen(!isOpen)
274
+ },
275
+ h("span", { className: "dsh-git-bash-value" }, t(MODE_LABELS[mode])),
276
+ IconChevron === null ? "\u25BE" : h(IconChevron, { size: 12 })
277
+ );
278
+ const items = MODES.map((value) => h(MenuItemButton, {
279
+ key: value,
280
+ onSelect: () => choose(value)
281
+ }, h(
282
+ "span",
283
+ { className: "dsh-git-bash-item" },
284
+ h("span", null, t(MODE_LABELS[value])),
285
+ value === mode && IconCheck !== null ? h("span", { className: "dsh-git-bash-check" }, h(IconCheck, { size: 14 })) : null
286
+ )));
287
+ return h(
288
+ "div",
289
+ { className: "dsh-git-bash-row", id: SETTINGS_ROW_ID },
290
+ h("div", { className: "dsh-git-bash-main" }, title, hint, failure),
291
+ h(Menu, {
292
+ open,
293
+ anchor: trigger(open),
294
+ items: [],
295
+ onSelect: () => setOpen(false),
296
+ onClose: () => setOpen(false),
297
+ align: "end",
298
+ portal: true,
299
+ autoFocus: false
300
+ }, ...items)
301
+ );
302
+ }
303
+ return ShellRow;
304
+ }
305
+
306
+ // client/src/styles.js
307
+ var STYLE_MARKER = "data-dsh-git-bash-style";
308
+ var CSS = `
309
+ .dsh-git-bash-row {
310
+ display: flex;
311
+ align-items: flex-start;
312
+ justify-content: space-between;
313
+ gap: 24px;
314
+ padding: 16px 0;
315
+ /* NO border of our own. cds-theme owns the rule that draws the hairline under
316
+ every settings row (the slot-child selector), and its selector is more
317
+ specific than a single class \u2014 so a border here LOSES and the row ends up
318
+ with the theme's line anyway. Declaring it was worse than useless: it made
319
+ this row's line look different from its neighbours whenever the specificity
320
+ race went the other way. The theme draws it; the row leaves space for it. */
321
+ }
322
+
323
+ .dsh-git-bash-main {
324
+ min-width: 0;
325
+ flex: 1;
326
+ }
327
+
328
+ .dsh-git-bash-title {
329
+ font-size: var(--dsh-content-font-size, 14px);
330
+ line-height: 22px;
331
+ color: var(--dsw-alias-label-primary);
332
+ }
333
+
334
+ .dsh-git-bash-hint {
335
+ margin-top: 2px;
336
+ font-size: var(--dsh-content-font-size-caption, 12px);
337
+ line-height: 18px;
338
+ color: var(--dsw-alias-label-tertiary);
339
+ }
340
+
341
+ .dsh-git-bash-status {
342
+ margin-top: 6px;
343
+ font-size: var(--dsh-content-font-size-caption, 12px);
344
+ line-height: 18px;
345
+ color: var(--dsw-alias-label-secondary);
346
+ /* A Windows path is long and has no spaces to break at, so wrap anywhere
347
+ rather than let it push the menu off the row. */
348
+ overflow-wrap: anywhere;
349
+ }
350
+
351
+ .dsh-git-bash-missing {
352
+ color: var(--dsw-alias-state-error-primary, var(--dsw-alias-code-diff-deleted));
353
+ }
354
+
355
+ /* The trigger states the current mode, so the row is readable without opening
356
+ the menu \u2014 the same shape the host's own multi-value rows use. */
357
+ .dsh-git-bash-trigger {
358
+ display: inline-flex;
359
+ align-items: center;
360
+ gap: 6px;
361
+ flex: none;
362
+ align-self: flex-start;
363
+ height: 32px;
364
+ padding: 0 12px;
365
+ border: 0;
366
+ border-radius: var(--dsw-radius-md);
367
+ background: var(--dsw-alias-bg-layer-2);
368
+ color: var(--dsw-alias-label-primary);
369
+ font: inherit;
370
+ font-size: var(--dsh-content-font-size-secondary, 13px);
371
+ cursor: pointer;
372
+ }
373
+
374
+ .dsh-git-bash-trigger:hover {
375
+ background: var(--dsw-alias-interactive-bg-hover);
376
+ }
377
+
378
+ .dsh-git-bash-trigger:focus-visible {
379
+ outline: 2px solid var(--dsw-alias-brand-primary);
380
+ outline-offset: 1px;
381
+ }
382
+
383
+ .dsh-git-bash-select {
384
+ flex: none;
385
+ align-self: flex-start;
386
+ height: 32px;
387
+ padding: 0 8px;
388
+ border: 0.5px solid var(--dsw-alias-border-l1);
389
+ border-radius: var(--dsw-radius-md);
390
+ background: var(--dsw-alias-bg-layer-2);
391
+ color: var(--dsw-alias-label-primary);
392
+ font: inherit;
393
+ font-size: var(--dsh-content-font-size-secondary, 13px);
394
+ }
395
+
396
+ /* One row per mode inside the popup, with a check on the active one. */
397
+ .dsh-git-bash-item {
398
+ display: flex;
399
+ align-items: center;
400
+ justify-content: space-between;
401
+ gap: 16px;
402
+ min-width: 190px;
403
+ }
404
+
405
+ .dsh-git-bash-check {
406
+ display: inline-flex;
407
+ color: var(--dsw-alias-brand-primary);
408
+ }
409
+ `;
410
+ function installCss(doc) {
411
+ if (doc === void 0 || doc === null || typeof doc.createElement !== "function") {
412
+ return function disposeWithoutDocument() {
413
+ };
414
+ }
415
+ const previous = doc.querySelector(`style[${STYLE_MARKER}]`);
416
+ if (previous !== null) previous.remove();
417
+ const tag = doc.createElement("style");
418
+ tag.setAttribute(STYLE_MARKER, "");
419
+ tag.textContent = CSS;
420
+ doc.head.append(tag);
421
+ return function dispose() {
422
+ tag.remove();
423
+ };
424
+ }
425
+
426
+ // client/src/lifecycle.js
427
+ var MODE_EVENT = "git-bash/mode";
428
+ var MODE_PATH = "/dsh-git-bash/mode";
429
+ var MODE_MARKER_HEADER = "x-dsh-git-bash";
430
+ function createLifecycle({ React: React2, primitives: primitives2 }) {
431
+ const ShellRow = createSettingsRow({ React: React2, primitives: primitives2 });
432
+ return function apply2(ctx) {
433
+ ctx.effect(() => {
434
+ return ctx.locale.register(LOCALE_NS, { en, zh });
435
+ }, "dsh-git-bash: strings");
436
+ ctx.effect(
437
+ () => installCss(typeof document === "undefined" ? void 0 : document),
438
+ "dsh-git-bash: row stylesheet"
439
+ );
440
+ const storage = typeof localStorage === "undefined" ? void 0 : localStorage;
441
+ const shellInfo = createShellInfoStore(
442
+ typeof globalThis === "undefined" ? void 0 : globalThis
443
+ );
444
+ ctx.effect(() => () => shellInfo.dispose(), "dsh-git-bash: shell info store");
445
+ const modeStore = createModeStore(storage);
446
+ ctx.effect(() => () => modeStore.dispose(), "dsh-git-bash: mode store");
447
+ const publish = () => {
448
+ const mode = modeStore.getSnapshot();
449
+ ctx.emit(MODE_EVENT, { mode });
450
+ if (typeof fetch !== "function") return;
451
+ try {
452
+ void fetch(MODE_PATH, {
453
+ method: "POST",
454
+ credentials: "same-origin",
455
+ keepalive: true,
456
+ headers: {
457
+ "content-type": "application/json",
458
+ [MODE_MARKER_HEADER]: "1"
459
+ },
460
+ body: JSON.stringify({ mode })
461
+ }).catch(() => {
462
+ });
463
+ } catch (error) {
464
+ }
465
+ };
466
+ publish();
467
+ ctx.effect(() => modeStore.subscribe(publish), "dsh-git-bash: publish mode");
468
+ ctx.slots.inject("settings.general.item", () => ctx.slots.register({
469
+ name: "settings.general.item",
470
+ id: SETTINGS_ROW_ID,
471
+ order: 43,
472
+ locale: LOCALE_NS,
473
+ // The stores as ORDINARY props, not a `hooks` entry: a function component
474
+ // receives props, while `hooks:` builds a `useName(selector)` prop that
475
+ // only a framework-aware occupant reads. See `store.js`.
476
+ inject: () => ({
477
+ modeStore,
478
+ shellInfoStore: shellInfo,
479
+ setMode: (value) => modeStore.set(value)
480
+ })
481
+ }, ShellRow));
482
+ };
483
+ }
484
+
485
+ // client/src/index.js
486
+ var name = PLUGIN_ID;
487
+ var inject = ["slots", "locale"];
488
+ var React = null;
489
+ try {
490
+ React = require("react");
491
+ } catch (error) {
492
+ React = null;
493
+ }
494
+ var primitives = null;
495
+ try {
496
+ primitives = require("@deepseek-ai/dsh-client-ui-primitives");
497
+ } catch (error) {
498
+ primitives = null;
499
+ }
500
+ var apply = React === null ? function applyWithoutReact() {
501
+ } : createLifecycle({ React, primitives });
502
+ return module.exports
503
+ },
504
+ })
@@ -0,0 +1,8 @@
1
+ # dsh bundle patch: inserts this plugin into a profile's layer stack.
2
+ #
3
+ # `name` MUST equal the package's own `name` field. When the two disagree the
4
+ # Loader cannot resolve this row to a package, the row never activates, and the
5
+ # plugin silently disappears — no error surfaces anywhere. See AGENTS.md.
6
+ - insert:
7
+ - id: dsh-git-bash
8
+ name: '@hwfee/dsh-git-bash'
package/icon.svg ADDED
@@ -0,0 +1,16 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48" height="48" role="img" aria-label="Generative UI">
2
+ <!--
3
+ Original artwork, MIT, drawn for this plugin.
4
+
5
+ The mark reads as a card whose contents are being composed: a rounded frame
6
+ (the card), a filled accent bar (a chart/answer emerging), and two ghost
7
+ bars (content still forming). Neutral enough to sit beside any theme; the
8
+ two outer colours are the plugin's own, the accent matches the clay used by
9
+ cds-theme without depending on it.
10
+ -->
11
+ <rect x="4.5" y="8.5" width="39" height="31" rx="6" fill="none" stroke="#c6613f" stroke-width="2.4"/>
12
+ <rect x="11" y="16" width="9" height="16" rx="2.4" fill="#c6613f"/>
13
+ <rect x="24" y="16" width="13" height="4.6" rx="2.3" fill="#c6613f" opacity="0.75"/>
14
+ <rect x="24" y="24" width="13" height="4.6" rx="2.3" fill="#c6613f" opacity="0.4"/>
15
+ <rect x="24" y="32" width="7" height="2.2" rx="1.1" fill="#c6613f" opacity="0.22"/>
16
+ </svg>