dsh-windows-mcp 0.2.0 → 0.2.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/lib/client.js CHANGED
@@ -32,6 +32,14 @@ window.__ModuleLoader__.load({
32
32
  var PREPARE_CANCEL_PATH = "/dsh-windows-mcp/prepare/cancel";
33
33
  var BRIDGE_PATH = "/dsh-windows-mcp/bridge";
34
34
  var UPDATE_PATH = "/dsh-windows-mcp/update";
35
+ var SETTINGS_PATH = "/dsh-windows-mcp/settings";
36
+ var SETTINGS_SLOT = "settings.section";
37
+ var SETTINGS_ENTRY_ID = "windows-mcp-settings";
38
+ var SETTINGS_ORDER = 62;
39
+ var SETTINGS_NAV_ICON_MARKER = "data-dwmc-nav-icon";
40
+ var SETTINGS_NAV_ICON_STYLE_ID = PACKAGE_ID + "-settings-nav-icon-styles";
41
+ var SETTINGS_NAV_ROW_SELECTOR = '[role="dialog"] nav button';
42
+ var REPO_URL = "https://github.com/helloHub1212/dsh-windows-mcp";
35
43
  var STYLE_ID = PACKAGE_ID + "-computer-use-styles";
36
44
  var LABEL = "Computer Use";
37
45
  /** Only `slots` is injected: it is certainly provided, and a declaration for
@@ -91,6 +99,35 @@ window.__ModuleLoader__.load({
91
99
  "color:var(--dsw-alias-label-secondary);white-space:pre-wrap;word-break:break-all}",
92
100
  ".dwmc-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px}",
93
101
  "@media (prefers-reduced-motion:reduce){.dwmc-progress>i{animation:none}}",
102
+ /* Settings page: plain cards, one control per row, theme tokens only. */
103
+ ".dwmc-set{display:flex;flex-direction:column;gap:18px;max-width:720px;",
104
+ "color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.55}",
105
+ ".dwmc-set__head{display:flex;flex-direction:column;gap:4px}",
106
+ ".dwmc-set__title{margin:0;font-size:16px;font-weight:600}",
107
+ ".dwmc-set__sub{margin:0;font-size:12px;color:var(--dsw-alias-label-secondary)}",
108
+ ".dwmc-card{border:1px solid var(--dsw-alias-border-l1);border-radius:12px;",
109
+ "background:var(--dsw-alias-bg-layer-1);padding:14px 16px;display:flex;flex-direction:column;gap:12px}",
110
+ ".dwmc-card__title{margin:0;font-size:13px;font-weight:600}",
111
+ ".dwmc-field{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap}",
112
+ ".dwmc-field__label{display:flex;flex-direction:column;gap:2px;min-width:180px}",
113
+ ".dwmc-field__hint{font-size:12px;color:var(--dsw-alias-label-secondary)}",
114
+ ".dwmc-input,.dwmc-select{font:inherit;font-size:12px;color:var(--dsw-alias-label-primary);",
115
+ "background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l1);",
116
+ "border-radius:8px;padding:5px 8px;min-width:120px}",
117
+ ".dwmc-input:focus-visible,.dwmc-select:focus-visible{outline:2px solid",
118
+ "color-mix(in srgb,var(--dsw-alias-brand-primary) 45%,transparent);outline-offset:1px}",
119
+ ".dwmc-checks{display:flex;flex-wrap:wrap;gap:6px 14px}",
120
+ ".dwmc-check{display:inline-flex;align-items:center;gap:6px;font-size:12px;",
121
+ "color:var(--dsw-alias-label-primary);cursor:pointer}",
122
+ ".dwmc-check input{accent-color:var(--dsw-alias-brand-primary);margin:0}",
123
+ ".dwmc-set__actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap}",
124
+ ".dwmc-star{display:inline-flex;align-items:center;gap:7px;height:30px;padding:0 14px;font:inherit;",
125
+ "font-size:12px;cursor:pointer;border-radius:8px;border:1px solid transparent;background:transparent;",
126
+ "color:var(--dsw-alias-brand-primary);",
127
+ "box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--dsw-alias-brand-primary) 42%,transparent)}",
128
+ ".dwmc-star:hover{background:color-mix(in srgb,var(--dsw-alias-brand-primary) 12%,transparent)}",
129
+ ".dwmc-star svg{width:14px;height:14px;display:block}",
130
+ ".dwmc-saved{font-size:12px;color:var(--dsw-alias-state-success-primary)}",
94
131
  ].join("");
95
132
 
96
133
  /** Inject the stylesheet once. Failures are cosmetic and never fatal. */
@@ -317,6 +354,37 @@ window.__ModuleLoader__.load({
317
354
  versionInstalled: "版本已切换,重启 DSH 生效。",
318
355
  noPrevious: "没有可回退的版本",
319
356
  updateHint: "更新只下载新版本、旧版本原样保留;出问题一键回退,不需要重装依赖。",
357
+ settingsTitle: "Windows-MCP 参数",
358
+ settingsNav: "桌面控制",
359
+ settingsSub: "调整桥接与更新行为;桥接参数写入配置后需要重启 DSH 才生效。",
360
+ settingsRestart: "桥接参数已写入配置,重启 DSH 后生效。",
361
+ settingsSaved: "已保存",
362
+ settingsSave: "保存",
363
+ settingsSaving: "保存中…",
364
+ settingsRevert: "还原",
365
+ settingsFailed: "保存失败",
366
+ settingsIgnored: "部分取值无效,已回退为默认值:",
367
+ sectionBridge: "桥接(重启 DSH 生效)",
368
+ sectionUpdates: "更新与镜像",
369
+ sectionAbout: "关于",
370
+ timeoutLabel: "工具调用超时",
371
+ timeoutHint: "毫秒,5000–600000。越长越不容易被中断,卡住时也等得越久。",
372
+ scaleLabel: "截图缩放",
373
+ scaleHint: "越小越省视觉 token,细节也越少。",
374
+ backendLabel: "截图后端",
375
+ backendHint: "auto 交给上游挑;dxcam 最快,pillow 最慢但最稳。",
376
+ flashLabel: "截图闪光边框",
377
+ flashHint: "关掉可少一次桌面重绘;默认关闭。",
378
+ excludeLabel: "屏蔽的工具",
379
+ excludeHint: "屏蔽的工具不会出现在模型工具表里,能省上下文。",
380
+ autoCheckLabel: "启动时检查更新",
381
+ autoCheckHint: "每次启动后台查一次最新版,结果缓存 6 小时。",
382
+ mirrorLabel: "首选镜像",
383
+ mirrorHint: "auto 用测速最快的镜像;固定某个可避开波动。",
384
+ starLabel: "点个 star",
385
+ starHint: "觉得有用就给个 star,插件会继续维护。",
386
+ aboutRepo: "仓库",
387
+ aboutVersion: "版本",
320
388
  },
321
389
  en: {
322
390
  title: "Windows desktop automation (Windows-MCP)",
@@ -360,6 +428,37 @@ window.__ModuleLoader__.load({
360
428
  versionInstalled: "Version switched — restart DSH to apply it.",
361
429
  noPrevious: "no previous version to roll back to",
362
430
  updateHint: "An update only downloads the new build; the old one is kept as-is. Roll back with one click — no reinstall.",
431
+ settingsTitle: "Windows-MCP settings",
432
+ settingsNav: "Desktop",
433
+ settingsSub: "Tune the bridge and the update behaviour. Bridge values apply after a DSH restart.",
434
+ settingsRestart: "Bridge values were written to the patch layer — restart DSH to apply them.",
435
+ settingsSaved: "Saved",
436
+ settingsSave: "Save",
437
+ settingsSaving: "Saving…",
438
+ settingsRevert: "Revert",
439
+ settingsFailed: "Could not save",
440
+ settingsIgnored: "Some values were invalid and fell back to the defaults:",
441
+ sectionBridge: "Bridge (applies after a DSH restart)",
442
+ sectionUpdates: "Updates and mirrors",
443
+ sectionAbout: "About",
444
+ timeoutLabel: "Tool call timeout",
445
+ timeoutHint: "Milliseconds, 5000–600000. Longer waits are interrupted less often, and stall longer when something hangs.",
446
+ scaleLabel: "Screenshot scale",
447
+ scaleHint: "Smaller costs fewer vision tokens and shows less detail.",
448
+ backendLabel: "Screenshot backend",
449
+ backendHint: "auto lets the server choose; dxcam is fastest, pillow slowest but most compatible.",
450
+ flashLabel: "Screenshot flash border",
451
+ flashHint: "Turning it off saves one desktop repaint; off by default.",
452
+ excludeLabel: "Hidden tools",
453
+ excludeHint: "Hidden tools stay out of the model's tool table, which saves context.",
454
+ autoCheckLabel: "Check for updates at boot",
455
+ autoCheckHint: "One background check per boot, cached for six hours.",
456
+ mirrorLabel: "Preferred mirror",
457
+ mirrorHint: "auto uses the fastest probe; pinning one avoids jitter.",
458
+ starLabel: "Star on GitHub",
459
+ starHint: "If it helps, a star keeps the plugin maintained.",
460
+ aboutRepo: "Repository",
461
+ aboutVersion: "Version",
363
462
  },
364
463
  };
365
464
 
@@ -641,6 +740,352 @@ window.__ModuleLoader__.load({
641
740
  return React.createElement(WindowsMcpPanel, { __compact: true });
642
741
  }
643
742
 
743
+ /* ------------------------------------------------------------------ *
744
+ * settings page
745
+ * ------------------------------------------------------------------ */
746
+
747
+ var SCALE_CHOICES = [0.25, 0.5, 0.75, 1];
748
+ var BACKEND_CHOICES = ["auto", "dxcam", "mss", "pillow"];
749
+ var MIRROR_CHOICES = ["auto", "ustc", "aliyun", "tencent", "huawei", "nju", "tuna", "pypi"];
750
+ var EXCLUDE_CHOICES = ["App", "PowerShell", "FileSystem"];
751
+
752
+ /** A mutable copy of the host settings, so a form never edits state in place. */
753
+ function cloneSettings(input) {
754
+ var bridge = input && input.bridge ? input.bridge : {};
755
+ var plugin = input && input.plugin ? input.plugin : {};
756
+ return {
757
+ bridge: {
758
+ toolCallTimeoutMs: Number(bridge.toolCallTimeoutMs),
759
+ excludeTools: Array.isArray(bridge.excludeTools) ? bridge.excludeTools.slice() : [],
760
+ screenshotScale: Number(bridge.screenshotScale),
761
+ screenshotBackend: String(bridge.screenshotBackend || "auto"),
762
+ disableFlash: bridge.disableFlash === true,
763
+ },
764
+ plugin: {
765
+ autoCheckUpdates: plugin.autoCheckUpdates === true,
766
+ preferredMirror: String(plugin.preferredMirror || "auto"),
767
+ },
768
+ };
769
+ }
770
+
771
+ /** GET/POST the host settings; only the settings page mounts this. */
772
+ function useSettings() {
773
+ var pair = React.useState({ phase: "loading", view: null, error: null });
774
+ var view = pair[0];
775
+ var setView = pair[1];
776
+ var savedPair = React.useState({ phase: "idle", body: null, error: null });
777
+ var saved = savedPair[0];
778
+ var setSaved = savedPair[1];
779
+
780
+ var load = React.useCallback(function () {
781
+ return fetch(apiUrl(SETTINGS_PATH), { headers: { accept: "application/json" } })
782
+ .then(function (response) {
783
+ return response.json();
784
+ })
785
+ .then(function (body) {
786
+ setView({ phase: "ready", view: body, error: null });
787
+ return body;
788
+ })
789
+ .catch(function (error) {
790
+ setView({ phase: "error", view: null, error: String(error && error.message ? error.message : error) });
791
+ });
792
+ }, []);
793
+
794
+ React.useEffect(
795
+ function () {
796
+ load();
797
+ },
798
+ [load],
799
+ );
800
+
801
+ var save = React.useCallback(function (next) {
802
+ setSaved({ phase: "saving", body: null, error: null });
803
+ return fetch(apiUrl(SETTINGS_PATH), {
804
+ method: "POST",
805
+ headers: { accept: "application/json", "content-type": "application/json" },
806
+ body: JSON.stringify({ settings: next }),
807
+ })
808
+ .then(function (response) {
809
+ return response.json();
810
+ })
811
+ .then(function (body) {
812
+ setView({ phase: "ready", view: body, error: null });
813
+ setSaved({ phase: body && body.ok === false ? "error" : "saved", body: body, error: null });
814
+ return body;
815
+ })
816
+ .catch(function (error) {
817
+ setSaved({ phase: "error", body: null, error: String(error && error.message ? error.message : error) });
818
+ return null;
819
+ });
820
+ }, []);
821
+
822
+ return { view: view, saved: saved, save: save, reload: load };
823
+ }
824
+
825
+ function starIcon() {
826
+ return React.createElement(
827
+ "svg",
828
+ { viewBox: "0 0 16 16", fill: "currentColor", "aria-hidden": "true" },
829
+ React.createElement("path", {
830
+ d: "M8 1.2l2.06 4.18 4.61.67-3.34 3.25.79 4.6L8 11.73l-4.12 2.17.79-4.6L1.33 6.05l4.61-.67z",
831
+ }),
832
+ );
833
+ }
834
+
835
+ /** Settings → left rail: the plugin's parameter page. */
836
+ function SettingsPage() {
837
+ var t = copy();
838
+ var host = useSettings();
839
+ var view = host.view;
840
+ var draftPair = React.useState(null);
841
+ var draft = draftPair[0];
842
+ var setDraft = draftPair[1];
843
+
844
+ var ready = view.phase === "ready" && view.view && view.view.settings ? view.view : null;
845
+
846
+ React.useEffect(
847
+ function () {
848
+ if (ready !== null) setDraft(cloneSettings(ready.settings));
849
+ },
850
+ [ready === null ? null : JSON.stringify(ready.settings)],
851
+ );
852
+
853
+ if (view.phase === "loading") return React.createElement("p", { className: "dwmc-note" }, t.checking);
854
+ if (view.phase === "error") return React.createElement("p", { className: "dwmc-error" }, t.hostDown + " — " + String(view.error || ""));
855
+ if (draft === null || ready === null) return React.createElement("p", { className: "dwmc-note" }, t.checking);
856
+
857
+ var dirty = JSON.stringify(draft) !== JSON.stringify(cloneSettings(ready.settings));
858
+ var saving = host.saved.phase === "saving";
859
+
860
+ var update = function (section, key, value) {
861
+ var next = cloneSettings(draft);
862
+ next[section][key] = value;
863
+ setDraft(next);
864
+ };
865
+ var toggleExclude = function (name) {
866
+ var next = cloneSettings(draft);
867
+ var at = next.bridge.excludeTools.indexOf(name);
868
+ if (at === -1) next.bridge.excludeTools.push(name);
869
+ else next.bridge.excludeTools.splice(at, 1);
870
+ setDraft(next);
871
+ };
872
+ var save = function () {
873
+ host.save(draft).then(function (body) {
874
+ if (body && body.settings) setDraft(cloneSettings(body.settings));
875
+ });
876
+ };
877
+
878
+ var field = function (label, hint, control) {
879
+ return React.createElement(
880
+ "div",
881
+ { className: "dwmc-field", key: label },
882
+ React.createElement(
883
+ "div",
884
+ { className: "dwmc-field__label" },
885
+ React.createElement("span", null, label),
886
+ hint ? React.createElement("span", { className: "dwmc-field__hint" }, hint) : null,
887
+ ),
888
+ control,
889
+ );
890
+ };
891
+ var check = function (label, checked, onChange, key) {
892
+ return React.createElement(
893
+ "label",
894
+ { className: "dwmc-check", key: key || label },
895
+ React.createElement("input", { type: "checkbox", checked: checked === true, onChange: function (event) { onChange(event.target.checked); } }),
896
+ label,
897
+ );
898
+ };
899
+ var card = function (title, children) {
900
+ return React.createElement(
901
+ "section",
902
+ { className: "dwmc-card" },
903
+ React.createElement("h3", { className: "dwmc-card__title" }, title),
904
+ children,
905
+ );
906
+ };
907
+
908
+ var bridgeCard = card(t.sectionBridge, [
909
+ field(
910
+ t.timeoutLabel,
911
+ t.timeoutHint,
912
+ React.createElement("input", {
913
+ className: "dwmc-input",
914
+ type: "number",
915
+ min: 5000,
916
+ max: 600000,
917
+ step: 1000,
918
+ value: String(draft.bridge.toolCallTimeoutMs),
919
+ onChange: function (event) {
920
+ update("bridge", "toolCallTimeoutMs", Number(event.target.value));
921
+ },
922
+ }),
923
+ ),
924
+ field(
925
+ t.scaleLabel,
926
+ t.scaleHint,
927
+ React.createElement(
928
+ "select",
929
+ {
930
+ className: "dwmc-select",
931
+ value: String(draft.bridge.screenshotScale),
932
+ onChange: function (event) {
933
+ update("bridge", "screenshotScale", Number(event.target.value));
934
+ },
935
+ },
936
+ SCALE_CHOICES.map(function (scale) {
937
+ return React.createElement("option", { key: String(scale), value: String(scale) }, String(scale));
938
+ }),
939
+ ),
940
+ ),
941
+ field(
942
+ t.backendLabel,
943
+ t.backendHint,
944
+ React.createElement(
945
+ "select",
946
+ {
947
+ className: "dwmc-select",
948
+ value: draft.bridge.screenshotBackend,
949
+ onChange: function (event) {
950
+ update("bridge", "screenshotBackend", event.target.value);
951
+ },
952
+ },
953
+ BACKEND_CHOICES.map(function (backend) {
954
+ return React.createElement("option", { key: backend, value: backend }, backend);
955
+ }),
956
+ ),
957
+ ),
958
+ field(
959
+ t.flashLabel,
960
+ t.flashHint,
961
+ React.createElement("input", {
962
+ type: "checkbox",
963
+ checked: draft.bridge.disableFlash === false ? false : true,
964
+ onChange: function (event) {
965
+ update("bridge", "disableFlash", event.target.checked);
966
+ },
967
+ }),
968
+ ),
969
+ field(
970
+ t.excludeLabel,
971
+ t.excludeHint,
972
+ React.createElement(
973
+ "div",
974
+ { className: "dwmc-checks" },
975
+ EXCLUDE_CHOICES.map(function (name) {
976
+ return check(name, draft.bridge.excludeTools.indexOf(name) !== -1, function () { toggleExclude(name); }, name);
977
+ }),
978
+ ),
979
+ ),
980
+ ]);
981
+
982
+ var updatesCard = card(t.sectionUpdates, [
983
+ field(
984
+ t.autoCheckLabel,
985
+ t.autoCheckHint,
986
+ React.createElement("input", {
987
+ type: "checkbox",
988
+ checked: draft.plugin.autoCheckUpdates === true,
989
+ onChange: function (event) {
990
+ update("plugin", "autoCheckUpdates", event.target.checked);
991
+ },
992
+ }),
993
+ ),
994
+ field(
995
+ t.mirrorLabel,
996
+ t.mirrorHint,
997
+ React.createElement(
998
+ "select",
999
+ {
1000
+ className: "dwmc-select",
1001
+ value: draft.plugin.preferredMirror,
1002
+ onChange: function (event) {
1003
+ update("plugin", "preferredMirror", event.target.value);
1004
+ },
1005
+ },
1006
+ MIRROR_CHOICES.map(function (mirror) {
1007
+ return React.createElement("option", { key: mirror, value: mirror }, mirror);
1008
+ }),
1009
+ ),
1010
+ ),
1011
+ ]);
1012
+
1013
+ var notes = [];
1014
+ if (host.saved.phase === "saved") notes.push(React.createElement("span", { key: "saved", className: "dwmc-saved" }, t.settingsSaved));
1015
+ if (host.saved.phase === "error") notes.push(React.createElement("span", { key: "err", className: "dwmc-error" }, t.settingsFailed));
1016
+ if (ready.restartRequired && host.saved.phase !== "saving") {
1017
+ notes.push(React.createElement("span", { key: "restart", className: "dwmc-warn" }, t.settingsRestart));
1018
+ }
1019
+ if (host.saved.body && Array.isArray(host.saved.body.problems) && host.saved.body.problems.length > 0) {
1020
+ notes.push(React.createElement("span", { key: "ignored", className: "dwmc-warn" }, t.settingsIgnored + " " + host.saved.body.problems.join(", ")));
1021
+ }
1022
+
1023
+ var aboutCard = card(t.sectionAbout, [
1024
+ React.createElement(
1025
+ "div",
1026
+ { className: "dwmc-rows" },
1027
+ React.createElement("dt", null, t.aboutVersion),
1028
+ React.createElement("dd", { className: "dwmc-mono" }, "v" + String(ready.pluginVersion || "?")),
1029
+ React.createElement("dt", null, t.aboutRepo),
1030
+ React.createElement(
1031
+ "dd",
1032
+ null,
1033
+ React.createElement("a", { className: "dwmc-mono", href: REPO_URL, target: "_blank", rel: "noopener noreferrer" }, REPO_URL.replace("https://", "")),
1034
+ ),
1035
+ ),
1036
+ React.createElement(
1037
+ "div",
1038
+ { className: "dwmc-set__actions" },
1039
+ React.createElement(
1040
+ "button",
1041
+ {
1042
+ type: "button",
1043
+ className: "dwmc-star",
1044
+ onClick: function () {
1045
+ try {
1046
+ window.open(REPO_URL, "_blank", "noopener,noreferrer");
1047
+ } catch (error) {
1048
+ /* a blocked popup is not worth failing the page over */
1049
+ }
1050
+ },
1051
+ },
1052
+ starIcon(),
1053
+ t.starLabel,
1054
+ ),
1055
+ React.createElement("span", { className: "dwmc-note" }, t.starHint),
1056
+ ),
1057
+ ]);
1058
+
1059
+ return React.createElement(
1060
+ "div",
1061
+ { className: "dwmc-set" },
1062
+ React.createElement(
1063
+ "div",
1064
+ { className: "dwmc-set__head" },
1065
+ React.createElement("h2", { className: "dwmc-set__title" }, t.settingsTitle),
1066
+ React.createElement("p", { className: "dwmc-set__sub" }, t.settingsSub),
1067
+ ),
1068
+ bridgeCard,
1069
+ updatesCard,
1070
+ aboutCard,
1071
+ React.createElement(
1072
+ "div",
1073
+ { className: "dwmc-set__actions" },
1074
+ React.createElement(
1075
+ "button",
1076
+ { type: "button", className: "dwmc-btn", onClick: save, disabled: saving || !dirty },
1077
+ saving ? t.settingsSaving : t.settingsSave,
1078
+ ),
1079
+ React.createElement(
1080
+ "button",
1081
+ { type: "button", className: "dwmc-btn dwmc-btn--quiet", onClick: function () { setDraft(cloneSettings(ready.settings)); }, disabled: saving || !dirty },
1082
+ t.settingsRevert,
1083
+ ),
1084
+ notes.length > 0 ? React.createElement("span", { className: "dwmc-set__actions" }, notes) : null,
1085
+ ),
1086
+ );
1087
+ }
1088
+
644
1089
  /* ------------------------------------------------------------------ *
645
1090
  * registration
646
1091
  * ------------------------------------------------------------------ */
@@ -714,6 +1159,135 @@ window.__ModuleLoader__.load({
714
1159
  }
715
1160
  }
716
1161
 
1162
+ /**
1163
+ * Settings → left rail. `settings.section` is a list slot: one page per
1164
+ * registered entry, ordered by `order`, labelled by `label()`.
1165
+ */
1166
+ /**
1167
+ * The glyph of the settings entry: a monitor with a cursor cut out of its
1168
+ * screen ("this page drives the desktop"). `settings.section` has no icon
1169
+ * field, so the entry is painted on the shell's nav row: the row is marked
1170
+ * by matching its visible text against our own localized label, the shell's
1171
+ * gear is hidden and a mask-image painted in `currentColor` takes its place.
1172
+ */
1173
+ function settingsNavMaskSvg() {
1174
+ return [
1175
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="#000">',
1176
+ '<path fill-rule="evenodd" d="M2.6 2.6h10.8c.66 0 1.2.54 1.2 1.2v6.4c0 .66-.54 1.2-1.2 1.2H2.6',
1177
+ 'c-.66 0-1.2-.54-1.2-1.2V3.8c0-.66.54-1.2 1.2-1.2Zm3.1 2.5 4.5 2.85-2.1.36.98 2.06-1.16.55',
1178
+ '-0.97-2.05-1.3 1.05z"/>',
1179
+ '<rect x="7" y="11.2" width="2" height="1.7" rx=".3"/>',
1180
+ '<rect x="5.2" y="12.9" width="5.6" height="1.4" rx=".7"/>',
1181
+ "</svg>",
1182
+ ].join("");
1183
+ }
1184
+
1185
+ /** Encoded as a CSS `mask-image` source; the mask reads alpha only. */
1186
+ function settingsNavMaskUrl(svg) {
1187
+ var source = typeof svg === "string" ? svg : settingsNavMaskSvg();
1188
+ return "data:image/svg+xml," + encodeURIComponent(source).replace(/'/gu, "%27");
1189
+ }
1190
+
1191
+ /**
1192
+ * Whether a nav row is this plugin's own. Pure: the row whose visible text
1193
+ * is the label the shell currently projects, trimmed. An empty label matches
1194
+ * nothing, so a locale that has not resolved yet never marks the whole nav.
1195
+ */
1196
+ function isOwnSettingsNavRow(rowText, wantedLabel) {
1197
+ var wanted = String(wantedLabel === null || wantedLabel === undefined ? "" : wantedLabel).trim();
1198
+ if (wanted.length === 0) return false;
1199
+ return String(rowText === null || rowText === undefined ? "" : rowText).trim() === wanted;
1200
+ }
1201
+
1202
+ /** Stylesheet for the marked row: hide the shell icon, draw ours. */
1203
+ function settingsNavIconCss(maskUrl) {
1204
+ var marker = "[" + SETTINGS_NAV_ICON_MARKER + "]";
1205
+ return [
1206
+ marker + " > svg{display:none}",
1207
+ marker + "::before{content:'';flex:none;width:16px;height:16px;",
1208
+ "background-color:currentColor;",
1209
+ "-webkit-mask-image:url('" + maskUrl + "');mask-image:url('" + maskUrl + "');",
1210
+ "-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;",
1211
+ "-webkit-mask-position:center;mask-position:center;",
1212
+ "-webkit-mask-size:16px 16px;mask-size:16px 16px}",
1213
+ ].join("");
1214
+ }
1215
+
1216
+ /**
1217
+ * Install the nav glyph. Idempotent, and a no-op outside a browser. The
1218
+ * observer follows re-renders and locale switches, so the text and the
1219
+ * glyph can never disagree; the marker and the stylesheet belong to the
1220
+ * fiber when the client context offers `effect`.
1221
+ */
1222
+ function installSettingsNavIcon(ctx) {
1223
+ if (typeof document === "undefined" || typeof MutationObserver !== "function") return null;
1224
+ var install = function () {
1225
+ var existing = document.getElementById(SETTINGS_NAV_ICON_STYLE_ID);
1226
+ if (existing !== null) return function () {};
1227
+ var style = document.createElement("style");
1228
+ style.id = SETTINGS_NAV_ICON_STYLE_ID;
1229
+ style.textContent = settingsNavIconCss(settingsNavMaskUrl());
1230
+ document.head.appendChild(style);
1231
+ var disposed = false;
1232
+ var scheduled = false;
1233
+ var sync = function () {
1234
+ scheduled = false;
1235
+ if (disposed) return;
1236
+ var wanted = copy().settingsNav;
1237
+ var rows = document.querySelectorAll(SETTINGS_NAV_ROW_SELECTOR);
1238
+ for (var index = 0; index < rows.length; index += 1) {
1239
+ var row = rows[index];
1240
+ if (isOwnSettingsNavRow(row.textContent, wanted)) row.setAttribute(SETTINGS_NAV_ICON_MARKER, "");
1241
+ else row.removeAttribute(SETTINGS_NAV_ICON_MARKER);
1242
+ }
1243
+ };
1244
+ var schedule = function () {
1245
+ if (scheduled || disposed) return;
1246
+ scheduled = true;
1247
+ if (typeof queueMicrotask === "function") queueMicrotask(sync);
1248
+ else setTimeout(sync, 0);
1249
+ };
1250
+ sync();
1251
+ var observer = new MutationObserver(schedule);
1252
+ observer.observe(document.body, { childList: true, subtree: true, characterData: true });
1253
+ return function () {
1254
+ disposed = true;
1255
+ observer.disconnect();
1256
+ var marked = document.querySelectorAll("[" + SETTINGS_NAV_ICON_MARKER + "]");
1257
+ for (var at = 0; at < marked.length; at += 1) marked[at].removeAttribute(SETTINGS_NAV_ICON_MARKER);
1258
+ if (style.parentNode !== null) style.parentNode.removeChild(style);
1259
+ };
1260
+ };
1261
+ if (ctx && typeof ctx.effect === "function") return ctx.effect(install, "dsh-windows-mcp: settings nav icon");
1262
+ return install();
1263
+ }
1264
+
1265
+ function registerSettingsSection(ctx) {
1266
+ try {
1267
+ ctx.slots.inject(SETTINGS_SLOT, function () {
1268
+ return ctx.slots.register(
1269
+ {
1270
+ name: SETTINGS_SLOT,
1271
+ id: SETTINGS_ENTRY_ID,
1272
+ order: SETTINGS_ORDER,
1273
+ label: function () {
1274
+ return copy().settingsNav;
1275
+ },
1276
+ },
1277
+ function SettingsSection() {
1278
+ return React.createElement(SettingsPage, null);
1279
+ },
1280
+ );
1281
+ });
1282
+ } catch (error) {
1283
+ try {
1284
+ console.error("[" + PACKAGE_ID + "] settings section registration failed:", error);
1285
+ } catch (ignored) {
1286
+ /* nothing to do */
1287
+ }
1288
+ }
1289
+ }
1290
+
717
1291
  /**
718
1292
  * @param ctx - client cordis context.
719
1293
  */
@@ -721,11 +1295,30 @@ window.__ModuleLoader__.load({
721
1295
  installStyles();
722
1296
  registerComposerPill(ctx);
723
1297
  registerBundleSlots(ctx);
1298
+ registerSettingsSection(ctx);
1299
+ installSettingsNavIcon(ctx);
724
1300
  }
725
1301
 
726
1302
  exports.apply = apply;
727
1303
  exports.inject = inject;
728
- exports.__internals = { CSS: CSS, apiUrl: apiUrl, tooltipFor: tooltipFor, LABEL: LABEL, STATE_PATH: STATE_PATH, UPDATE_PATH: UPDATE_PATH };
1304
+ exports.__internals = {
1305
+ CSS: CSS,
1306
+ apiUrl: apiUrl,
1307
+ tooltipFor: tooltipFor,
1308
+ LABEL: LABEL,
1309
+ STATE_PATH: STATE_PATH,
1310
+ UPDATE_PATH: UPDATE_PATH,
1311
+ SETTINGS_PATH: SETTINGS_PATH,
1312
+ REPO_URL: REPO_URL,
1313
+ SETTINGS_SLOT: SETTINGS_SLOT,
1314
+ SETTINGS_NAV_ICON: settingsNavMaskSvg(),
1315
+ SETTINGS_NAV_ICON_MARKER: SETTINGS_NAV_ICON_MARKER,
1316
+ isOwnSettingsNavRow: isOwnSettingsNavRow,
1317
+ settingsNavIconCss: settingsNavIconCss,
1318
+ settingsNavMaskUrl: settingsNavMaskUrl,
1319
+ cloneSettings: cloneSettings,
1320
+ SettingsPage: SettingsPage,
1321
+ };
729
1322
  return module.exports;
730
1323
  },
731
1324
  });