@hwfee/dsh-git-bash 0.1.0 → 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 +59 -18
- package/client/client.js +633 -2
- package/lib/index.js +622 -36
- package/package.json +2 -2
package/client/client.js
CHANGED
|
@@ -66,7 +66,12 @@ window.__ModuleLoader__.load({
|
|
|
66
66
|
"shell.reason.gitbash-not-found": "Git Bash not found. Install Git for Windows, or set DSH_GIT_BASH.",
|
|
67
67
|
"shell.reason.pwsh-not-found": "PowerShell not found. Install PowerShell 7 or Windows PowerShell.",
|
|
68
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."
|
|
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"
|
|
70
75
|
};
|
|
71
76
|
var zh = {
|
|
72
77
|
"shell.title": "\u9ED8\u8BA4 shell",
|
|
@@ -84,7 +89,11 @@ window.__ModuleLoader__.load({
|
|
|
84
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",
|
|
85
90
|
"shell.reason.pwsh-not-found": "\u6CA1\u627E\u5230 PowerShell\u3002\u8BF7\u5B89\u88C5 PowerShell 7 \u6216 Windows PowerShell\u3002",
|
|
86
91
|
"shell.reason.no-shell-for-platform": "Windows PowerShell \u53EA\u5728 Windows \u4E0A\u53EF\u7528\u3002",
|
|
87
|
-
"shell.reason.no-shell-found": "\u6CA1\u6709\u53EF\u7528\u7684 shell\u3002\u8BF7\u5B89\u88C5 Git for Windows\uFF0C\u6216\u8BBE\u7F6E DSH_GIT_BASH\u3002"
|
|
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"
|
|
88
97
|
};
|
|
89
98
|
|
|
90
99
|
// lib/mode.js
|
|
@@ -303,6 +312,235 @@ window.__ModuleLoader__.load({
|
|
|
303
312
|
return ShellRow;
|
|
304
313
|
}
|
|
305
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
|
+
|
|
306
544
|
// client/src/styles.js
|
|
307
545
|
var STYLE_MARKER = "data-dsh-git-bash-style";
|
|
308
546
|
var CSS = `
|
|
@@ -406,12 +644,396 @@ window.__ModuleLoader__.load({
|
|
|
406
644
|
display: inline-flex;
|
|
407
645
|
color: var(--dsw-alias-brand-primary);
|
|
408
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
|
+
}
|
|
409
1029
|
`;
|
|
410
1030
|
function installCss(doc) {
|
|
411
1031
|
if (doc === void 0 || doc === null || typeof doc.createElement !== "function") {
|
|
412
1032
|
return function disposeWithoutDocument() {
|
|
413
1033
|
};
|
|
414
1034
|
}
|
|
1035
|
+
if (CSS.trim() === "") return function disposeWithoutCss() {
|
|
1036
|
+
};
|
|
415
1037
|
const previous = doc.querySelector(`style[${STYLE_MARKER}]`);
|
|
416
1038
|
if (previous !== null) previous.remove();
|
|
417
1039
|
const tag = doc.createElement("style");
|
|
@@ -429,6 +1051,7 @@ window.__ModuleLoader__.load({
|
|
|
429
1051
|
var MODE_MARKER_HEADER = "x-dsh-git-bash";
|
|
430
1052
|
function createLifecycle({ React: React2, primitives: primitives2 }) {
|
|
431
1053
|
const ShellRow = createSettingsRow({ React: React2, primitives: primitives2 });
|
|
1054
|
+
const RunShellRow = createRunShellRow({ React: React2 });
|
|
432
1055
|
return function apply2(ctx) {
|
|
433
1056
|
ctx.effect(() => {
|
|
434
1057
|
return ctx.locale.register(LOCALE_NS, { en, zh });
|
|
@@ -479,6 +1102,14 @@ window.__ModuleLoader__.load({
|
|
|
479
1102
|
setMode: (value) => modeStore.set(value)
|
|
480
1103
|
})
|
|
481
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
|
+
}
|
|
482
1113
|
};
|
|
483
1114
|
}
|
|
484
1115
|
|