@hwfee/dsh-git-bash 0.0.0-stage → 0.1.1

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.
@@ -0,0 +1,1135 @@
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
+ // The tool card's header: the reference names the action, not the tool.
71
+ "run.done": "Ran a command",
72
+ "run.running": "Running a command",
73
+ "run.stopped": "Stopped running the command",
74
+ "run.copy": "Copy command"
75
+ };
76
+ var zh = {
77
+ "shell.title": "\u9ED8\u8BA4 shell",
78
+ "shell.hint": "\u7528\u54EA\u4E2A\u89E3\u91CA\u5668\u6267\u884C shell \u547D\u4EE4\u3002",
79
+ "shell.mode": "Shell \u6A21\u5F0F",
80
+ "shell.mode.auto": "\u81EA\u52A8",
81
+ "shell.mode.pwsh": "Windows PowerShell",
82
+ "shell.mode.gitbash": "Git Bash",
83
+ "shell.current": "{kind} \xB7 {path}",
84
+ "shell.notFound": "\u6CA1\u6709\u89E3\u6790\u5230 shell",
85
+ "shell.kind.gitbash": "Git Bash",
86
+ "shell.kind.posix-bash": "bash",
87
+ "shell.kind.pwsh": "PowerShell 7",
88
+ "shell.kind.windows-powershell": "Windows PowerShell 5.1",
89
+ "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",
90
+ "shell.reason.pwsh-not-found": "\u6CA1\u627E\u5230 PowerShell\u3002\u8BF7\u5B89\u88C5 PowerShell 7 \u6216 Windows PowerShell\u3002",
91
+ "shell.reason.no-shell-for-platform": "Windows PowerShell \u53EA\u5728 Windows \u4E0A\u53EF\u7528\u3002",
92
+ "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",
93
+ "run.done": "\u5DF2\u8FD0\u884C\u547D\u4EE4",
94
+ "run.running": "\u6B63\u5728\u8FD0\u884C\u547D\u4EE4",
95
+ "run.stopped": "\u5DF2\u505C\u6B62\u8FD0\u884C\u547D\u4EE4",
96
+ "run.copy": "\u590D\u5236\u547D\u4EE4"
97
+ };
98
+
99
+ // lib/mode.js
100
+ var MODES = ["auto", "pwsh", "gitbash"];
101
+ var DEFAULT_MODE = "auto";
102
+ var MODE_LABELS = {
103
+ auto: "shell.mode.auto",
104
+ pwsh: "shell.mode.pwsh",
105
+ gitbash: "shell.mode.gitbash"
106
+ };
107
+ var KIND_LABELS = {
108
+ gitbash: "shell.kind.gitbash",
109
+ "posix-bash": "shell.kind.posix-bash",
110
+ pwsh: "shell.kind.pwsh",
111
+ "windows-powershell": "shell.kind.windows-powershell"
112
+ };
113
+ var REASON_LABELS = {
114
+ "gitbash-not-found": "shell.reason.gitbash-not-found",
115
+ "pwsh-not-found": "shell.reason.pwsh-not-found",
116
+ "no-shell-for-platform": "shell.reason.no-shell-for-platform",
117
+ "no-shell-found": "shell.reason.no-shell-found"
118
+ };
119
+ var LEGACY_BOOLEAN_KEY = "@hwfee/dsh-git-bash:exposeNativeShell";
120
+ function isMode(value) {
121
+ return typeof value === "string" && MODES.includes(value);
122
+ }
123
+ function normalizeMode(value) {
124
+ return isMode(value) ? value : DEFAULT_MODE;
125
+ }
126
+ function readMode(storage, key) {
127
+ if (storage === null || storage === void 0) return DEFAULT_MODE;
128
+ let raw;
129
+ try {
130
+ raw = storage.getItem(key);
131
+ } catch {
132
+ return DEFAULT_MODE;
133
+ }
134
+ return normalizeMode(raw);
135
+ }
136
+ function writeMode(storage, key, mode) {
137
+ if (storage === null || storage === void 0) return false;
138
+ if (!isMode(mode)) return false;
139
+ try {
140
+ storage.setItem(key, mode);
141
+ return true;
142
+ } catch {
143
+ return false;
144
+ }
145
+ }
146
+ function migrateLegacyMode(storage, key) {
147
+ if (storage === null || storage === void 0) return null;
148
+ let current;
149
+ try {
150
+ current = storage.getItem(key);
151
+ } catch {
152
+ return null;
153
+ }
154
+ if (isMode(current)) return null;
155
+ let legacy;
156
+ try {
157
+ legacy = storage.getItem(LEGACY_BOOLEAN_KEY);
158
+ } catch {
159
+ return null;
160
+ }
161
+ const migrated = legacy === "false" ? "gitbash" : legacy === "true" ? DEFAULT_MODE : null;
162
+ if (migrated === null) return null;
163
+ try {
164
+ storage.setItem(key, migrated);
165
+ } catch {
166
+ }
167
+ return migrated;
168
+ }
169
+
170
+ // client/src/store.js
171
+ var MODE_KEY = PLUGIN_ID + ":mode";
172
+ var SHELL_GLOBAL = "__dshGitBash";
173
+ function createShellInfoStore(carrier) {
174
+ const raw = carrier === void 0 ? void 0 : carrier[SHELL_GLOBAL];
175
+ const valid = raw !== null && typeof raw === "object";
176
+ const normalize = (record) => {
177
+ const entry = record !== null && typeof record === "object" ? record : {};
178
+ return {
179
+ path: typeof entry.path === "string" ? entry.path : null,
180
+ kind: typeof entry.kind === "string" ? entry.kind : null,
181
+ source: typeof entry.source === "string" ? entry.source : null,
182
+ reason: typeof entry.reason === "string" ? entry.reason : null
183
+ };
184
+ };
185
+ const byMode = valid && raw.byMode !== null && typeof raw.byMode === "object" ? raw.byMode : null;
186
+ const active = valid && raw.active !== null && typeof raw.active === "object" ? raw.active : raw;
187
+ const single = normalize(active);
188
+ const snapshots = byMode === null ? null : Object.fromEntries(Object.entries(byMode).map(([key, value]) => [key, normalize(value)]));
189
+ return {
190
+ /**
191
+ * The resolution for one mode.
192
+ *
193
+ * Falls back to the active record when the mode is unknown, so a stale or
194
+ * future value still shows SOMETHING rather than an empty row.
195
+ *
196
+ * @param {string} mode one of the three modes
197
+ * @returns {object} the resolution
198
+ */
199
+ forMode(mode) {
200
+ if (snapshots === null) return single;
201
+ return snapshots[mode] ?? single;
202
+ },
203
+ getSnapshot: () => single,
204
+ subscribe: () => () => {
205
+ },
206
+ dispose: () => {
207
+ }
208
+ };
209
+ }
210
+ function createModeStore(storage) {
211
+ migrateLegacyMode(storage, MODE_KEY);
212
+ let current = readMode(storage, MODE_KEY);
213
+ const listeners = /* @__PURE__ */ new Set();
214
+ return {
215
+ getSnapshot: () => current,
216
+ subscribe(listener) {
217
+ listeners.add(listener);
218
+ return () => {
219
+ listeners.delete(listener);
220
+ };
221
+ },
222
+ set(value) {
223
+ if (!MODES.includes(value) || value === current) return;
224
+ current = value;
225
+ writeMode(storage, MODE_KEY, value);
226
+ for (const listener of listeners) listener();
227
+ },
228
+ dispose() {
229
+ listeners.clear();
230
+ }
231
+ };
232
+ }
233
+ function describeShell(info, t) {
234
+ if (info.path === null) {
235
+ return t(info.reason === null ? "shell.notFound" : REASON_LABELS[info.reason] ?? "shell.notFound");
236
+ }
237
+ const kind = info.kind === null ? "" : t(KIND_LABELS[info.kind] ?? info.kind);
238
+ return t("shell.current", { path: info.path, kind });
239
+ }
240
+
241
+ // client/src/settings-row.js
242
+ function createSettingsRow({ React: React2, primitives: primitives2 }) {
243
+ const h = React2.createElement;
244
+ const Menu = primitives2 === null ? null : primitives2.Menu;
245
+ const MenuItemButton = primitives2 === null ? null : primitives2.MenuItemButton;
246
+ const IconChevron = primitives2 === null ? null : primitives2.IconChevronDownOutlineRegular;
247
+ const IconCheck = primitives2 === null ? null : primitives2.IconCheckOutlineRegular;
248
+ function ShellRow(props) {
249
+ const t = props.t;
250
+ const mode = React2.useSyncExternalStore(props.modeStore.subscribe, props.modeStore.getSnapshot);
251
+ const info = props.shellInfoStore.forMode(mode);
252
+ const [open, setOpen] = React2.useState(false);
253
+ const title = h("div", { className: "dsh-git-bash-title" }, t("shell.title"));
254
+ const hint = h("div", { className: "dsh-git-bash-hint" }, t("shell.hint"));
255
+ const failure = info.path === null ? h("div", { className: "dsh-git-bash-status dsh-git-bash-missing" }, describeShell(info, t)) : null;
256
+ const label = t("shell.mode");
257
+ const choose = (value) => {
258
+ props.setMode(value);
259
+ setOpen(false);
260
+ };
261
+ if (Menu === null || MenuItemButton === null) {
262
+ return h(
263
+ "div",
264
+ { className: "dsh-git-bash-row", id: SETTINGS_ROW_ID },
265
+ h("div", { className: "dsh-git-bash-main" }, title, hint, failure),
266
+ h("select", {
267
+ className: "dsh-git-bash-select",
268
+ "aria-label": label,
269
+ value: mode,
270
+ onChange: (event) => props.setMode(event.target.value)
271
+ }, MODES.map((value) => h("option", { key: value, value }, t(MODE_LABELS[value]))))
272
+ );
273
+ }
274
+ const trigger = (isOpen) => h(
275
+ "button",
276
+ {
277
+ type: "button",
278
+ className: "dsh-git-bash-trigger",
279
+ "aria-label": label,
280
+ "aria-haspopup": "menu",
281
+ "aria-expanded": isOpen,
282
+ onClick: () => setOpen(!isOpen)
283
+ },
284
+ h("span", { className: "dsh-git-bash-value" }, t(MODE_LABELS[mode])),
285
+ IconChevron === null ? "\u25BE" : h(IconChevron, { size: 12 })
286
+ );
287
+ const items = MODES.map((value) => h(MenuItemButton, {
288
+ key: value,
289
+ onSelect: () => choose(value)
290
+ }, h(
291
+ "span",
292
+ { className: "dsh-git-bash-item" },
293
+ h("span", null, t(MODE_LABELS[value])),
294
+ value === mode && IconCheck !== null ? h("span", { className: "dsh-git-bash-check" }, h(IconCheck, { size: 14 })) : null
295
+ )));
296
+ return h(
297
+ "div",
298
+ { className: "dsh-git-bash-row", id: SETTINGS_ROW_ID },
299
+ h("div", { className: "dsh-git-bash-main" }, title, hint, failure),
300
+ h(Menu, {
301
+ open,
302
+ anchor: trigger(open),
303
+ items: [],
304
+ onSelect: () => setOpen(false),
305
+ onClose: () => setOpen(false),
306
+ align: "end",
307
+ portal: true,
308
+ autoFocus: false
309
+ }, ...items)
310
+ );
311
+ }
312
+ return ShellRow;
313
+ }
314
+
315
+ // client/src/tool-view.js
316
+ var OPERATORS = ["&&", "||", "|", ";", ">>", ">", "<"];
317
+ var WORD_BREAK = /* @__PURE__ */ new Set([" ", " ", "\n", "|", ";", ">", "<", '"', "'"]);
318
+ function tokenizeShell(command) {
319
+ const tokens = [];
320
+ if (typeof command !== "string" || command === "") return tokens;
321
+ let i = 0;
322
+ let expectCommand = true;
323
+ while (i < command.length) {
324
+ const ch = command[i];
325
+ if (ch === " " || ch === " " || ch === "\n") {
326
+ const start = i;
327
+ while (i < command.length && (command[i] === " " || command[i] === " " || command[i] === "\n")) i += 1;
328
+ tokens.push({ text: command.slice(start, i), kind: "plain" });
329
+ continue;
330
+ }
331
+ if (ch === '"' || ch === "'") {
332
+ const quote = ch;
333
+ let j2 = i + 1;
334
+ while (j2 < command.length) {
335
+ if (quote === '"' && command[j2] === "\\") {
336
+ j2 += 2;
337
+ continue;
338
+ }
339
+ if (command[j2] === quote) {
340
+ j2 += 1;
341
+ break;
342
+ }
343
+ j2 += 1;
344
+ }
345
+ tokens.push({ text: command.slice(i, j2), kind: "string" });
346
+ i = j2;
347
+ expectCommand = false;
348
+ continue;
349
+ }
350
+ const operator = OPERATORS.find(function(candidate) {
351
+ return command.startsWith(candidate, i);
352
+ });
353
+ if (operator !== void 0) {
354
+ tokens.push({ text: operator, kind: "operator" });
355
+ i += operator.length;
356
+ expectCommand = true;
357
+ continue;
358
+ }
359
+ let j = i;
360
+ while (j < command.length && !WORD_BREAK.has(command[j])) j += 1;
361
+ const word = command.slice(i, j);
362
+ if (expectCommand) {
363
+ tokens.push({ text: word, kind: "command" });
364
+ expectCommand = false;
365
+ } else {
366
+ tokens.push({ text: word, kind: word.startsWith("-") ? "flag" : "arg" });
367
+ }
368
+ i = j;
369
+ }
370
+ return tokens;
371
+ }
372
+ function readArguments(argsRaw) {
373
+ if (typeof argsRaw !== "string" || argsRaw === "") return null;
374
+ try {
375
+ const value = JSON.parse(argsRaw);
376
+ return typeof value === "object" && value !== null ? value : null;
377
+ } catch (error) {
378
+ return null;
379
+ }
380
+ }
381
+ function resultText(block) {
382
+ const parts = [];
383
+ const content = Array.isArray(block?.content) ? block.content : [];
384
+ for (const part of content) {
385
+ if (part?.type === "text") parts.push(part.text);
386
+ else parts.push(JSON.stringify(part, null, 2));
387
+ }
388
+ const error = block?.error;
389
+ if (parts.length === 0 && error !== void 0 && error !== null) {
390
+ parts.push(String(error.name ?? "Error") + ": " + String(error.code ?? ""));
391
+ }
392
+ return parts.join("\n");
393
+ }
394
+ function createRunShellRow({ React: React2 }) {
395
+ const h = React2.createElement;
396
+ function CopyCommandButton({ text, label }) {
397
+ const [copied, setCopied] = React2.useState(false);
398
+ const timer = React2.useRef(null);
399
+ React2.useEffect(function clearOnUnmount() {
400
+ return function clear() {
401
+ if (timer.current !== null) clearTimeout(timer.current);
402
+ };
403
+ }, []);
404
+ const copy = function copy2() {
405
+ const clipboard = typeof navigator === "undefined" ? void 0 : navigator.clipboard;
406
+ if (clipboard === void 0 || typeof clipboard.writeText !== "function") return;
407
+ void clipboard.writeText(text).then(function done() {
408
+ setCopied(true);
409
+ if (timer.current !== null) clearTimeout(timer.current);
410
+ timer.current = setTimeout(function decay() {
411
+ setCopied(false);
412
+ }, 1e3);
413
+ }, function failed() {
414
+ });
415
+ };
416
+ return h(
417
+ "button",
418
+ {
419
+ type: "button",
420
+ className: "dsh-gb-copy",
421
+ "aria-label": label,
422
+ "data-copied": copied ? "true" : void 0,
423
+ onClick: copy
424
+ },
425
+ // THE HOST'S OWN COPY GLYPH, not a character stand-in. The two-rect mark
426
+ // below is the exact SVG the banner renders (`_action_1pq26_43` button,
427
+ // `aria-label="Copy"`), copied so the command card's control is visually
428
+ // the same control the read and edit cards show — same 14px box, same 1px
429
+ // stroke, same fill rule. A `⧉` character rendered at a different weight
430
+ // and optical size, which is what this replaced.
431
+ copied ? h("svg", { width: 12, height: 12, viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true" }, h("path", { d: "M3.5 8.5l3 3 6-6", stroke: "currentColor", strokeWidth: 1.2, strokeLinecap: "round", strokeLinejoin: "round" })) : h(
432
+ "svg",
433
+ {
434
+ // 12px inside an 18px box: the command box is a small surface, and the
435
+ // 14px glyph the banner uses looked heavy in it.
436
+ width: 12,
437
+ height: 12,
438
+ viewBox: "0 0 16 16",
439
+ fill: "none",
440
+ xmlns: "http://www.w3.org/2000/svg",
441
+ "aria-hidden": "true",
442
+ strokeWidth: 1
443
+ },
444
+ h("rect", { x: 1.52075, y: 4.07373, width: 10.3932, height: 10.3932, rx: 2, stroke: "currentColor" }),
445
+ h("path", {
446
+ d: "M11.9792 1.53296C13.36 1.53296 14.4792 2.65225 14.4792 4.03296V9.42847C14.4792 10.3756 13.9521 11.1987 13.1755 11.6228V10.3298C13.3652 10.0787 13.4792 9.7674 13.4792 9.42847V4.03296C13.4792 3.20453 12.8077 2.53296 11.9792 2.53296H6.58374C6.27966 2.53301 5.99684 2.6235 5.7605 2.77905H4.42358C4.85652 2.03463 5.66056 1.53304 6.58374 1.53296H11.9792Z",
447
+ fill: "currentColor"
448
+ })
449
+ )
450
+ );
451
+ }
452
+ function RunShellRow(props) {
453
+ const t = typeof props.t === "function" ? props.t : function untranslated(key) {
454
+ return key;
455
+ };
456
+ const block = props.block;
457
+ const done = block !== null && typeof block === "object" && "kind" in block;
458
+ const call = done ? block.call : block;
459
+ const args = readArguments(done ? call?.argsRaw : block?.argsRaw);
460
+ const command = typeof args?.command === "string" ? args.command : "";
461
+ const description = typeof args?.description === "string" ? args.description.trim() : "";
462
+ const output = done ? resultText(block) : "";
463
+ const interrupted = done && block.error?.code === "interrupted";
464
+ const state = !done ? block?.phase === "preparing" ? "preparing" : "running" : interrupted ? "stopped" : block.isError ? "error" : "ok";
465
+ const disclosure = typeof props.useDisclosure === "function" ? props.useDisclosure() : null;
466
+ const expanded = disclosure !== null && disclosure.expanded === true;
467
+ const toggle = disclosure === null ? null : disclosure.toggle;
468
+ const canExpand = command !== "" || output !== "";
469
+ const title = description !== "" ? description : state === "stopped" ? t("run.stopped") : state === "running" || state === "preparing" ? t("run.running") : t("run.done");
470
+ const activate = canExpand && toggle !== null ? toggle : void 0;
471
+ const onKeyDown = canExpand && toggle !== null ? function onKeyDown2(event) {
472
+ if (event.key !== "Enter" && event.key !== " ") return;
473
+ event.preventDefault();
474
+ toggle();
475
+ } : void 0;
476
+ return h(
477
+ "div",
478
+ {
479
+ className: "dsh-gb-tool",
480
+ // The wire name that dispatched this row: run_shell, pwsh or bash. It
481
+ // stays on the DOM because the theme and the E2E probes key on
482
+ // `[data-tool]`, exactly like the host's own rows.
483
+ "data-tool": typeof props.toolName === "string" && props.toolName !== "" ? props.toolName : "run_shell",
484
+ "data-state": state,
485
+ "data-expanded": expanded ? "true" : "false"
486
+ },
487
+ h(
488
+ "div",
489
+ {
490
+ className: "dsh-gb-tool-head" + (canExpand ? " is-expandable" : ""),
491
+ role: canExpand ? "button" : void 0,
492
+ tabIndex: canExpand ? 0 : void 0,
493
+ "aria-expanded": canExpand ? expanded : void 0,
494
+ onClick: activate,
495
+ onKeyDown
496
+ },
497
+ h(
498
+ "span",
499
+ { className: "dsh-gb-tool-chevron", "aria-hidden": "true" },
500
+ h("svg", {
501
+ width: 14,
502
+ height: 14,
503
+ viewBox: "0 0 16 16",
504
+ fill: "none",
505
+ xmlns: "http://www.w3.org/2000/svg",
506
+ strokeWidth: 1
507
+ }, h("path", {
508
+ // One down-chevron; the stylesheet rotates it while the row is closed.
509
+ d: "M4 6L7.29289 9.29289C9.68342 9.68342 8.31658 9.68342 8.70711 9.29289L12 6",
510
+ stroke: "currentColor"
511
+ }))
512
+ ),
513
+ h("span", { className: "dsh-gb-tool-title" }, title)
514
+ ),
515
+ canExpand && expanded ? h(
516
+ "div",
517
+ { className: "dsh-gb-tool-body" },
518
+ h(
519
+ "div",
520
+ { className: "dsh-gb-tool-request" },
521
+ h("span", { className: "dsh-gb-tool-prompt", "aria-hidden": "true" }, "$"),
522
+ h(
523
+ "code",
524
+ { className: "dsh-gb-tool-command" },
525
+ tokenizeShell(command).map(function(token, index) {
526
+ return h("span", { key: index, "data-token": token.kind }, token.text);
527
+ })
528
+ ),
529
+ // A HOOK COMPONENT, NOT A HELPER CALL. `CopyCommandButton` uses
530
+ // useState/useRef/useEffect, so calling it as a plain function would
531
+ // add hooks only while the row is expanded — the hook count would
532
+ // change between renders and React would throw, dropping the whole
533
+ // row. `h(...)` gives it its own component instance, mounted and
534
+ // unmounted with the body.
535
+ h(CopyCommandButton, { text: command, label: t("run.copy") })
536
+ ),
537
+ output !== "" ? h("pre", { className: "dsh-gb-tool-respond" }, output) : null
538
+ ) : null
539
+ );
540
+ }
541
+ return RunShellRow;
542
+ }
543
+
544
+ // client/src/styles.js
545
+ var STYLE_MARKER = "data-dsh-git-bash-style";
546
+ var CSS = `
547
+ .dsh-git-bash-row {
548
+ display: flex;
549
+ align-items: flex-start;
550
+ justify-content: space-between;
551
+ gap: 24px;
552
+ padding: 16px 0;
553
+ /* NO border of our own. cds-theme owns the rule that draws the hairline under
554
+ every settings row (the slot-child selector), and its selector is more
555
+ specific than a single class \u2014 so a border here LOSES and the row ends up
556
+ with the theme's line anyway. Declaring it was worse than useless: it made
557
+ this row's line look different from its neighbours whenever the specificity
558
+ race went the other way. The theme draws it; the row leaves space for it. */
559
+ }
560
+
561
+ .dsh-git-bash-main {
562
+ min-width: 0;
563
+ flex: 1;
564
+ }
565
+
566
+ .dsh-git-bash-title {
567
+ font-size: var(--dsh-content-font-size, 14px);
568
+ line-height: 22px;
569
+ color: var(--dsw-alias-label-primary);
570
+ }
571
+
572
+ .dsh-git-bash-hint {
573
+ margin-top: 2px;
574
+ font-size: var(--dsh-content-font-size-caption, 12px);
575
+ line-height: 18px;
576
+ color: var(--dsw-alias-label-tertiary);
577
+ }
578
+
579
+ .dsh-git-bash-status {
580
+ margin-top: 6px;
581
+ font-size: var(--dsh-content-font-size-caption, 12px);
582
+ line-height: 18px;
583
+ color: var(--dsw-alias-label-secondary);
584
+ /* A Windows path is long and has no spaces to break at, so wrap anywhere
585
+ rather than let it push the menu off the row. */
586
+ overflow-wrap: anywhere;
587
+ }
588
+
589
+ .dsh-git-bash-missing {
590
+ color: var(--dsw-alias-state-error-primary, var(--dsw-alias-code-diff-deleted));
591
+ }
592
+
593
+ /* The trigger states the current mode, so the row is readable without opening
594
+ the menu \u2014 the same shape the host's own multi-value rows use. */
595
+ .dsh-git-bash-trigger {
596
+ display: inline-flex;
597
+ align-items: center;
598
+ gap: 6px;
599
+ flex: none;
600
+ align-self: flex-start;
601
+ height: 32px;
602
+ padding: 0 12px;
603
+ border: 0;
604
+ border-radius: var(--dsw-radius-md);
605
+ background: var(--dsw-alias-bg-layer-2);
606
+ color: var(--dsw-alias-label-primary);
607
+ font: inherit;
608
+ font-size: var(--dsh-content-font-size-secondary, 13px);
609
+ cursor: pointer;
610
+ }
611
+
612
+ .dsh-git-bash-trigger:hover {
613
+ background: var(--dsw-alias-interactive-bg-hover);
614
+ }
615
+
616
+ .dsh-git-bash-trigger:focus-visible {
617
+ outline: 2px solid var(--dsw-alias-brand-primary);
618
+ outline-offset: 1px;
619
+ }
620
+
621
+ .dsh-git-bash-select {
622
+ flex: none;
623
+ align-self: flex-start;
624
+ height: 32px;
625
+ padding: 0 8px;
626
+ border: 0.5px solid var(--dsw-alias-border-l1);
627
+ border-radius: var(--dsw-radius-md);
628
+ background: var(--dsw-alias-bg-layer-2);
629
+ color: var(--dsw-alias-label-primary);
630
+ font: inherit;
631
+ font-size: var(--dsh-content-font-size-secondary, 13px);
632
+ }
633
+
634
+ /* One row per mode inside the popup, with a check on the active one. */
635
+ .dsh-git-bash-item {
636
+ display: flex;
637
+ align-items: center;
638
+ justify-content: space-between;
639
+ gap: 16px;
640
+ min-width: 190px;
641
+ }
642
+
643
+ .dsh-git-bash-check {
644
+ display: inline-flex;
645
+ color: var(--dsw-alias-brand-primary);
646
+ }
647
+
648
+ /* ---- the run_shell tool card --------------------------------------
649
+ The reference (Claude Code desktop) draws a shell call as one boxed
650
+ command line and then the output as BARE flowing text. Colours are the
651
+ reference's own Shiki palette; every surface/ink value is a host token so
652
+ the card follows the app's light and dark themes. */
653
+ .dsh-gb-tool {
654
+ --dsh-gb-command: #0073e6;
655
+ --dsh-gb-argument: #1e9e3c;
656
+ --dsh-gb-punctuation: #737373;
657
+ --dsh-gb-ink: #1a1a1a;
658
+ }
659
+
660
+ body[data-ds-dark-theme] .dsh-gb-tool {
661
+ --dsh-gb-command: #70b8ff;
662
+ --dsh-gb-argument: #9be963;
663
+ --dsh-gb-punctuation: #818898;
664
+ --dsh-gb-ink: #eaecf0;
665
+ }
666
+
667
+ /* The header: description only, 24px seat, muted until hovered. No tool name
668
+ and no icon \u2014 the reference's rows are a sentence and a chevron. */
669
+ .dsh-gb-tool-head {
670
+ display: flex;
671
+ align-items: center;
672
+ gap: 6px;
673
+ min-height: 24px;
674
+ color: var(--dsw-alias-label-tertiary);
675
+ font-size: var(--dsh-content-font-size-secondary, 13px);
676
+ line-height: 18px;
677
+ transition: color 100ms cubic-bezier(0.16, 1, 0.3, 1);
678
+ }
679
+
680
+ .dsh-gb-tool-head.is-expandable {
681
+ cursor: pointer;
682
+ }
683
+
684
+ .dsh-gb-tool-head.is-expandable:hover,
685
+ .dsh-gb-tool-head.is-expandable:focus-visible {
686
+ color: var(--dsw-alias-label-secondary);
687
+ }
688
+
689
+ .dsh-gb-tool-head:focus-visible {
690
+ outline: none;
691
+ }
692
+
693
+ .dsh-gb-tool[data-state="error"] .dsh-gb-tool-head {
694
+ color: var(--dsw-alias-state-error-primary);
695
+ }
696
+
697
+ .dsh-gb-tool[data-state="stopped"] .dsh-gb-tool-head {
698
+ color: var(--dsw-alias-state-warn-label, var(--dsw-alias-label-tertiary));
699
+ }
700
+
701
+ .dsh-gb-tool-chevron {
702
+ display: inline-flex;
703
+ flex: none;
704
+ transform: rotate(-90deg);
705
+ transition: transform 160ms cubic-bezier(0.16, 1, 0.3, 1);
706
+ }
707
+
708
+ .dsh-gb-tool[data-expanded="true"] .dsh-gb-tool-chevron {
709
+ transform: none;
710
+ }
711
+
712
+ /* THE ROW ITSELF IS PROSE, NOT CODE, AND IT HAS AN EXPLICIT SIZE.
713
+ Measured live: the card inherited 16px from the conversation, so the tool
714
+ name rendered at 16px next to host rows that are 13px \u2014 the "enormous font"
715
+ report. Host tool rows are 13/19 in this density, so this pins the same.
716
+ 24px keeps the row on the same rhythm as the host cells. */
717
+ [data-conversation-content] [data-slot="tool.call.toolview"] .dsh-gb-tool {
718
+ font-family: var(--dsw-font-family) !important;
719
+ font-size: calc(13px + var(--dsh-content-font-delta, 0px));
720
+ line-height: calc(19px + var(--dsh-content-font-delta, 0px));
721
+ font-weight: 400;
722
+ letter-spacing: normal;
723
+ }
724
+ [data-conversation-content] [data-slot="tool.call.toolview"] .dsh-gb-tool-head {
725
+ min-height: 24px;
726
+ align-items: center;
727
+ }
728
+ .dsh-gb-tool-title {
729
+ min-width: 0;
730
+ overflow: hidden;
731
+ text-overflow: ellipsis;
732
+ white-space: nowrap;
733
+ }
734
+
735
+ /* Body: the card is inset under the row (the reference reserves 10px on each
736
+ side and 4px above). */
737
+ .dsh-gb-tool-body {
738
+ display: flex;
739
+ flex-direction: column;
740
+ /* ZERO, NOT 10px. The flex gap stacked on top of the output card's own margin,
741
+ so the visual distance was 12px where the margin said 2px. The output's
742
+ margin is now the only thing controlling that gap. */
743
+ gap: 0;
744
+ margin: 4px 10px 8px 10px;
745
+ }
746
+
747
+ /* The request: a real code surface with the "$" prompt, wrapped by default
748
+ (the theme has no wrap toggle \u2014 every block wraps).
749
+
750
+ SURFACE. The card sits on the conversation canvas, and the command box must
751
+ read as a distinct block rather than melting into it: the fill is the host's
752
+ code-block token (a shade off the canvas in light mode, a lifted dark surface
753
+ in dark mode) plus a hairline, which is exactly the reference's separation.
754
+
755
+ TYPE. Both halves of this card (request and response below) read the SAME
756
+ rung the theme publishes for tool-card payloads \u2014
757
+ --dsw-font-markdown-code-block-small \u2014 rather than a hard-coded 12px.
758
+ The hard-coded pair used to disagree with the read/edit cards (now 13/20),
759
+ which is why the command and its output looked like two different breeds of
760
+ text in one card: same family, different size and leading, and a font:
761
+ shorthand resets letter-spacing/kerning each time it is declared. */
762
+ [data-conversation-content] [data-slot="tool.call.toolview"] .dsh-gb-tool-request {
763
+ position: relative;
764
+ display: flex;
765
+ align-items: flex-start;
766
+ gap: 4px;
767
+ /* NO CAP HERE EITHER. The 160px ceiling was copied from the reference's
768
+ fixed-height panel; a command is as long as it is, and clipping it behind a
769
+ scroller hides the thing the card exists to show. */
770
+ max-height: none;
771
+ /* 8px TOP/BOTTOM, 12px SIDES \u2014 the reverse-engineered fence inset
772
+ (docs/tool-cards-bash-terminal.md; same numbers in the reference's own
773
+ card). It replaces the old 5px-plus-a-rail pair: that 1ch term was a second
774
+ guess at the same inset, so the real value drifted with the code font's
775
+ digit width \u2014 measured 17.2px at 13px mono, i.e. 5px wider than the 12px the
776
+ table says, and it changed whenever the user moved the code-size setting.
777
+ A literal 12px is the only way the fence stays put. */
778
+ padding: 8px 12px;
779
+ overflow: visible;
780
+ /* NO FILL, ONE OUTLINE. The reference's input card carries no background
781
+ class at all: its "card" is a 1px inset outline
782
+ (.epitaxy-card-outline-overlay { outline: 1px solid var(--cds-border);
783
+ outline-offset: -1px }), which is why it needs no per-theme colour and
784
+ never fights the surface behind it. */
785
+ /* !important: measured live, a plain transparent lost to a higher-
786
+ specificity surface rule and the box kept painting the theme's card tint.
787
+ The reference has NO fill here at all, so this must win. */
788
+ background: var(--cds-card-surface, #ffffff) !important;
789
+ border: 0;
790
+ border-radius: 8px;
791
+ /* THE HAIRLINE IS A DOUBLE RING, not an outline. Two inset shadows: the
792
+ first paints the border colour, the second paints the CARD COLOUR over it,
793
+ so only the outer half of the first ring survives and the line reads
794
+ thinner than 1px. This is how the reference engraves its fence without
795
+ sub-pixel support. */
796
+ position: relative;
797
+ /* ONE STEP OFF THE CODE SURFACE, same 96/4 ink mix the theme's tool cards
798
+ use: identical to the code block makes the box vanish into the card,
799
+ identical to the card makes the block vanish into the box. */
800
+ background: color-mix(in srgb, var(--dsw-alias-markdown-code-block, #ffffff) 96%, var(--dsw-alias-label-primary, #1f1f1f));
801
+ color: var(--dsh-gb-ink);
802
+ font: var(--dsw-font-markdown-code-block-small, 12px/1.6 var(--ds-font-family-code));
803
+ /* !important: the theme pins the TEXT family on every non-code descendant of
804
+ the conversation with !important, and a card div is not code/pre \u2014 a
805
+ plain declaration here would lose and the command would render in Sans. */
806
+ font-family: var(--ds-font-family-code) !important;
807
+ white-space: pre-wrap;
808
+ overflow-wrap: anywhere;
809
+ }
810
+
811
+ [data-conversation-content] [data-slot="tool.call.toolview"] .dsh-gb-tool-request::after {
812
+ content: "";
813
+ position: absolute;
814
+ inset: 0;
815
+ border-radius: inherit;
816
+ pointer-events: none;
817
+ box-shadow:
818
+ inset 0 0 0 1px var(--dsw-alias-border-l1),
819
+ inset 0 0 0 1px var(--cds-card-surface, #ffffff);
820
+ }
821
+
822
+ /* Room for the copy control in the top-right corner of the box. */
823
+ [data-conversation-content] [data-slot="tool.call.toolview"] .dsh-gb-tool-command {
824
+ min-width: 0;
825
+ padding-right: 22px;
826
+ font: inherit;
827
+ white-space: inherit;
828
+ }
829
+
830
+ /* The copy control: inside the command box, its own corner, quiet until the
831
+ row is hovered \u2014 the same reveal the reference uses for its code rail. */
832
+ .dsh-gb-copy {
833
+ position: absolute;
834
+ /* Tucked into the box's top-right corner, on its first line. top: 4px
835
+ aligns it with the command text's cap height rather than the box's border
836
+ (a single-line command left it looking high). */
837
+ top: 8px;
838
+ right: 8px;
839
+ display: inline-flex;
840
+ align-items: center;
841
+ justify-content: center;
842
+ /* THE SHARED COPY CONTROL. The theme publishes --cds-copy-* on the
843
+ conversation content so the tool-row rail, the markdown banner and this
844
+ command box render one control instead of three drifting ones; the
845
+ fallbacks keep the card correct without the theme installed. */
846
+ width: var(--cds-copy-size, 18px);
847
+ height: var(--cds-copy-size, 18px);
848
+ padding: 0;
849
+ border: 0;
850
+ border-radius: var(--cds-copy-radius, var(--dsw-radius-xs, 4px));
851
+ /* TRANSPARENT, not filled. A solid plate inside an already-tinted box read as
852
+ a second surface; the reference's rail is bare and lets the code show
853
+ through. */
854
+ background: transparent;
855
+ color: var(--cds-copy-ink, var(--dsw-alias-label-tertiary));
856
+ font: inherit;
857
+ line-height: 1;
858
+ cursor: pointer;
859
+ opacity: 0;
860
+ /* Invisible AND unclickable until revealed. Without this the control still
861
+ answered the pointer while it had no paint, so a click on blank-looking
862
+ space silently copied the command. */
863
+ pointer-events: none;
864
+ transition: opacity var(--cds-copy-reveal, 100ms) cubic-bezier(0.16, 1, 0.3, 1),
865
+ color var(--cds-copy-reveal, 100ms) cubic-bezier(0.16, 1, 0.3, 1);
866
+ }
867
+
868
+ /* HOVER-ONLY, exactly like the read and write cards: the rail is invisible
869
+ until the pointer is on the tool row (or focus is inside it). */
870
+ .dsh-gb-tool:hover .dsh-gb-copy,
871
+ /* HOVER ONLY. The focus-within member kept the control painted after a click
872
+ landed inside the card, until focus moved elsewhere \u2014 the same bug that was
873
+ fixed in the theme rail. The button keeps its own :focus-visible rule so a
874
+ keyboard user still sees it when they tab to it. */
875
+ .dsh-gb-copy:focus-visible {
876
+ opacity: 1;
877
+ pointer-events: auto;
878
+ }
879
+
880
+ .dsh-gb-copy:hover {
881
+ color: var(--cds-copy-ink-hover, var(--dsw-alias-label-primary));
882
+ }
883
+
884
+ .dsh-gb-copy[data-copied="true"] {
885
+ color: var(--dsw-alias-state-success-primary);
886
+ }
887
+
888
+ .dsh-gb-copy-check {
889
+ font-size: 12px;
890
+ line-height: 1;
891
+ }
892
+
893
+ @media (prefers-reduced-motion: reduce) {
894
+ .dsh-gb-copy {
895
+ transition: none;
896
+ }
897
+ }
898
+
899
+ .dsh-gb-tool-prompt {
900
+ flex: none;
901
+ color: var(--dsh-gb-punctuation);
902
+ user-select: none;
903
+ }
904
+
905
+
906
+
907
+ .dsh-gb-tool-command [data-token="command"],
908
+ .dsh-gb-tool-command [data-token="flag"] {
909
+ color: var(--dsh-gb-command);
910
+ }
911
+
912
+ .dsh-gb-tool-command [data-token="arg"] {
913
+ color: var(--dsh-gb-argument);
914
+ }
915
+
916
+ .dsh-gb-tool-command [data-token="operator"] {
917
+ color: var(--dsh-gb-punctuation);
918
+ }
919
+
920
+ /* The response: no label, no frame \u2014 the same flowing text the thinking rows
921
+ use. It caps its own height instead of pushing the conversation.
922
+
923
+ Same rung as the request above, deliberately: the mock's output read as a
924
+ different typeface from its command because one was 12/1.6 and the other
925
+ 12/1.65 in a family that falls back per-glyph (Anthropic Mono has no CJK, so
926
+ a Chinese character in the output lands on a different face than the ASCII
927
+ beside it). Sharing one token keeps size, leading and the shorthand's
928
+ letter-spacing reset identical. */
929
+ [data-conversation-content] [data-slot="tool.call.toolview"] .dsh-gb-tool-respond {
930
+ /* NO CAP, NO SCROLLER. The reference clips the output at 160px because its
931
+ card is a fixed-height panel; this one is inline in a transcript, so a long
932
+ output should simply be long. */
933
+ max-height: none;
934
+ overflow: visible;
935
+ /* 5px ABOVE, NOT 8px. The gap between the command fence and its output is its
936
+ own number in the table (docs/tool-cards-bash-terminal.md), separate from
937
+ the 8px top inset inside the fence \u2014 the two were being read as one value,
938
+ which left the output sitting 3px too far from the command it belongs to.
939
+ The flex gap on .dsh-gb-tool-body is 0, so this margin is the only thing
940
+ contributing to that distance. Kept as margin (not row-gap) so the output
941
+ alone moves: a gap would also push a card that has no command above it. */
942
+ margin: 5px 0 0;
943
+ /* 5px / 8px INSET. The output is bare text on the card surface, so without
944
+ its own padding its first glyph starts at the card's left edge while the
945
+ command above it starts 12px in \u2014 the two blocks read as different widths
946
+ of the same column. The smaller inset than the fence is deliberate: the
947
+ output is unboxed, so it lines up optically with the fence's TEXT, not with
948
+ the fence's border. */
949
+ padding: 5px 8px;
950
+ background: transparent;
951
+ border: 0;
952
+ outline: 0;
953
+ color: var(--dsw-alias-label-secondary);
954
+ font: var(--dsw-font-markdown-code-block-small, 12px/1.6 var(--ds-font-family-code));
955
+ /* !important: the theme pins the TEXT family on every non-code descendant of
956
+ the conversation with !important, and a card div is not code/pre \u2014 a
957
+ plain declaration here would lose and the command would render in Sans. */
958
+ font-family: var(--ds-font-family-code) !important;
959
+ white-space: pre-wrap;
960
+ overflow-wrap: anywhere;
961
+ }
962
+ /* ---- typography measured from the reference's terminal view ----------
963
+ .epitaxy-terminal-view is the only place in that app where the leading is
964
+ hard-coded instead of tokenised:
965
+ font-size: var(--cds-font-size-code, .75rem); /* 13px */
966
+ line-height: 1.55; /* = 20.15px, NOT the 19px
967
+ --cds-leading-code token */
968
+ Everything in the view is weight 400 except the tool name, which is the only
969
+ \font-weight declaration in the whole terminal stylesheet:
970
+ .terminal-tool-name { font-weight: 600 }
971
+ Letter-spacing is 0 everywhere (0 occurrences in the CSS; xterm default), and
972
+ the view deliberately does NOT use abular-nums. */
973
+
974
+ [data-conversation-content] [data-slot="tool.call.toolview"] .dsh-gb-tool-title {
975
+ font-weight: 600;
976
+ }
977
+
978
+ /* EVERY DESCENDANT TOO (superseded below, kept for the record). The theme pins the prose family with a rule that
979
+ MATCHES the token spans inside the command (conversation content, minus
980
+ code/pre/kbd/samp). Inheritance loses to any matching rule, so the box was
981
+ mono while its characters were sans \u2014 the "still sans" report. */
982
+ [data-conversation-content] [data-slot="tool.call.toolview"] .dsh-gb-tool-request span,
983
+ [data-conversation-content] [data-slot="tool.call.toolview"] .dsh-gb-tool-respond span,
984
+ [data-conversation-content] [data-slot="tool.call.toolview"] .dsh-gb-tool-request code,
985
+ [data-conversation-content] [data-slot="tool.call.toolview"] .dsh-gb-tool-request,
986
+ [data-conversation-content] [data-slot="tool.call.toolview"] .dsh-gb-tool-respond {
987
+ /* 13px PLUS THE USER'S DELTA. A literal 13px here ignored the code-size
988
+ setting entirely: the code block beside it grew to 14px while the command
989
+ card stayed 13px. --dsh-code-font-delta is what the system-fonts plugin
990
+ drives, and 1.55 keeps the measured ratio as the size changes. */
991
+ /* FIXED. Only inline code follows the code-size setting; a command block is
992
+ pinned so its leading stays tuned to the card. */
993
+ font-size: 12px;
994
+ line-height: 17px;
995
+ font-weight: 400;
996
+ letter-spacing: 0;
997
+ }
998
+ /* THE OUTPUT CARD IS ONE PIXEL TIGHTER: 12/16, matching the reference's
999
+ text-code row, while the command and diff surfaces stay at 12/17.
1000
+ NO BACKTICKS IN THIS COMMENT: the whole sheet is a template literal, so a
1001
+ stray backtick ends the string and the words after it become CODE \u2014 that is
1002
+ exactly how the text-code row name here shipped a bundle that threw
1003
+ "code is not defined" and failed the entire web boot. */
1004
+ [data-conversation-content] [data-slot="tool.call.toolview"] .dsh-gb-tool-respond {
1005
+ line-height: 16px;
1006
+ }
1007
+ [data-conversation-content] [data-slot="tool.call.toolview"] .dsh-gb-tool-request span,
1008
+ [data-conversation-content] [data-slot="tool.call.toolview"] .dsh-gb-tool-respond span,
1009
+ [data-conversation-content] [data-slot="tool.call.toolview"] .dsh-gb-tool-request code {
1010
+ font-family: var(--ds-font-family-code) !important;
1011
+ font-size: inherit;
1012
+ }
1013
+ /* CODE IS MONO, THE ROW TITLE IS NOT. This used to be scoped to the whole
1014
+ card with a universal child selector, which also caught .dsh-gb-tool-title
1015
+ and turned the tool name into monospace. Only the command box and the output
1016
+ box are code.
1017
+ NO BACKTICKS IN THIS COMMENT. Writing that selector name here in a code span
1018
+ ended the enclosing template literal: the sheet evaluated to a string times a
1019
+ string, i.e. NaN, installCss threw on CSS.trim, and apply() died BEFORE the
1020
+ tool.call.toolview registrations below it \u2014 the shell card silently fell back
1021
+ to the host's generic card with no error anywhere. This is the third variant
1022
+ of the same mistake in this file. */
1023
+ [data-conversation-content] [data-slot="tool.call.toolview"] .dsh-gb-tool-request,
1024
+ [data-conversation-content] [data-slot="tool.call.toolview"] .dsh-gb-tool-request *,
1025
+ [data-conversation-content] [data-slot="tool.call.toolview"] .dsh-gb-tool-respond,
1026
+ [data-conversation-content] [data-slot="tool.call.toolview"] .dsh-gb-tool-respond *:not(svg) {
1027
+ font-family: var(--ds-font-family-code) !important;
1028
+ }
1029
+ `;
1030
+ function installCss(doc) {
1031
+ if (doc === void 0 || doc === null || typeof doc.createElement !== "function") {
1032
+ return function disposeWithoutDocument() {
1033
+ };
1034
+ }
1035
+ if (CSS.trim() === "") return function disposeWithoutCss() {
1036
+ };
1037
+ const previous = doc.querySelector(`style[${STYLE_MARKER}]`);
1038
+ if (previous !== null) previous.remove();
1039
+ const tag = doc.createElement("style");
1040
+ tag.setAttribute(STYLE_MARKER, "");
1041
+ tag.textContent = CSS;
1042
+ doc.head.append(tag);
1043
+ return function dispose() {
1044
+ tag.remove();
1045
+ };
1046
+ }
1047
+
1048
+ // client/src/lifecycle.js
1049
+ var MODE_EVENT = "git-bash/mode";
1050
+ var MODE_PATH = "/dsh-git-bash/mode";
1051
+ var MODE_MARKER_HEADER = "x-dsh-git-bash";
1052
+ function createLifecycle({ React: React2, primitives: primitives2 }) {
1053
+ const ShellRow = createSettingsRow({ React: React2, primitives: primitives2 });
1054
+ const RunShellRow = createRunShellRow({ React: React2 });
1055
+ return function apply2(ctx) {
1056
+ ctx.effect(() => {
1057
+ return ctx.locale.register(LOCALE_NS, { en, zh });
1058
+ }, "dsh-git-bash: strings");
1059
+ ctx.effect(
1060
+ () => installCss(typeof document === "undefined" ? void 0 : document),
1061
+ "dsh-git-bash: row stylesheet"
1062
+ );
1063
+ const storage = typeof localStorage === "undefined" ? void 0 : localStorage;
1064
+ const shellInfo = createShellInfoStore(
1065
+ typeof globalThis === "undefined" ? void 0 : globalThis
1066
+ );
1067
+ ctx.effect(() => () => shellInfo.dispose(), "dsh-git-bash: shell info store");
1068
+ const modeStore = createModeStore(storage);
1069
+ ctx.effect(() => () => modeStore.dispose(), "dsh-git-bash: mode store");
1070
+ const publish = () => {
1071
+ const mode = modeStore.getSnapshot();
1072
+ ctx.emit(MODE_EVENT, { mode });
1073
+ if (typeof fetch !== "function") return;
1074
+ try {
1075
+ void fetch(MODE_PATH, {
1076
+ method: "POST",
1077
+ credentials: "same-origin",
1078
+ keepalive: true,
1079
+ headers: {
1080
+ "content-type": "application/json",
1081
+ [MODE_MARKER_HEADER]: "1"
1082
+ },
1083
+ body: JSON.stringify({ mode })
1084
+ }).catch(() => {
1085
+ });
1086
+ } catch (error) {
1087
+ }
1088
+ };
1089
+ publish();
1090
+ ctx.effect(() => modeStore.subscribe(publish), "dsh-git-bash: publish mode");
1091
+ ctx.slots.inject("settings.general.item", () => ctx.slots.register({
1092
+ name: "settings.general.item",
1093
+ id: SETTINGS_ROW_ID,
1094
+ order: 43,
1095
+ locale: LOCALE_NS,
1096
+ // The stores as ORDINARY props, not a `hooks` entry: a function component
1097
+ // receives props, while `hooks:` builds a `useName(selector)` prop that
1098
+ // only a framework-aware occupant reads. See `store.js`.
1099
+ inject: () => ({
1100
+ modeStore,
1101
+ shellInfoStore: shellInfo,
1102
+ setMode: (value) => modeStore.set(value)
1103
+ })
1104
+ }, ShellRow));
1105
+ for (const wireName of ["run_shell", "pwsh", "bash"]) {
1106
+ ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
1107
+ name: "tool.call.toolview",
1108
+ key: wireName,
1109
+ priority: 100,
1110
+ locale: LOCALE_NS
1111
+ }, RunShellRow));
1112
+ }
1113
+ };
1114
+ }
1115
+
1116
+ // client/src/index.js
1117
+ var name = PLUGIN_ID;
1118
+ var inject = ["slots", "locale"];
1119
+ var React = null;
1120
+ try {
1121
+ React = require("react");
1122
+ } catch (error) {
1123
+ React = null;
1124
+ }
1125
+ var primitives = null;
1126
+ try {
1127
+ primitives = require("@deepseek-ai/dsh-client-ui-primitives");
1128
+ } catch (error) {
1129
+ primitives = null;
1130
+ }
1131
+ var apply = React === null ? function applyWithoutReact() {
1132
+ } : createLifecycle({ React, primitives });
1133
+ return module.exports
1134
+ },
1135
+ })