@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.
- package/README.md +105 -2
- package/client/client.js +1135 -0
- package/cordis.patch.yml +8 -0
- package/icon.svg +16 -0
- package/lib/index.js +1144 -0
- package/lib/mode-route.js +204 -0
- package/lib/mode.js +244 -0
- package/lib/resolve.js +161 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +62 -4
package/client/client.js
ADDED
|
@@ -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
|
+
})
|