@comecaramelos/dsh-chrome-mcp 0.0.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/README.md +22 -78
  2. package/assets/dsh-chrome-mcp-social.png +0 -0
  3. package/assets/icon.svg +71 -0
  4. package/assets/prelaunch-windows-chrome.html +277 -0
  5. package/lib/client.js +1433 -568
  6. package/lib/client.js.map +7 -0
  7. package/lib/host/apply.d.ts +3 -0
  8. package/lib/host/apply.d.ts.map +1 -0
  9. package/lib/host/apply.js +31 -0
  10. package/lib/host/apply.js.map +1 -0
  11. package/lib/host/bridge.d.ts +65 -0
  12. package/lib/host/bridge.d.ts.map +1 -0
  13. package/lib/host/bridge.js +176 -0
  14. package/lib/host/bridge.js.map +1 -0
  15. package/lib/host/catalog.d.ts +31 -0
  16. package/lib/host/catalog.d.ts.map +1 -0
  17. package/lib/host/catalog.js +92 -0
  18. package/lib/host/catalog.js.map +1 -0
  19. package/lib/host/constants.d.ts +229 -0
  20. package/lib/host/constants.d.ts.map +1 -0
  21. package/lib/host/constants.js +242 -0
  22. package/lib/host/constants.js.map +1 -0
  23. package/lib/host/controller/bridge-run.d.ts +38 -0
  24. package/lib/host/controller/bridge-run.d.ts.map +1 -0
  25. package/lib/host/controller/bridge-run.js +69 -0
  26. package/lib/host/controller/bridge-run.js.map +1 -0
  27. package/lib/host/controller/captures.d.ts +22 -0
  28. package/lib/host/controller/captures.d.ts.map +1 -0
  29. package/lib/host/controller/captures.js +60 -0
  30. package/lib/host/controller/captures.js.map +1 -0
  31. package/lib/host/controller/commits.d.ts +4 -0
  32. package/lib/host/controller/commits.d.ts.map +1 -0
  33. package/lib/host/controller/commits.js +75 -0
  34. package/lib/host/controller/commits.js.map +1 -0
  35. package/lib/host/controller/controller.d.ts +40 -0
  36. package/lib/host/controller/controller.d.ts.map +1 -0
  37. package/lib/host/controller/controller.js +167 -0
  38. package/lib/host/controller/controller.js.map +1 -0
  39. package/lib/host/controller/index.d.ts +26 -0
  40. package/lib/host/controller/index.d.ts.map +1 -0
  41. package/lib/host/controller/index.js +25 -0
  42. package/lib/host/controller/index.js.map +1 -0
  43. package/lib/host/controller/notes.d.ts +26 -0
  44. package/lib/host/controller/notes.d.ts.map +1 -0
  45. package/lib/host/controller/notes.js +49 -0
  46. package/lib/host/controller/notes.js.map +1 -0
  47. package/lib/host/controller/persist.d.ts +9 -0
  48. package/lib/host/controller/persist.d.ts.map +1 -0
  49. package/lib/host/controller/persist.js +51 -0
  50. package/lib/host/controller/persist.js.map +1 -0
  51. package/lib/host/controller/runs.d.ts +56 -0
  52. package/lib/host/controller/runs.d.ts.map +1 -0
  53. package/lib/host/controller/runs.js +211 -0
  54. package/lib/host/controller/runs.js.map +1 -0
  55. package/lib/host/controller/state.d.ts +70 -0
  56. package/lib/host/controller/state.d.ts.map +1 -0
  57. package/lib/host/controller/state.js +2 -0
  58. package/lib/host/controller/state.js.map +1 -0
  59. package/lib/host/controller/windows-run.d.ts +9 -0
  60. package/lib/host/controller/windows-run.d.ts.map +1 -0
  61. package/lib/host/controller/windows-run.js +78 -0
  62. package/lib/host/controller/windows-run.js.map +1 -0
  63. package/lib/host/discovery/candidates.d.ts +37 -0
  64. package/lib/host/discovery/candidates.d.ts.map +1 -0
  65. package/lib/host/discovery/candidates.js +56 -0
  66. package/lib/host/discovery/candidates.js.map +1 -0
  67. package/lib/host/discovery/index.d.ts +18 -0
  68. package/lib/host/discovery/index.d.ts.map +1 -0
  69. package/lib/host/discovery/index.js +18 -0
  70. package/lib/host/discovery/index.js.map +1 -0
  71. package/lib/host/discovery/probe.d.ts +18 -0
  72. package/lib/host/discovery/probe.d.ts.map +1 -0
  73. package/lib/host/discovery/probe.js +97 -0
  74. package/lib/host/discovery/probe.js.map +1 -0
  75. package/lib/host/discovery/scan.d.ts +67 -0
  76. package/lib/host/discovery/scan.d.ts.map +1 -0
  77. package/lib/host/discovery/scan.js +169 -0
  78. package/lib/host/discovery/scan.js.map +1 -0
  79. package/lib/host/flags.d.ts +42 -0
  80. package/lib/host/flags.d.ts.map +1 -0
  81. package/lib/host/flags.js +118 -0
  82. package/lib/host/flags.js.map +1 -0
  83. package/lib/host/platform.d.ts +38 -0
  84. package/lib/host/platform.d.ts.map +1 -0
  85. package/lib/host/platform.js +64 -0
  86. package/lib/host/platform.js.map +1 -0
  87. package/lib/host/plugin-meta.d.ts +12 -0
  88. package/lib/host/plugin-meta.d.ts.map +1 -0
  89. package/lib/host/plugin-meta.js +12 -0
  90. package/lib/host/plugin-meta.js.map +1 -0
  91. package/lib/host/schema.d.ts +307 -0
  92. package/lib/host/schema.d.ts.map +1 -0
  93. package/lib/host/schema.js +171 -0
  94. package/lib/host/schema.js.map +1 -0
  95. package/lib/host/settings.d.ts +9 -0
  96. package/lib/host/settings.d.ts.map +1 -0
  97. package/lib/host/settings.js +70 -0
  98. package/lib/host/settings.js.map +1 -0
  99. package/lib/host/status.d.ts +28 -0
  100. package/lib/host/status.d.ts.map +1 -0
  101. package/lib/host/status.js +60 -0
  102. package/lib/host/status.js.map +1 -0
  103. package/lib/host/tool.d.ts +59 -0
  104. package/lib/host/tool.d.ts.map +1 -0
  105. package/lib/host/tool.js +51 -0
  106. package/lib/host/tool.js.map +1 -0
  107. package/lib/host/types/config.d.ts +47 -0
  108. package/lib/host/types/config.d.ts.map +1 -0
  109. package/lib/host/types/config.js +2 -0
  110. package/lib/host/types/config.js.map +1 -0
  111. package/lib/host/types/executable.d.ts +30 -0
  112. package/lib/host/types/executable.d.ts.map +1 -0
  113. package/lib/host/types/executable.js +9 -0
  114. package/lib/host/types/executable.js.map +1 -0
  115. package/lib/host/types/index.d.ts +23 -0
  116. package/lib/host/types/index.d.ts.map +1 -0
  117. package/lib/host/types/index.js +2 -0
  118. package/lib/host/types/index.js.map +1 -0
  119. package/lib/host/types/plugin.d.ts +38 -0
  120. package/lib/host/types/plugin.d.ts.map +1 -0
  121. package/lib/host/types/plugin.js +10 -0
  122. package/lib/host/types/plugin.js.map +1 -0
  123. package/lib/host/types/settings.d.ts +127 -0
  124. package/lib/host/types/settings.d.ts.map +1 -0
  125. package/lib/host/types/settings.js +2 -0
  126. package/lib/host/types/settings.js.map +1 -0
  127. package/lib/host/types/stderr.d.ts +53 -0
  128. package/lib/host/types/stderr.d.ts.map +1 -0
  129. package/lib/host/types/stderr.js +9 -0
  130. package/lib/host/types/stderr.js.map +1 -0
  131. package/lib/host/types/windows.d.ts +39 -0
  132. package/lib/host/types/windows.d.ts.map +1 -0
  133. package/lib/host/types/windows.js +9 -0
  134. package/lib/host/types/windows.js.map +1 -0
  135. package/lib/host/values.d.ts +89 -0
  136. package/lib/host/values.d.ts.map +1 -0
  137. package/lib/host/values.js +212 -0
  138. package/lib/host/values.js.map +1 -0
  139. package/lib/host/windows/connect-flags.d.ts +16 -0
  140. package/lib/host/windows/connect-flags.d.ts.map +1 -0
  141. package/lib/host/windows/connect-flags.js +67 -0
  142. package/lib/host/windows/connect-flags.js.map +1 -0
  143. package/lib/host/windows/index.d.ts +31 -0
  144. package/lib/host/windows/index.d.ts.map +1 -0
  145. package/lib/host/windows/index.js +30 -0
  146. package/lib/host/windows/index.js.map +1 -0
  147. package/lib/host/windows/landing.d.ts +47 -0
  148. package/lib/host/windows/landing.d.ts.map +1 -0
  149. package/lib/host/windows/landing.js +174 -0
  150. package/lib/host/windows/landing.js.map +1 -0
  151. package/lib/host/windows/link.d.ts +11 -0
  152. package/lib/host/windows/link.d.ts.map +1 -0
  153. package/lib/host/windows/link.js +52 -0
  154. package/lib/host/windows/link.js.map +1 -0
  155. package/lib/host/windows/paths.d.ts +43 -0
  156. package/lib/host/windows/paths.d.ts.map +1 -0
  157. package/lib/host/windows/paths.js +121 -0
  158. package/lib/host/windows/paths.js.map +1 -0
  159. package/lib/host/windows/run.d.ts +74 -0
  160. package/lib/host/windows/run.d.ts.map +1 -0
  161. package/lib/host/windows/run.js +210 -0
  162. package/lib/host/windows/run.js.map +1 -0
  163. package/lib/host/windows/spawn.d.ts +22 -0
  164. package/lib/host/windows/spawn.d.ts.map +1 -0
  165. package/lib/host/windows/spawn.js +73 -0
  166. package/lib/host/windows/spawn.js.map +1 -0
  167. package/lib/index.d.ts +86 -0
  168. package/lib/index.d.ts.map +1 -0
  169. package/lib/index.js +72 -625
  170. package/lib/index.js.map +1 -0
  171. package/package.json +26 -4
package/lib/client.js CHANGED
@@ -4,582 +4,1447 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ "use strict";
8
+ var __create = Object.create;
9
+ var __defProp = Object.defineProperty;
10
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
11
+ var __getOwnPropNames = Object.getOwnPropertyNames;
12
+ var __getProtoOf = Object.getPrototypeOf;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
27
+ // If the importer is in node compatibility mode or this is not an ESM
28
+ // file that has been converted to a CommonJS file using a Babel-
29
+ // compatible transform (i.e. "__esModule" has not been set), then set
30
+ // "default" to the CommonJS "module.exports" for node compatibility.
31
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
32
+ mod
33
+ ));
34
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
7
35
 
8
- var react = require("react");
9
- var reactJsx = require("react/jsx-runtime");
10
- var clientStore = require("@deepseek-ai/dsh-client-store");
11
- // NOTE: no UI primitives required — the extra flags editor is a plain
12
- // textarea writing through the controller (props.saveFlags), not the
13
- // shell Menu primitive the reference used for its profile picker.
36
+ // src/client/index.ts
37
+ var index_exports = {};
38
+ __export(index_exports, {
39
+ CHECK_POLL_TICK_MS: () => CHECK_POLL_TICK_MS,
40
+ CHECK_POLL_TIMEOUT_MS: () => CHECK_POLL_TIMEOUT_MS,
41
+ apply: () => apply,
42
+ inject: () => inject
43
+ });
44
+ module.exports = __toCommonJS(index_exports);
45
+
46
+ // src/client/card/index.ts
47
+ var react4 = __toESM(require("react"), 1);
48
+ var import_jsx_runtime6 = require("react/jsx-runtime");
49
+
50
+ // dsh-css:/home/roberto/dev/dsh/dsh-chrome-mcp/src/client/styles/ChromeMcpCard.module.css
51
+ var cssText = '.e5pfpa_card{border:.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-layer-3);border-radius:16px;list-style:none;transition:border-color .16s,background .16s}.e5pfpa_card:hover{border-color:var(--dsw-alias-label-dimmed)}.e5pfpa_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.e5pfpa_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.e5pfpa_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.e5pfpa_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.e5pfpa_nameRow{align-items:center;gap:8px;min-width:0;display:flex}.e5pfpa_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.e5pfpa_desc{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.e5pfpa_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.e5pfpa_chevronOpen{transform:rotate(180deg)}.e5pfpa_body{border-top:.5px solid var(--dsw-alias-border-l2);display:flex;flex-direction:column;gap:10px;margin:0 16px;padding:12px 0}.e5pfpa_field{align-items:flex-start;gap:4px;display:flex;flex-direction:column;width:100%}.e5pfpa_fieldRow{align-items:center;gap:8px;display:flex;width:100%}.e5pfpa_fieldTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:22px;flex:1;min-width:0}.e5pfpa_fieldDesc{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400;line-height:18px;width:100%}.e5pfpa_selector{background:var(--dsw-alias-bg-module-platform);height:36px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;border:none;border-radius:18px;align-items:center;gap:12px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.e5pfpa_selector:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.e5pfpa_selector:disabled{cursor:default}.e5pfpa_selectorWrap{display:flex}.e5pfpa_pillChevron{flex:none}.e5pfpa_row{display:flex;gap:10px;align-items:center}.e5pfpa_button{display:inline-flex;align-items:center;justify-content:center;gap:4px;border:.5px solid var(--dsw-alias-border-l3);border-radius:14px;cursor:pointer;font-size:12px;line-height:18px;color:var(--dsw-alias-label-primary);background:transparent;padding:0 10px;height:28px}.e5pfpa_button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.e5pfpa_button:disabled{cursor:not-allowed;opacity:.4}.e5pfpa_button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.e5pfpa_readOnly{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px}.e5pfpa_error{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px;line-height:1.5}.e5pfpa_dot{box-sizing:border-box;corner-shape:round;border-radius:50%;flex:none;width:8px;height:8px;display:inline-block}.e5pfpa_dotError{background:var(--dsw-alias-state-error-primary)}.e5pfpa_empty{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px}.e5pfpa_catalog{flex-direction:column;align-items:stretch;gap:10px;display:flex}.e5pfpa_catalog+.e5pfpa_catalog{border-top:.5px solid var(--dsw-alias-border-l2);padding-top:12px}.e5pfpa_catalog>.e5pfpa_linkButton{align-self:flex-start}.e5pfpa_catalog>.e5pfpa_linkButtonRow{align-self:flex-start;display:flex;gap:8px;align-items:center}.e5pfpa_linkButton{box-sizing:border-box;border:none;background:0 0;color:var(--dsw-alias-label-tertiary);cursor:pointer;border-radius:14px;align-items:center;height:28px;padding:0 10px;font:inherit;font-size:12px;line-height:18px;display:inline-flex}.e5pfpa_linkButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}.e5pfpa_linkButton:disabled{opacity:.4;cursor:default}.e5pfpa_linkButton:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.e5pfpa_catalogEntry{border:.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-module-platform);border-radius:10px;padding:6px}.e5pfpa_catalogRow{grid-template-columns:minmax(0,1.4fr) minmax(0,1fr) auto;align-items:center;gap:6px;display:grid}.e5pfpa_catalogInput{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:8px;height:28px;padding:0 10px;font:inherit;font-size:14px;line-height:22px;min-width:0;width:100%}.e5pfpa_catalogInput::placeholder{color:var(--dsw-alias-label-dimmed)}.e5pfpa_catalogInput:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-1px}.e5pfpa_catalogInput:disabled{opacity:.6;cursor:default}.e5pfpa_iconButton{box-sizing:border-box;width:28px;height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;display:inline-flex}.e5pfpa_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.e5pfpa_iconButton:disabled{opacity:.4;cursor:default}.e5pfpa_iconButton:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.e5pfpa_iconButtonDanger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary)}.e5pfpa_addButton{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:14px;align-self:flex-start;align-items:center;gap:4px;height:28px;padding:0 10px;font:inherit;font-size:12px;line-height:18px;display:inline-flex}.e5pfpa_addButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.e5pfpa_addButton:disabled{opacity:.4;cursor:default}.e5pfpa_disclosure{flex-direction:column;gap:10px;display:flex}.e5pfpa_disclosureSummary{color:var(--dsw-alias-label-secondary);cursor:pointer;list-style:none;align-items:center;gap:6px;font-size:12px;line-height:18px;user-select:none;display:flex}.e5pfpa_disclosureSummary::-webkit-details-marker{display:none}.e5pfpa_disclosureSummary:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px;border-radius:6px}.e5pfpa_disclosureLabel:before{content:"\\25b8";display:inline-block;margin-right:6px;transition:transform .16s}.e5pfpa_disclosure[open] .e5pfpa_disclosureLabel:before{transform:rotate(90deg)}.e5pfpa_disclosureCount{color:var(--dsw-alias-label-tertiary)}.e5pfpa_disclosureBody{flex-direction:column;gap:10px;display:flex}.e5pfpa_disclosureBody>.e5pfpa_linkButton{align-self:flex-start}.e5pfpa_disclosureBody>.e5pfpa_linkButtonRow{align-self:flex-start;display:flex;gap:8px;align-items:center}.e5pfpa_toggleLabel{align-items:center;display:inline-flex;flex:none}.e5pfpa_fetchDialog{background:color-mix(in srgb,var(--dsw-alias-label-primary) 28%,transparent);align-items:center;justify-content:center;display:flex;position:fixed;inset:0;z-index:90}.e5pfpa_dialogContent{background:var(--dsw-alias-bg-layer-3);border:.5px solid var(--dsw-alias-border-l3);border-radius:16px;flex-direction:column;gap:10px;width:min(520px,calc(100vw - 32px));max-height:min(620px,calc(100vh - 48px));display:flex;overflow:hidden}.e5pfpa_dialogHeader{align-items:center;gap:8px;padding:14px 16px 0;display:flex}.e5pfpa_dialogTitle{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:22px;flex:1;min-width:0;margin:0}.e5pfpa_dialogClose{box-sizing:border-box;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;flex:none;width:28px;height:28px;display:inline-flex}.e5pfpa_dialogClose:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.e5pfpa_dialogClose:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.e5pfpa_dialogDescription{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;margin:0;padding:0 16px}.e5pfpa_dialogBody{flex-direction:column;flex:1;gap:8px;min-height:0;padding:0 16px;display:flex}.e5pfpa_candidateToolbar{align-items:center;gap:8px;display:flex}.e5pfpa_candidateSearch{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:8px;height:32px;padding:0 10px;font:inherit;font-size:12px;line-height:20px;flex:1;min-width:0}.e5pfpa_candidateSearch::placeholder{color:var(--dsw-alias-label-dimmed)}.e5pfpa_candidateSearch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-1px}.e5pfpa_candidateList{flex-direction:column;gap:2px;margin:0;padding:0 0 10px;list-style:none;overflow-y:auto;display:flex}.e5pfpa_candidate{border-radius:8px;padding:4px 6px}.e5pfpa_candidate:hover{background:var(--dsw-alias-interactive-bg-hover)}.e5pfpa_candidateLabel{align-items:center;gap:8px;min-width:0;display:flex}.e5pfpa_candidateLabel input[type=checkbox]{accent-color:var(--dsw-alias-brand-primary);flex:none;margin:0}.e5pfpa_candidateId{color:var(--dsw-alias-label-primary);font-size:12px;line-height:18px;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.e5pfpa_candidateNote{color:var(--dsw-alias-label-tertiary);flex:none;font-size:11px;line-height:16px}.e5pfpa_dialogFooter{border-top:.5px solid var(--dsw-alias-border-l2);align-items:center;justify-content:flex-end;gap:8px;padding:12px 16px;display:flex}.e5pfpa_dialogButton{display:inline-flex;align-items:center;justify-content:center;gap:4px;border:.5px solid var(--dsw-alias-border-l3);border-radius:16px;cursor:pointer;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary);background:transparent;padding:0 12px;height:32px}.e5pfpa_dialogButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.e5pfpa_dialogButton:disabled{cursor:not-allowed;opacity:.4}.e5pfpa_dialogButton:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.e5pfpa_dialogButtonPrimary{color:var(--dsw-alias-label-primary);font-weight:600}.e5pfpa_selectorLabel{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.e5pfpa_result{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.e5pfpa_resultValue{color:var(--dsw-alias-label-secondary);margin-left:4px}.e5pfpa_resultAge{color:var(--dsw-alias-label-dimmed);margin-left:4px}.e5pfpa_notice{flex-direction:column;gap:4px;display:flex;border:.5px solid var(--dsw-alias-border-l3);border-radius:10px;background:var(--dsw-alias-bg-layer-1);padding:8px 10px}.e5pfpa_noticeTitle{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:600;line-height:18px}.e5pfpa_noticeBody{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.e5pfpa_docsLink{color:var(--dsw-alias-label-tertiary);align-items:center;gap:4px;display:inline-flex;text-decoration:none;font-size:12px;line-height:1.5}.e5pfpa_docsLinkLabel{text-decoration:none}.e5pfpa_docsLink:hover{color:var(--dsw-alias-brand-primary)}.e5pfpa_docsLink:hover .e5pfpa_docsLinkLabel{text-decoration:underline}.e5pfpa_docsLink:hover .e5pfpa_docsIcon{color:var(--dsw-alias-brand-primary)}.e5pfpa_docsIcon{color:var(--dsw-alias-label-tertiary);flex:none}.e5pfpa_docsLink:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px;border-radius:4px}.e5pfpa_catalogRowSingle{grid-template-columns:minmax(0,1fr) auto}.e5pfpa_catalogInputMono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}\n';
52
+ var ChromeMcpCard_default = { "card": "e5pfpa_card", "cardOpen": "e5pfpa_cardOpen", "header": "e5pfpa_header", "headText": "e5pfpa_headText", "nameRow": "e5pfpa_nameRow", "name": "e5pfpa_name", "desc": "e5pfpa_desc", "chevron": "e5pfpa_chevron", "chevronOpen": "e5pfpa_chevronOpen", "body": "e5pfpa_body", "field": "e5pfpa_field", "fieldRow": "e5pfpa_fieldRow", "fieldTitle": "e5pfpa_fieldTitle", "fieldDesc": "e5pfpa_fieldDesc", "selector": "e5pfpa_selector", "selectorWrap": "e5pfpa_selectorWrap", "pillChevron": "e5pfpa_pillChevron", "row": "e5pfpa_row", "button": "e5pfpa_button", "readOnly": "e5pfpa_readOnly", "error": "e5pfpa_error", "dot": "e5pfpa_dot", "dotError": "e5pfpa_dotError", "empty": "e5pfpa_empty", "catalog": "e5pfpa_catalog", "linkButton": "e5pfpa_linkButton", "linkButtonRow": "e5pfpa_linkButtonRow", "catalogEntry": "e5pfpa_catalogEntry", "catalogRow": "e5pfpa_catalogRow", "catalogInput": "e5pfpa_catalogInput", "iconButton": "e5pfpa_iconButton", "iconButtonDanger": "e5pfpa_iconButtonDanger", "addButton": "e5pfpa_addButton", "disclosure": "e5pfpa_disclosure", "disclosureSummary": "e5pfpa_disclosureSummary", "disclosureLabel": "e5pfpa_disclosureLabel", "disclosureCount": "e5pfpa_disclosureCount", "disclosureBody": "e5pfpa_disclosureBody", "toggleLabel": "e5pfpa_toggleLabel", "fetchDialog": "e5pfpa_fetchDialog", "dialogContent": "e5pfpa_dialogContent", "dialogHeader": "e5pfpa_dialogHeader", "dialogTitle": "e5pfpa_dialogTitle", "dialogClose": "e5pfpa_dialogClose", "dialogDescription": "e5pfpa_dialogDescription", "dialogBody": "e5pfpa_dialogBody", "candidateToolbar": "e5pfpa_candidateToolbar", "candidateSearch": "e5pfpa_candidateSearch", "candidateList": "e5pfpa_candidateList", "candidate": "e5pfpa_candidate", "candidateLabel": "e5pfpa_candidateLabel", "candidateId": "e5pfpa_candidateId", "candidateNote": "e5pfpa_candidateNote", "dialogFooter": "e5pfpa_dialogFooter", "dialogButton": "e5pfpa_dialogButton", "dialogButtonPrimary": "e5pfpa_dialogButtonPrimary", "selectorLabel": "e5pfpa_selectorLabel", "result": "e5pfpa_result", "resultValue": "e5pfpa_resultValue", "resultAge": "e5pfpa_resultAge", "notice": "e5pfpa_notice", "noticeTitle": "e5pfpa_noticeTitle", "noticeBody": "e5pfpa_noticeBody", "docsLink": "e5pfpa_docsLink", "docsLinkLabel": "e5pfpa_docsLinkLabel", "docsIcon": "e5pfpa_docsIcon", "catalogRowSingle": "e5pfpa_catalogRowSingle", "catalogInputMono": "e5pfpa_catalogInputMono" };
53
+
54
+ // src/client/card/flags.ts
55
+ var import_jsx_runtime2 = require("react/jsx-runtime");
56
+
57
+ // src/client/plugin-meta.ts
58
+ var SETTINGS_NAMESPACE = "chrome-mcp";
59
+ var LOCALE_NAMESPACE = "chromeMcp";
60
+ var PLUGIN_ID = "@comecaramelos/dsh-chrome-mcp";
61
+ var CONFIGURATION_URL = "https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/docs/configuration.md";
62
+ var inject = ["slots", "locale", "settingsScope"];
63
+
64
+ // src/client/card/icons.ts
65
+ var CHEVRON_PATH = "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z";
66
+ var TRASH_PATH = "M2.5 4h11M6.5 4V2.5h3V4M4 4l.7 9a1 1 0 001 .9h4.6a1 1 0 001-.9L12 4M6.5 6.8v4.4M9.5 6.8v4.4";
67
+ var CLOSE_PATHS = [
68
+ "M14.1168 13.197L13.197 14.1167L1.8833 2.80303L2.80309 1.88324L14.1168 13.197Z",
69
+ "M13.197 1.88326L14.1168 2.80305L2.80309 14.1168L1.8833 13.197L13.197 1.88326Z"
70
+ ];
71
+ var DOCS_PATHS = [
72
+ "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",
73
+ "M15 3h6v6",
74
+ "M10 14 21 3"
75
+ ];
76
+
77
+ // src/client/card/rows.ts
78
+ var react = __toESM(require("react"), 1);
79
+ var import_jsx_runtime = require("react/jsx-runtime");
80
+ function RowList(props) {
81
+ var served = Array.isArray(props.values) ? props.values : [];
82
+ var writable = props.writable === true;
83
+ var busy = props.busy === true;
84
+ var labels = Array.isArray(props.labels) ? props.labels : [];
85
+ var placeholders = Array.isArray(props.placeholders) ? props.placeholders : [];
86
+ var columns = Math.max(1, labels.length);
87
+ var blankRow = [];
88
+ for (var blank = 0; blank < columns; blank++) blankRow.push("");
89
+ var draftState = react.useState(null);
90
+ var draft = draftState[0];
91
+ var setDraft = draftState[1];
92
+ var focusedState = react.useState(false);
93
+ var focused = focusedState[0];
94
+ var setFocused = focusedState[1];
95
+ var servedKey = JSON.stringify(served);
96
+ var syncState = react.useState("");
97
+ var servedSync = syncState[0];
98
+ var setServedSync = syncState[1];
99
+ if (servedKey !== servedSync) {
100
+ setServedSync(servedKey);
101
+ if (!focused) setDraft(null);
102
+ }
103
+ var rows = draft === null ? served : draft;
104
+ var save = (next) => {
105
+ if (typeof props.save !== "function") return Promise.resolve();
106
+ setDraft(next);
107
+ return Promise.resolve(props.save(next)).catch(() => {
108
+ });
109
+ };
110
+ var withDraft = (make) => make(draft === null ? served : draft);
111
+ var patchRow = (index2, column, value) => {
112
+ setFocused(true);
113
+ setDraft(
114
+ withDraft(
115
+ (current) => current.map(
116
+ (row, at) => at === index2 ? row.map((cell, field) => field === column ? value : cell) : row
117
+ )
118
+ )
119
+ );
120
+ };
121
+ var removeRow = (index2) => {
122
+ if (!writable || rows[index2] === void 0) return;
123
+ save(rows.filter((_row, at) => at !== index2));
124
+ };
125
+ var addRow = () => {
126
+ if (!writable) return;
127
+ setDraft(withDraft((current) => current.concat([blankRow])));
128
+ };
129
+ var commit = () => {
130
+ if (draft === null) return;
131
+ save(rows);
132
+ };
133
+ var fieldNode = (row, index2, column) => (0, import_jsx_runtime.jsx)("input", {
134
+ className: props.inputClassName ? ChromeMcpCard_default.catalogInput + " " + props.inputClassName : ChromeMcpCard_default.catalogInput,
135
+ type: "text",
136
+ value: row[column] === void 0 ? "" : row[column],
137
+ placeholder: placeholders[column],
138
+ "aria-label": labels[column] + " " + (index2 + 1),
139
+ title: column === 0 ? row[column] : void 0,
140
+ disabled: !writable,
141
+ spellCheck: false,
142
+ autoComplete: "off",
143
+ autocapitalize: "off",
144
+ onChange: (event) => {
145
+ patchRow(index2, column, event && event.target ? String(event.target.value ?? "") : "");
146
+ },
147
+ onFocus: () => setFocused(true),
148
+ onBlur: () => {
149
+ setFocused(false);
150
+ commit();
151
+ },
152
+ onKeyDown: (event) => {
153
+ if (event && event.key === "Enter") {
154
+ setFocused(false);
155
+ commit();
156
+ }
157
+ }
158
+ }, column);
159
+ var entryNode = (row, index2) => {
160
+ var cells = [];
161
+ var width = row.length === 0 ? columns : row.length;
162
+ for (var column = 0; column < width; column++) cells.push(fieldNode(row, index2, column));
163
+ cells.push(
164
+ (0, import_jsx_runtime.jsx)("button", {
165
+ type: "button",
166
+ className: ChromeMcpCard_default.iconButton + " " + ChromeMcpCard_default.iconButtonDanger,
167
+ "aria-label": props.removeLabel + " " + (index2 + 1),
168
+ title: props.removeLabel,
169
+ disabled: !writable,
170
+ onClick: () => {
171
+ removeRow(index2);
172
+ },
173
+ children: (0, import_jsx_runtime.jsx)("svg", {
174
+ width: 14,
175
+ height: 14,
176
+ viewBox: "0 0 16 16",
177
+ fill: "none",
178
+ "aria-hidden": "true",
179
+ children: (0, import_jsx_runtime.jsx)("path", {
180
+ d: TRASH_PATH,
181
+ stroke: "currentColor",
182
+ strokeWidth: 1.3,
183
+ strokeLinecap: "round",
184
+ strokeLinejoin: "round"
185
+ })
186
+ })
187
+ })
188
+ );
189
+ return (0, import_jsx_runtime.jsx)("div", {
190
+ className: ChromeMcpCard_default.catalogEntry,
191
+ children: (0, import_jsx_runtime.jsxs)("div", {
192
+ className: props.rowClassName ? ChromeMcpCard_default.catalogRow + " " + props.rowClassName : ChromeMcpCard_default.catalogRow,
193
+ children: cells
194
+ })
195
+ }, index2);
196
+ };
197
+ var body = [];
198
+ if (typeof props.renderAction === "function") body.push(props.renderAction(rows, save));
199
+ var visible = [];
200
+ for (var index = 0; index < rows.length; index++) visible.push(entryNode(rows[index], index));
201
+ body = body.concat(visible);
202
+ body.push(
203
+ (0, import_jsx_runtime.jsx)("button", {
204
+ type: "button",
205
+ className: ChromeMcpCard_default.addButton,
206
+ disabled: !writable || busy,
207
+ onClick: addRow,
208
+ children: props.addLabel
209
+ })
210
+ );
211
+ var emptyLine = rows.length === 0 ? (0, import_jsx_runtime.jsx)("p", { className: ChromeMcpCard_default.empty, children: props.emptyText }) : null;
212
+ if (props.disclosure !== true) {
213
+ return [emptyLine].concat(body);
214
+ }
215
+ return [
216
+ emptyLine,
217
+ (0, import_jsx_runtime.jsxs)("details", {
218
+ className: ChromeMcpCard_default.disclosure,
219
+ children: [
220
+ (0, import_jsx_runtime.jsxs)("summary", {
221
+ className: ChromeMcpCard_default.disclosureSummary,
222
+ children: [
223
+ (0, import_jsx_runtime.jsx)("span", { className: ChromeMcpCard_default.disclosureLabel, children: props.rowsLabel }),
224
+ (0, import_jsx_runtime.jsx)("span", { className: ChromeMcpCard_default.disclosureCount, children: String(rows.length) })
225
+ ]
226
+ }),
227
+ (0, import_jsx_runtime.jsx)("div", { className: ChromeMcpCard_default.disclosureBody, children: body })
228
+ ]
229
+ })
230
+ ];
231
+ }
232
+
233
+ // src/client/card/flags.ts
234
+ function rowsOf(flags) {
235
+ return flags.map((flag) => [flag]);
236
+ }
237
+ function flagsOf(rows) {
238
+ var flags = [];
239
+ for (const row of rows) {
240
+ var flag = typeof row === "undefined" || row === null ? "" : String(row[0] ?? "").trim();
241
+ if (flag === "") continue;
242
+ if (flags.indexOf(flag) !== -1) continue;
243
+ flags.push(flag);
244
+ }
245
+ return flags;
246
+ }
247
+ function ExtraFlagsEditor(props) {
248
+ var t = props.t;
249
+ var writable = props.writable;
250
+ var flags = Array.isArray(props.flags) ? props.flags : [];
251
+ var defaults = Array.isArray(props.defaultFlags) ? props.defaultFlags : [];
252
+ var wsl = Array.isArray(props.wslFlags) ? props.wslFlags : [];
253
+ var save = (next) => {
254
+ if (typeof props.saveFlags !== "function") return Promise.resolve();
255
+ return Promise.resolve(props.saveFlags(flagsOf(next)));
256
+ };
257
+ var restoreAction = (visible) => {
258
+ var current = flagsOf(visible);
259
+ var merged = current.concat(defaults.filter((flag) => current.indexOf(flag) === -1));
260
+ return (0, import_jsx_runtime2.jsx)("button", {
261
+ type: "button",
262
+ className: ChromeMcpCard_default.linkButton,
263
+ // The defaults list is host-served — with nothing to recover there
264
+ // is nothing to do.
265
+ disabled: !writable || defaults.length === 0,
266
+ title: t("flagsRestoreTitle"),
267
+ onClick: () => {
268
+ if (!writable) return;
269
+ save(merged.map((flag) => [flag]));
270
+ },
271
+ children: t("flagsRestore")
272
+ });
273
+ };
274
+ var wslAction = (visible) => {
275
+ var current = flagsOf(visible);
276
+ var merged = current.concat(wsl.filter((flag) => current.indexOf(flag) === -1));
277
+ return (0, import_jsx_runtime2.jsx)("button", {
278
+ type: "button",
279
+ className: ChromeMcpCard_default.linkButton,
280
+ // The recommended list is host-served — with every one of them
281
+ // already in the rows there is nothing left to add.
282
+ disabled: !writable || wsl.length === 0,
283
+ title: t("flagsWslTitle"),
284
+ onClick: () => {
285
+ if (!writable) return;
286
+ save(merged.map((flag) => [flag]));
287
+ },
288
+ children: t("flagsWsl")
289
+ });
290
+ };
291
+ var renderActions = (visible) => (0, import_jsx_runtime2.jsxs)("div", {
292
+ className: ChromeMcpCard_default.linkButtonRow,
293
+ children: [restoreAction(visible), wslAction(visible)]
294
+ });
295
+ return (0, import_jsx_runtime2.jsxs)("div", {
296
+ className: ChromeMcpCard_default.catalog,
297
+ children: [
298
+ (0, import_jsx_runtime2.jsxs)("div", {
299
+ className: ChromeMcpCard_default.field,
300
+ children: [
301
+ (0, import_jsx_runtime2.jsxs)("div", {
302
+ className: ChromeMcpCard_default.fieldRow,
303
+ children: [
304
+ (0, import_jsx_runtime2.jsx)("span", { className: ChromeMcpCard_default.fieldTitle, children: t("flagsTitle") }),
305
+ // The guide link rides the same field row the picker's
306
+ // pill occupies.
307
+ (0, import_jsx_runtime2.jsxs)("a", {
308
+ className: ChromeMcpCard_default.docsLink,
309
+ href: CONFIGURATION_URL,
310
+ target: "_blank",
311
+ rel: "noopener noreferrer",
312
+ title: t("docsMessage"),
313
+ children: [
314
+ (0, import_jsx_runtime2.jsx)("svg", {
315
+ className: ChromeMcpCard_default.docsIcon,
316
+ width: 14,
317
+ height: 14,
318
+ viewBox: "0 0 24 24",
319
+ fill: "none",
320
+ stroke: "currentColor",
321
+ strokeWidth: 2,
322
+ strokeLinecap: "round",
323
+ strokeLinejoin: "round",
324
+ "aria-hidden": "true",
325
+ children: DOCS_PATHS.map((d, at) => (0, import_jsx_runtime2.jsx)("path", { d }, at))
326
+ }),
327
+ (0, import_jsx_runtime2.jsx)("span", { className: ChromeMcpCard_default.docsLinkLabel, children: t("docsLinkLabel") })
328
+ ]
329
+ })
330
+ ]
331
+ }),
332
+ (0, import_jsx_runtime2.jsx)("div", { className: ChromeMcpCard_default.fieldDesc, children: t("flagsHint") })
333
+ ]
334
+ }),
335
+ // One row per saved flag: the single field is the flag itself, and
336
+ // the two append controls — Restore defaults and Flags WSL — ride
337
+ // the list's action line.
338
+ (0, import_jsx_runtime2.jsx)(RowList, {
339
+ values: rowsOf(flags),
340
+ labels: [t("flagsValueLabel")],
341
+ placeholders: [t("flagsValuePlaceholder")],
342
+ writable,
343
+ save,
344
+ renderAction: renderActions,
345
+ emptyText: t("flagsEmpty"),
346
+ rowsLabel: t("flagsRows"),
347
+ addLabel: t("addFlag"),
348
+ removeLabel: t("removeFlag"),
349
+ rowClassName: ChromeMcpCard_default.catalogRowSingle,
350
+ inputClassName: ChromeMcpCard_default.catalogInputMono,
351
+ disclosure: true
352
+ })
353
+ ]
354
+ });
355
+ }
356
+
357
+ // src/client/card/picker.ts
358
+ var import_jsx_runtime3 = require("react/jsx-runtime");
359
+ var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
360
+ function CatalogPicker(props) {
361
+ var entries = props.entries;
362
+ var options = props.options;
363
+ var selectedId = props.selectedId;
364
+ var writable = props.writable;
365
+ var busy = props.busy === true;
366
+ var rows = entries.map((entry) => [entry.id, entry.name]);
367
+ var saveEntries = (next) => {
368
+ if (typeof props.saveEntries !== "function") return Promise.resolve();
369
+ return Promise.resolve(props.saveEntries(next.map((row) => ({ id: row[0] ?? "", name: row[1] ?? "" }))));
370
+ };
371
+ var fetchAction = () => (0, import_jsx_runtime3.jsx)("button", {
372
+ type: "button",
373
+ className: ChromeMcpCard_default.linkButton,
374
+ disabled: !writable || busy,
375
+ onClick: () => {
376
+ props.refresh();
377
+ },
378
+ children: busy ? props.busyLabel : props.refreshLabel
379
+ });
380
+ var windowsAction = () => (0, import_jsx_runtime3.jsx)("button", {
381
+ type: "button",
382
+ className: ChromeMcpCard_default.linkButton,
383
+ disabled: props.windowsDisabled === true,
384
+ title: props.windowsTitle,
385
+ onClick: () => {
386
+ if (typeof props.windowsOnOpen === "function") props.windowsOnOpen();
387
+ },
388
+ children: props.windowsLabel
389
+ });
390
+ var renderActions = () => (0, import_jsx_runtime3.jsxs)("div", {
391
+ className: ChromeMcpCard_default.linkButtonRow,
392
+ children: [fetchAction(), windowsAction()]
393
+ });
394
+ return (0, import_jsx_runtime3.jsxs)("div", {
395
+ className: ChromeMcpCard_default.catalog,
396
+ children: [
397
+ (0, import_jsx_runtime3.jsxs)("div", {
398
+ className: ChromeMcpCard_default.field,
399
+ children: [
400
+ (0, import_jsx_runtime3.jsxs)("div", {
401
+ className: ChromeMcpCard_default.fieldRow,
402
+ children: [
403
+ (0, import_jsx_runtime3.jsx)("span", { className: ChromeMcpCard_default.fieldTitle, children: props.selectorLabel }),
404
+ (0, import_jsx_runtime3.jsx)("span", {
405
+ className: ChromeMcpCard_default.selectorWrap,
406
+ children: (0, import_jsx_runtime3.jsx)(import_dsh_client_ui_primitives.Menu, {
407
+ open: props.menuOpen,
408
+ onClose: () => {
409
+ props.setMenuOpen(false);
410
+ },
411
+ items: options,
412
+ selectedId: options.some((option) => option.id === selectedId) ? selectedId : "",
413
+ onSelect: (id) => {
414
+ props.setMenuOpen(false);
415
+ props.select(id);
416
+ },
417
+ align: "end",
418
+ portal: true,
419
+ anchor: (0, import_jsx_runtime3.jsxs)("button", {
420
+ type: "button",
421
+ className: ChromeMcpCard_default.selector,
422
+ "aria-haspopup": "menu",
423
+ "aria-expanded": props.menuOpen,
424
+ disabled: !writable || busy,
425
+ title: props.selectedTitle,
426
+ onClick: () => {
427
+ props.setMenuOpen(!props.menuOpen);
428
+ },
429
+ children: [
430
+ // The reference card hands the pill a bare label;
431
+ // the path needs its own truncating span.
432
+ (0, import_jsx_runtime3.jsx)("span", { className: ChromeMcpCard_default.selectorLabel, children: props.selectedLabel }),
433
+ (0, import_jsx_runtime3.jsx)("svg", {
434
+ width: 14,
435
+ height: 14,
436
+ className: ChromeMcpCard_default.pillChevron,
437
+ viewBox: "0 0 14 14",
438
+ fill: "none",
439
+ xmlns: "http://www.w3.org/2000/svg",
440
+ children: (0, import_jsx_runtime3.jsx)("path", { d: CHEVRON_PATH, fill: "currentColor" })
441
+ })
442
+ ]
443
+ })
444
+ })
445
+ })
446
+ ]
447
+ }),
448
+ (0, import_jsx_runtime3.jsx)("div", { className: ChromeMcpCard_default.fieldDesc, children: props.hint })
449
+ ]
450
+ }),
451
+ // The saved rows: two fields per row, folded behind the disclosure,
452
+ // with the fetch action sitting inside the body.
453
+ (0, import_jsx_runtime3.jsx)(RowList, {
454
+ values: rows,
455
+ labels: [props.idLabel, props.nameLabel],
456
+ placeholders: [props.idPlaceholder, props.namePlaceholder],
457
+ writable,
458
+ busy,
459
+ save: saveEntries,
460
+ renderAction: renderActions,
461
+ emptyText: props.emptyText,
462
+ rowsLabel: props.rowsLabel,
463
+ addLabel: props.addLabel,
464
+ removeLabel: props.removeLabel,
465
+ disclosure: true
466
+ }),
467
+ props.statusLine === void 0 || props.statusLine === null ? null : props.statusLine
468
+ ]
469
+ });
470
+ }
471
+
472
+ // src/client/card/dialog.ts
473
+ var react2 = __toESM(require("react"), 1);
474
+ var import_jsx_runtime4 = require("react/jsx-runtime");
475
+ function CandidateDialog(props) {
476
+ var t = props.t;
477
+ var candidates = props.candidates;
478
+ var savedIds = props.savedIds;
479
+ var savedSet = new Set(savedIds);
480
+ var initialChecked = candidates.filter((candidate) => savedSet.has(candidate.id)).map((candidate) => candidate.id);
481
+ var servedKey = JSON.stringify(candidates) + "|" + JSON.stringify(savedIds);
482
+ var draftState = react2.useState({ key: "", query: "", checked: [] });
483
+ var draft = draftState[0];
484
+ var setDraft = draftState[1];
485
+ var dirty = draft.key !== servedKey;
486
+ var query = dirty ? "" : draft.query;
487
+ var checked = dirty ? initialChecked : draft.checked;
488
+ var checkedSet = new Set(checked);
489
+ var setQuery = (value) => {
490
+ setDraft({ key: servedKey, query: value, checked: Array.from(checkedSet) });
491
+ };
492
+ var setChecked = (make) => {
493
+ setDraft({ key: servedKey, query, checked: make(checked) });
494
+ };
495
+ var needle = query.trim().toLowerCase();
496
+ var shown = needle === "" ? candidates : candidates.filter((candidate) => candidate.id.toLowerCase().indexOf(needle) !== -1);
497
+ var addable = shown.filter((candidate) => !savedSet.has(candidate.id)).map((candidate) => candidate.id);
498
+ var allChecked = addable.length > 0 && addable.every((id) => checkedSet.has(id));
499
+ var toggle = (id, value) => {
500
+ setChecked((current) => value ? current.concat([id]) : current.filter((entry) => entry !== id));
501
+ };
502
+ return (0, import_jsx_runtime4.jsxs)("div", {
503
+ className: ChromeMcpCard_default.fetchDialog,
504
+ role: "dialog",
505
+ "aria-modal": "true",
506
+ "aria-label": props.title,
507
+ children: [
508
+ (0, import_jsx_runtime4.jsxs)("div", {
509
+ className: ChromeMcpCard_default.dialogContent,
510
+ children: [
511
+ (0, import_jsx_runtime4.jsxs)("div", {
512
+ className: ChromeMcpCard_default.dialogHeader,
513
+ children: [
514
+ (0, import_jsx_runtime4.jsx)("h2", { className: ChromeMcpCard_default.dialogTitle, children: props.title }),
515
+ (0, import_jsx_runtime4.jsx)("button", {
516
+ type: "button",
517
+ className: ChromeMcpCard_default.dialogClose,
518
+ "aria-label": t("close"),
519
+ onClick: () => props.onClose(),
520
+ children: (0, import_jsx_runtime4.jsx)("svg", {
521
+ width: 14,
522
+ height: 14,
523
+ viewBox: "0 0 16 16",
524
+ fill: "none",
525
+ xmlns: "http://www.w3.org/2000/svg",
526
+ "aria-hidden": "true",
527
+ children: CLOSE_PATHS.map((d, at) => (0, import_jsx_runtime4.jsx)("path", { d, fill: "currentColor" }, at))
528
+ })
529
+ })
530
+ ]
531
+ }),
532
+ (0, import_jsx_runtime4.jsx)("p", { className: ChromeMcpCard_default.dialogDescription, children: props.description }),
533
+ (0, import_jsx_runtime4.jsxs)("div", {
534
+ className: ChromeMcpCard_default.dialogBody,
535
+ children: [
536
+ (0, import_jsx_runtime4.jsxs)("div", {
537
+ className: ChromeMcpCard_default.candidateToolbar,
538
+ children: [
539
+ (0, import_jsx_runtime4.jsx)("input", {
540
+ className: ChromeMcpCard_default.candidateSearch,
541
+ type: "search",
542
+ placeholder: props.searchPlaceholder,
543
+ "aria-label": props.searchPlaceholder,
544
+ value: query,
545
+ onChange: (event) => {
546
+ setQuery(event && event.target ? String(event.target.value ?? "") : "");
547
+ }
548
+ }),
549
+ (0, import_jsx_runtime4.jsx)("button", {
550
+ type: "button",
551
+ className: ChromeMcpCard_default.linkButton,
552
+ onClick: () => {
553
+ if (allChecked) setChecked(() => []);
554
+ else setChecked((current) => Array.from(new Set(current.concat(addable))));
555
+ },
556
+ children: t("selectAll")
557
+ })
558
+ ]
559
+ }),
560
+ shown.length === 0 ? (0, import_jsx_runtime4.jsx)("p", { className: ChromeMcpCard_default.empty, children: t("dialogEmpty") }) : (0, import_jsx_runtime4.jsx)("ul", {
561
+ className: ChromeMcpCard_default.candidateList,
562
+ children: shown.map((candidate) => {
563
+ var isSaved = savedSet.has(candidate.id);
564
+ var note = candidate.note !== "" ? candidate.note : isSaved ? t("candidateSaved") : "";
565
+ return (0, import_jsx_runtime4.jsx)("li", {
566
+ className: ChromeMcpCard_default.candidate,
567
+ children: (0, import_jsx_runtime4.jsxs)("label", {
568
+ className: ChromeMcpCard_default.candidateLabel,
569
+ children: [
570
+ (0, import_jsx_runtime4.jsx)("input", {
571
+ type: "checkbox",
572
+ checked: isSaved ? true : checkedSet.has(candidate.id),
573
+ disabled: isSaved,
574
+ onChange: (event) => {
575
+ toggle(candidate.id, !!(event && event.target && event.target.checked));
576
+ }
577
+ }),
578
+ (0, import_jsx_runtime4.jsx)("span", { className: ChromeMcpCard_default.candidateId, children: candidate.id }),
579
+ note === "" ? null : (0, import_jsx_runtime4.jsx)("span", { className: ChromeMcpCard_default.candidateNote, children: note })
580
+ ]
581
+ })
582
+ }, candidate.id);
583
+ })
584
+ })
585
+ ]
586
+ }),
587
+ (0, import_jsx_runtime4.jsxs)("div", {
588
+ className: ChromeMcpCard_default.dialogFooter,
589
+ children: [
590
+ (0, import_jsx_runtime4.jsx)("button", {
591
+ type: "button",
592
+ className: ChromeMcpCard_default.dialogButton,
593
+ onClick: () => props.onClose(),
594
+ children: t("cancel")
595
+ }),
596
+ (0, import_jsx_runtime4.jsx)("button", {
597
+ type: "button",
598
+ className: ChromeMcpCard_default.dialogButton + " " + ChromeMcpCard_default.dialogButtonPrimary,
599
+ onClick: () => props.onAdd(Array.from(checkedSet)),
600
+ children: t("addSelected")
601
+ })
602
+ ]
603
+ })
604
+ ]
605
+ })
606
+ ]
607
+ });
608
+ }
609
+
610
+ // src/client/card/run-actions.ts
611
+ var react3 = __toESM(require("react"), 1);
612
+ var PROBED_AGE_TICK_MS = 1e4;
613
+ function useRunActions(deps) {
614
+ const runningState = react3.useState(false);
615
+ const isRunning = runningState[0];
616
+ const setRunning = runningState[1];
617
+ const windowsRunningState = react3.useState(false);
618
+ const isWindowsRunning = windowsRunningState[0];
619
+ const setWindowsRunning = windowsRunningState[1];
620
+ const dialogRef = react3.useState(null);
621
+ const dialog = dialogRef[0];
622
+ const setDialog = dialogRef[1];
623
+ const probedState = react3.useState(0);
624
+ const lastProbedAt = probedState[0];
625
+ const setLastProbedAt = probedState[1];
626
+ const clockState = react3.useState(() => Date.now());
627
+ const clock = clockState[0];
628
+ const setClock = clockState[1];
629
+ react3.useEffect(() => {
630
+ const tick = setInterval(() => setClock(Date.now()), PROBED_AGE_TICK_MS);
631
+ return () => clearInterval(tick);
632
+ }, []);
633
+ const runFetch = () => {
634
+ setRunning(true);
635
+ let promise;
636
+ try {
637
+ promise = deps.refreshExecutables();
638
+ } catch (caught) {
639
+ promise = Promise.reject(caught);
640
+ }
641
+ Promise.resolve(promise).then(
642
+ async (statuses) => {
643
+ setRunning(false);
644
+ const settled = Date.now();
645
+ setLastProbedAt(settled);
646
+ setClock(settled);
647
+ if (!Array.isArray(statuses) || statuses.length === 0) return;
648
+ const candidates = statuses.map((status) => ({
649
+ id: status.path,
650
+ note: status.version !== "" ? status.version : status.error,
651
+ broken: status.version === "" && status.error !== ""
652
+ }));
653
+ setDialog({ candidates });
654
+ },
655
+ () => {
656
+ setRunning(false);
657
+ }
658
+ );
659
+ };
660
+ const runWindowsChrome = () => {
661
+ setWindowsRunning(true);
662
+ let promise;
663
+ try {
664
+ promise = deps.openWindowsChrome();
665
+ } catch (caught) {
666
+ promise = Promise.reject(caught);
667
+ }
668
+ Promise.resolve(promise).then(
669
+ () => {
670
+ setWindowsRunning(false);
671
+ },
672
+ () => {
673
+ setWindowsRunning(false);
674
+ }
675
+ );
676
+ };
677
+ const addFromDialog = (ids) => {
678
+ setDialog(null);
679
+ if (typeof deps.addExecutables === "function") Promise.resolve(deps.addExecutables(ids)).catch(() => {
680
+ });
681
+ };
682
+ return {
683
+ running: isRunning,
684
+ windowsRunning: isWindowsRunning,
685
+ dialog,
686
+ runFetch,
687
+ runWindowsChrome,
688
+ addFromDialog,
689
+ closeDialog: () => setDialog(null),
690
+ lastProbedAt,
691
+ clock
692
+ };
693
+ }
694
+
695
+ // src/client/card/toggle.ts
696
+ var import_jsx_runtime5 = require("react/jsx-runtime");
697
+ var import_dsh_client_ui_primitives2 = require("@deepseek-ai/dsh-client-ui-primitives");
698
+ function LogOutputToggle(props) {
699
+ var t = props.t;
700
+ return (0, import_jsx_runtime5.jsxs)("div", {
701
+ className: ChromeMcpCard_default.catalog,
702
+ children: [
703
+ (0, import_jsx_runtime5.jsxs)("div", {
704
+ className: ChromeMcpCard_default.field,
705
+ children: [
706
+ (0, import_jsx_runtime5.jsxs)("div", {
707
+ className: ChromeMcpCard_default.fieldRow,
708
+ children: [
709
+ (0, import_jsx_runtime5.jsx)("span", { className: ChromeMcpCard_default.fieldTitle, children: t("reduceLabel") }),
710
+ (0, import_jsx_runtime5.jsx)("span", {
711
+ className: ChromeMcpCard_default.toggleLabel,
712
+ title: t("reduceTitle"),
713
+ children: (0, import_jsx_runtime5.jsx)(import_dsh_client_ui_primitives2.Switch, {
714
+ checked: props.checked,
715
+ label: t("reduceLabel"),
716
+ disabled: props.disabled,
717
+ onChange: () => {
718
+ if (typeof props.onToggle === "function") props.onToggle();
719
+ }
720
+ })
721
+ })
722
+ ]
723
+ }),
724
+ (0, import_jsx_runtime5.jsx)("div", { className: ChromeMcpCard_default.fieldDesc, children: t("reduceHint") })
725
+ ]
726
+ })
727
+ ]
728
+ });
729
+ }
730
+
731
+ // src/client/catalog.ts
732
+ var ID_MAX_LENGTH = 1024;
733
+ var PATH_PATTERN = /^[^\x00-\x1f\x7f]{1,1024}$/u;
734
+ var NAME_MAX_LENGTH = 200;
735
+ var ENTRY_CAP = 50;
736
+ function isValidEntryId(id) {
737
+ if (id === "" || id.length > ID_MAX_LENGTH) return false;
738
+ return PATH_PATTERN.test(id);
739
+ }
740
+ function entryLabel(entry) {
741
+ const name = entry.name.trim();
742
+ return name === "" ? entry.id : name;
743
+ }
744
+ function normalizeEntries(value) {
745
+ if (!Array.isArray(value)) return [];
746
+ const out = [];
747
+ const seen = /* @__PURE__ */ new Set();
748
+ for (const item of value) {
749
+ if (out.length === ENTRY_CAP) break;
750
+ if (item === null || typeof item !== "object" || Array.isArray(item)) continue;
751
+ const raw = item;
752
+ const id = typeof raw.id === "string" ? raw.id.trim() : "";
753
+ if (!isValidEntryId(id) || seen.has(id)) continue;
754
+ seen.add(id);
755
+ const name = typeof raw.name === "string" ? raw.name.trim() : "";
756
+ out.push({ id, name: name.slice(0, NAME_MAX_LENGTH) });
757
+ }
758
+ return out;
759
+ }
760
+
761
+ // src/client/card/view.ts
762
+ function labelOf(entries, id) {
763
+ const row = entries.find((entry) => entry.id === id);
764
+ return row === void 0 ? id : entryLabel(row);
765
+ }
766
+ function describeCard(state) {
767
+ const error = state.lastError || state.actionError || "";
768
+ const servedMode = typeof state.stderrMode === "string" ? state.stderrMode : "";
769
+ const stderrEffective = servedMode !== "" ? servedMode : state.rowStderr === "console" ? "console" : "log";
770
+ const executableEntries = normalizeEntries(state.executables);
771
+ const statusList = Array.isArray(state.executableStatus) ? state.executableStatus : [];
772
+ const optionIds = executableEntries.map((entry) => entry.id);
773
+ const effectivePath = typeof state.effectiveChromePath === "string" ? state.effectiveChromePath : "";
774
+ if (effectivePath !== "" && optionIds.indexOf(effectivePath) === -1) optionIds.push(effectivePath);
775
+ const executableOptions = optionIds.map((id) => ({ id, label: labelOf(executableEntries, id) }));
776
+ const brokenIds = statusList.filter((status) => status.version === "" && status.error !== "").map((status) => status.path);
777
+ return {
778
+ error,
779
+ reduceChecked: stderrEffective === "log",
780
+ executableEntries,
781
+ executableOptions,
782
+ brokenIds,
783
+ effectivePath,
784
+ version: typeof state.chromeVersion === "string" ? state.chromeVersion : "",
785
+ missing: state.chromeMissing === true && error === "",
786
+ dotIsError: error !== "",
787
+ wslWindowsExecutable: state.wslWindowsExecutable === true,
788
+ windowsChrome: readWindowsChrome(state.windowsChromeStatus),
789
+ connectMode: state.carriesConnectionMode === true
790
+ };
791
+ }
792
+ function executableLabel(view, id) {
793
+ return labelOf(view.executableEntries, id);
794
+ }
795
+ function readWindowsChrome(value) {
796
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return { state: "off", port: 0, error: "" };
797
+ const record = value;
798
+ const known = ["off", "launching", "connected", "unreachable", "not-found", "launch-failed", "not-applicable"];
799
+ const state = typeof record.state === "string" && known.indexOf(record.state) !== -1 ? record.state : "off";
800
+ const port = typeof record.port === "number" && Number.isFinite(record.port) ? Math.trunc(record.port) : 0;
801
+ const error = typeof record.error === "string" ? record.error : "";
802
+ return { state, port, error };
803
+ }
804
+ function windowsChromeButton(view) {
805
+ const status = view.windowsChrome;
806
+ if (status.state === "launching") return { labelKey: "openWindowsChromeBusy", titleKey: "openWindowsChromeTitle", disabled: true, busy: true };
807
+ if (status.state === "not-applicable") return { labelKey: "openWindowsChrome", titleKey: "windowsChromeNotWsl", disabled: true, busy: false };
808
+ if (status.state === "not-found") return { labelKey: "openWindowsChrome", titleKey: "windowsChromeNotFound", disabled: true, busy: false };
809
+ if (status.state === "unreachable") return { labelKey: "openWindowsChrome", titleKey: "windowsChromeUnreachable", disabled: true, busy: false };
810
+ return { labelKey: "openWindowsChrome", titleKey: "openWindowsChromeTitle", disabled: false, busy: false };
811
+ }
812
+ function isBroken(view, id) {
813
+ return view.brokenIds.indexOf(id) !== -1;
814
+ }
815
+ function probeAge(since, now, t) {
816
+ const seconds = Math.max(0, Math.floor((now - since) / 1e3));
817
+ if (seconds < 10) return t("probeJustNow");
818
+ if (seconds < 60) return String(seconds) + " " + t("probeSecondsAgo");
819
+ const minutes = Math.floor(seconds / 60);
820
+ if (minutes < 60) return String(minutes) + " " + t("probeMinutesAgo");
821
+ return String(Math.floor(minutes / 60)) + " " + t("probeHoursAgo");
822
+ }
823
+
824
+ // src/client/card/index.ts
825
+ function optionLabel(view, id, t) {
826
+ const label = executableLabel(view, id);
827
+ return isBroken(view, id) ? label + " (" + t("unavailableSuffix") + ")" : label;
828
+ }
829
+ function ChromeMcpCard(props) {
830
+ const t = props.t;
831
+ const state = props.useChromeMcpCard((snapshot) => snapshot);
832
+ const openState = react4.useState(false);
833
+ const open = openState[0];
834
+ const setOpen = openState[1];
835
+ const menuState = react4.useState(false);
836
+ const menuOpen = menuState[0];
837
+ const setMenuOpen = menuState[1];
838
+ const bodyId = react4.useId();
839
+ const runs = useRunActions({
840
+ refreshExecutables: props.refreshExecutables,
841
+ openWindowsChrome: props.openWindowsChrome,
842
+ addExecutables: props.addExecutables
843
+ });
844
+ if (!state.available) return null;
845
+ const view = describeCard(state);
846
+ const rows = view.executableEntries;
847
+ const savedIds = rows.map((entry) => entry.id);
848
+ const options = view.executableOptions.map((option) => ({ id: option.id, label: optionLabel(view, option.id, t) }));
849
+ const dotLabel = view.error !== "" ? view.error : t("chromeMissing");
850
+ const dotAria = view.dotIsError || view.missing ? t("statusError") : t("statusOk");
851
+ const windowsButton = windowsChromeButton(view);
852
+ const windowsDisabled = windowsButton.disabled || runs.windowsRunning || !state.writable;
853
+ const windowsBusy = windowsButton.busy || runs.windowsRunning;
854
+ const connectLabel = view.windowsChrome.port > 0 ? t("connectModeLabel") + " (http://127.0.0.1:" + String(view.windowsChrome.port) + ")" : t("connectModeLabel");
855
+ const selectedLabel = view.connectMode ? connectLabel : view.effectivePath === "" ? t("executableNone") : executableLabel(view, view.effectivePath);
856
+ const statusLine = runs.running ? (0, import_jsx_runtime6.jsx)("p", { className: ChromeMcpCard_default.result, role: "status", children: t("probing") }) : view.version === "" ? null : (0, import_jsx_runtime6.jsxs)("p", {
857
+ className: ChromeMcpCard_default.result,
858
+ role: "status",
859
+ children: [
860
+ t("versionLabel"),
861
+ (0, import_jsx_runtime6.jsx)("span", { className: ChromeMcpCard_default.resultValue, children: view.version }),
862
+ runs.lastProbedAt === 0 ? null : (0, import_jsx_runtime6.jsx)("span", { className: ChromeMcpCard_default.resultAge, children: probeAge(runs.lastProbedAt, runs.clock, t) })
863
+ ]
864
+ });
865
+ return (0, import_jsx_runtime6.jsx)("li", {
866
+ className: ChromeMcpCard_default.card + (open ? " " + ChromeMcpCard_default.cardOpen : ""),
867
+ children: [
868
+ (0, import_jsx_runtime6.jsxs)("button", {
869
+ type: "button",
870
+ className: ChromeMcpCard_default.header,
871
+ "aria-expanded": open,
872
+ "aria-label": t(open ? "collapse" : "expand") + ": " + t("title"),
873
+ "aria-controls": bodyId,
874
+ onClick: () => {
875
+ setOpen(!open);
876
+ if (!open && typeof props.refresh === "function") void props.refresh();
877
+ },
878
+ children: [
879
+ (0, import_jsx_runtime6.jsxs)("span", {
880
+ className: ChromeMcpCard_default.headText,
881
+ children: [
882
+ (0, import_jsx_runtime6.jsxs)("div", {
883
+ className: ChromeMcpCard_default.nameRow,
884
+ children: [
885
+ (0, import_jsx_runtime6.jsx)("span", { className: ChromeMcpCard_default.name, children: t("title") }),
886
+ view.dotIsError || view.missing ? (0, import_jsx_runtime6.jsx)("span", {
887
+ className: ChromeMcpCard_default.dot + " " + ChromeMcpCard_default.dotError,
888
+ role: "img",
889
+ "aria-label": dotAria,
890
+ title: dotLabel
891
+ }) : null
892
+ ]
893
+ }),
894
+ (0, import_jsx_runtime6.jsx)("span", { className: ChromeMcpCard_default.desc, children: t("description") })
895
+ ]
896
+ }),
897
+ (0, import_jsx_runtime6.jsx)("svg", {
898
+ width: 14,
899
+ height: 14,
900
+ className: ChromeMcpCard_default.chevron + (open ? " " + ChromeMcpCard_default.chevronOpen : ""),
901
+ viewBox: "0 0 14 14",
902
+ fill: "none",
903
+ xmlns: "http://www.w3.org/2000/svg",
904
+ children: (0, import_jsx_runtime6.jsx)("path", { d: CHEVRON_PATH, fill: "currentColor" })
905
+ })
906
+ ]
907
+ }),
908
+ open ? (0, import_jsx_runtime6.jsxs)("div", {
909
+ id: bodyId,
910
+ className: ChromeMcpCard_default.body,
911
+ children: [
912
+ !state.writable ? (0, import_jsx_runtime6.jsx)("p", { className: ChromeMcpCard_default.readOnly, role: "status", children: t("readOnly") }) : null,
913
+ (0, import_jsx_runtime6.jsx)(CatalogPicker, {
914
+ t,
915
+ entries: rows,
916
+ options,
917
+ idPlaceholder: t("executableIdPlaceholder"),
918
+ namePlaceholder: t("namePlaceholder"),
919
+ selectedId: view.effectivePath,
920
+ selectedLabel,
921
+ selectedTitle: view.connectMode ? connectLabel : view.effectivePath !== "" ? view.effectivePath : selectedLabel,
922
+ writable: state.writable,
923
+ busy: runs.running,
924
+ menuOpen,
925
+ setMenuOpen,
926
+ connectMode: view.connectMode,
927
+ selectorLabel: t("executableLabel"),
928
+ hint: view.connectMode ? t("connectModeHint") : t("executableHint"),
929
+ windowsLabel: windowsBusy ? t("openWindowsChromeBusy") : t(windowsButton.labelKey),
930
+ windowsTitle: t(windowsButton.titleKey),
931
+ windowsDisabled,
932
+ windowsOnOpen: runs.runWindowsChrome,
933
+ rowsLabel: t("executableRows"),
934
+ emptyText: t("executableEmpty"),
935
+ idLabel: t("executableIdLabel"),
936
+ nameLabel: t("executableNameLabel"),
937
+ removeLabel: t("removeExecutable"),
938
+ addLabel: t("addExecutable"),
939
+ refreshLabel: t("fetchExecutables"),
940
+ busyLabel: t("fetching"),
941
+ select: (id) => {
942
+ if (typeof props.selectExecutable === "function") void props.selectExecutable(id);
943
+ },
944
+ saveEntries: (entries) => props.saveExecutables(entries),
945
+ refresh: runs.runFetch,
946
+ statusLine
947
+ }),
948
+ // The one state no control of this card can express: the
949
+ // host runs under WSL and what is selected is a Windows
950
+ // executable — which the bridge cannot launch, however
951
+ // healthy its `--version` answer looks. Nothing has failed
952
+ // yet, so this is a notice (static copy, shown only while
953
+ // the host reports the state), not a third error surface:
954
+ // the required configuration differs.
955
+ view.wslWindowsExecutable ? (0, import_jsx_runtime6.jsxs)("div", {
956
+ className: ChromeMcpCard_default.notice,
957
+ role: "note",
958
+ children: [
959
+ (0, import_jsx_runtime6.jsx)("div", { className: ChromeMcpCard_default.noticeTitle, children: t("wslWindowsTitle") }),
960
+ (0, import_jsx_runtime6.jsx)("div", { className: ChromeMcpCard_default.noticeBody, children: t("wslWindowsNote") })
961
+ ]
962
+ }) : null,
963
+ (0, import_jsx_runtime6.jsx)(ExtraFlagsEditor, {
964
+ t,
965
+ flags: Array.isArray(state.extraFlags) ? state.extraFlags : [],
966
+ defaultFlags: Array.isArray(state.defaultExtraFlags) ? state.defaultExtraFlags : [],
967
+ wslFlags: Array.isArray(state.wslExtraFlags) ? state.wslExtraFlags : [],
968
+ writable: state.writable,
969
+ saveFlags: props.saveFlags
970
+ }),
971
+ (0, import_jsx_runtime6.jsx)(LogOutputToggle, {
972
+ t,
973
+ checked: view.reduceChecked,
974
+ disabled: !state.writable,
975
+ onToggle: props.toggleStderr
976
+ }),
977
+ state.lastError ? (0, import_jsx_runtime6.jsx)("p", { className: ChromeMcpCard_default.error, role: "status", children: state.lastError }) : null,
978
+ state.actionError ? (0, import_jsx_runtime6.jsx)("p", { className: ChromeMcpCard_default.error, role: "status", children: state.actionError }) : null,
979
+ runs.dialog !== null ? (0, import_jsx_runtime6.jsx)(CandidateDialog, {
980
+ t,
981
+ candidates: runs.dialog.candidates,
982
+ savedIds,
983
+ title: t("dialogExecutablesTitle"),
984
+ description: t("dialogExecutablesDescription"),
985
+ searchPlaceholder: t("searchExecutables"),
986
+ onAdd: runs.addFromDialog,
987
+ onClose: runs.closeDialog
988
+ }) : null
989
+ ]
990
+ }) : null
991
+ ]
992
+ });
993
+ }
14
994
 
15
- /**
16
- * Chrome DevTools MCP settings + status card, browser half.
17
- *
18
- * Registers one card into the shared `settings.plugin.item` slot
19
- * (Settings → Plugins → Plugin configuration), keyed by the
20
- * `chrome-mcp` settings namespace the host half serves. The card
21
- * offers, per the product spec:
22
- * - error reporting: chrome executable / connection errors surfaced
23
- * from the host's `lastError`, reported by a single UI element: the
24
- * red status dot in the header, whose `title` carries the full
25
- * message. A served `chromeMissing` flag keeps that dot red even
26
- * when the captured error was cleared (its title then falls back to
27
- * the not-found label). No body paragraph, no extra badge.
28
- * - an extra flags editor (host field `extraFlags`, one flag per
29
- * line, default `--no-usage-statistics`, `--no-performance-crux`), writing the persisted
30
- * field via scope.set (host fiber.update relaunches the bridge),
31
- * - the Chrome executable version discovered by the last check, and
32
- * - the full configuration-guide message with a link to
33
- * https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/docs/configuration.md
34
- * The card is collapsible like the stock plugin cards: closed by
35
- * default, with the header as its disclosure button.
36
- */
37
- /** Settings namespace served by the host half; also the slot key. */
38
- var NS = "chrome-mcp";
39
- /** Locale dictionary namespace. */
40
- var LOCALE_NS = "chromeMcp";
41
- /** Required services (cordis fiber inject). */
42
- var inject = ["slots", "locale", "settingsScope"];
995
+ // src/client/controller/budget.ts
996
+ var CHECK_POLL_TICK_MS = 700;
997
+ var CHECK_POLL_TIMEOUT_MS = 2e4;
43
998
 
44
- /** Configuration docs link (must match host CONFIGURATION_DOCS_URL). */
45
- var CONFIGURATION_URL =
46
- "https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/docs/configuration.md";
999
+ // src/client/controller/controller.ts
1000
+ var import_dsh_client_store = require("@deepseek-ai/dsh-client-store");
47
1001
 
48
- // ── card styles ────────────────────────────────────────────────────
49
- var CSS = {
50
- card: "dshcmc_card",
51
- cardOpen: "dshcmc_cardOpen",
52
- header: "dshcmc_header",
53
- headText: "dshcmc_headText",
54
- nameRow: "dshcmc_nameRow",
55
- name: "dshcmc_name",
56
- desc: "dshcmc_desc",
57
- chevron: "dshcmc_chevron",
58
- chevronOpen: "dshcmc_chevronOpen",
59
- body: "dshcmc_body",
60
- field: "dshcmc_field",
61
- rowText: "dshcmc_rowText",
62
- fieldTitle: "dshcmc_fieldTitle",
63
- fieldDesc: "dshcmc_fieldDesc",
64
- row: "dshcmc_row",
65
- button: "dshcmc_button",
66
- readOnly: "dshcmc_readOnly",
67
- dot: "dshcmc_dot",
68
- dotError: "dshcmc_dotError",
69
- link: "dshcmc_link",
70
- value: "dshcmc_value",
71
- docs: "dshcmc_docs",
72
- editor: "dshcmc_editor",
73
- text: "dshcmc_text"
74
- };
75
- var css =
76
- "." + CSS.card + "{border:.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-layer-3);border-radius:16px;list-style:none;transition:border-color .16s,background .16s}" +
77
- "." + CSS.card + ":hover{border-color:var(--dsw-alias-label-dimmed)}" +
78
- "." + CSS.cardOpen + "{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}" +
79
- "." + CSS.header + "{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}" +
80
- "." + CSS.header + ":focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}" +
81
- "." + CSS.headText + "{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}" +
82
- "." + CSS.nameRow + "{align-items:center;gap:8px;min-width:0;display:flex}" +
83
- "." + CSS.name + "{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}" +
84
- "." + CSS.desc + "{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}" +
85
- "." + CSS.chevron + "{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}" +
86
- "." + CSS.chevronOpen + "{transform:rotate(180deg)}" +
87
- "." + CSS.body + "{border-top:.5px solid var(--dsw-alias-border-l2);display:flex;flex-direction:column;gap:10px;margin:0 16px;padding:12px 0}" +
88
- "." + CSS.field + "{align-items:flex-start;gap:8px;display:flex}" +
89
- "." + CSS.rowText + "{flex-direction:column;flex:1;gap:4px;min-width:0;padding-right:48px;display:flex}" +
90
- "." + CSS.fieldTitle + "{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:22px}" +
91
- "." + CSS.fieldDesc + "{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400;line-height:18px}" +
92
- "." + CSS.row + "{display:flex;gap:10px;align-items:center}" +
93
- "." + CSS.button +
94
- "{display:inline-flex;align-items:center;justify-content:center;gap:4px;border:.5px solid var(--dsw-alias-border-l3);" +
95
- "border-radius:14px;cursor:pointer;font-size:12px;line-height:18px;color:var(--dsw-alias-label-primary);" +
96
- "background:transparent;padding:0 10px;height:28px}" +
97
- "." + CSS.button + ":hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}" +
98
- "." + CSS.button + ":disabled{cursor:not-allowed;opacity:.4}" +
99
- "." + CSS.button + ":focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}" +
100
- "." + CSS.readOnly + "{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px}" +
101
- "." + CSS.dot + "{box-sizing:border-box;corner-shape:round;border-radius:50%;flex:none;width:8px;height:8px;display:inline-block}" +
102
- "." + CSS.dotError + "{background:var(--dsw-alias-state-error-primary)}" +
103
- "." + CSS.link + "{color:var(--dsw-alias-brand-primary);text-decoration:none;font-size:12px;line-height:1.5}" +
104
- "." + CSS.link + ":hover{text-decoration:underline}" +
105
- "." + CSS.value + "{margin:0;color:var(--dsw-alias-label-secondary);font-size:12px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;line-height:1.5;white-space:pre-wrap}" +
106
- "." + CSS.docs + "{margin:0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.5;overflow-wrap:anywhere}" +
107
- "." + CSS.editor + "{flex-direction:column;gap:8px;display:flex}" +
108
- "." + CSS.text +
109
- "{box-sizing:border-box;width:100%;border:.5px solid var(--dsw-alias-border-l3);border-radius:10px;background:var(--dsw-alias-bg-layer-1);" +
110
- "color:var(--dsw-alias-label-primary);font-size:12px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;line-height:1.5;" +
111
- "padding:6px 10px;min-height:64px;resize:vertical}" +
112
- "." + CSS.text + ":focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-1px}";
113
- var TAG_ID = "@comecaramelos/dsh-chrome-mcp/ChromeMcpCard.module.css";
114
- if (typeof document !== "undefined" && document.querySelector('style[data-plugin-css="' + TAG_ID + '"]') === null) {
115
- var tag = document.createElement("style");
116
- tag.dataset.plugin = "@comecaramelos/dsh-chrome-mcp";
117
- tag.dataset.pluginCss = TAG_ID;
118
- tag.textContent = css;
119
- document.head.appendChild(tag);
120
- }
1002
+ // src/client/controller/read.ts
1003
+ function readNumber(value, fallback) {
1004
+ return typeof value === "number" ? value : fallback;
1005
+ }
1006
+ function readString(value, fallback) {
1007
+ return typeof value === "string" ? value : fallback;
1008
+ }
1009
+ function readWindowsChromeStatus(value) {
1010
+ const fallback = { state: "off", port: 0, error: "" };
1011
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return fallback;
1012
+ const status = value.state;
1013
+ const states = ["off", "launching", "connected", "unreachable", "not-found", "launch-failed", "not-applicable"];
1014
+ const port = value.port;
1015
+ const error = value.error;
1016
+ return {
1017
+ state: typeof status === "string" && states.indexOf(status) !== -1 ? status : "off",
1018
+ port: typeof port === "number" && Number.isFinite(port) ? Math.trunc(port) : 0,
1019
+ error: typeof error === "string" ? error : ""
1020
+ };
1021
+ }
1022
+ function readStatus(value) {
1023
+ if (!Array.isArray(value)) return [];
1024
+ const statuses = [];
1025
+ for (const item of value) {
1026
+ if (item === null || typeof item !== "object" || Array.isArray(item)) continue;
1027
+ const path = readString(item.path, "");
1028
+ if (path === "") continue;
1029
+ statuses.push({
1030
+ path,
1031
+ version: readString(item.version, ""),
1032
+ error: readString(item.error, "")
1033
+ });
1034
+ }
1035
+ return statuses;
1036
+ }
121
1037
 
122
- // ── locale dictionaries ────────────────────────────────────────────
123
- var en = {
124
- title: "Chrome DevTools MCP",
125
- description: "Chrome DevTools MCP server (chrome-devtools-mcp) settings and status",
126
- flagsTitle: "Extra flags",
127
- flagsHint: "One flag per line, appended to `npx -y chrome-devtools-mcp@latest`. Defaults to --no-usage-statistics, --no-performance-crux.",
128
- flagsPlaceholder: "No extra flags",
129
- flagsSave: "Save flags",
130
- versionTitle: "Detected Chrome version",
131
- check: "Re-check Chrome",
132
- checking: "Checking…",
133
- docsMessage: "Find the complete list of server parameters (e.g., --headless, --isolated, --slim) and how to configure WebSocket connections in the Configuration Guide:",
134
- statusError: "Error",
135
- statusOk: "OK",
136
- chromeMissing: "Chrome executable not found",
137
- readOnly: "Settings are read-only in this deployment.",
138
- expand: "Expand",
139
- collapse: "Collapse"
140
- };
141
- // ── card controller ────────────────────────────────────────────────
142
- /** Re-describe cadence (ms) while waiting for the host's check run. */
143
- var CHECK_POLL_TICK_MS = 700;
144
- /** Give up waiting for the check revision to advance after this (ms). The host check subprocess times out at 15s. */
145
- var CHECK_POLL_TIMEOUT_MS = 20000;
146
- /**
147
- * Projects the bound `chrome-mcp` settings scope onto a snapshot store
148
- * the card reads through its `useChromeMcpCard` hook, and owns the two
149
- * write actions (save flags, re-check). `mirror` is the shared
150
- * settings describe mirror (`settingsScope.describe()`) the re-check
151
- * action re-reads while the host's in-memory check status is still en
152
- * route — module plugins have no client→host push channel beyond
153
- * persisted writes.
154
- */
155
- var ChromeMcpCardController = class {
156
- constructor(scope, mirror) {
157
- this.scope = scope;
158
- this.mirror = mirror;
159
- this.disposed = false;
160
- this.pollTimer = null;
161
- this.pollGeneration = 0;
162
- this.checkWaitStarted = false;
163
- this.store = clientStore.createSnapshotStore({
164
- available: false,
165
- writable: false,
166
- extraFlags: [],
167
- lastError: "",
168
- chromeMissing: false,
169
- checkRevision: 0,
170
- chromeVersion: "",
171
- actionError: ""
172
- });
173
- this.unsubscribe = scope.subscribe(() => this.publish());
174
- this.publish();
175
- }
176
- /** Republish the latest scope snapshot into the card store. */
177
- publish() {
178
- if (this.disposed) return;
179
- var previous = this.store.getSnapshot();
180
- var snapshot = this.scope.getSnapshot();
181
- if (snapshot.status !== "ready" || snapshot.value === void 0) {
182
- this.store.set({
183
- available: false,
184
- writable: false,
185
- extraFlags: [],
186
- lastError: "",
187
- chromeMissing: false,
188
- checkRevision: 0,
189
- chromeVersion: "",
190
- actionError: previous.actionError
191
- });
192
- return;
193
- }
194
- var value = snapshot.value;
195
- // The status fields (lastError / chromeMissing / checkRevision /
196
- // chromeVersion) ride the composition **base** layer: describe()
197
- // clones that entry object fresh on every read, while the frozen
198
- // resolved `value` folds the base in only at commits. Base-only
199
- // host changes — the registration-time check, bridge-log and
200
- // tool-call captures — are therefore invisible to `value`, so
201
- // status is read from `base`, falling back to `value` only when
202
- // the read carries no base layer. `extraFlags` keeps reading
203
- // `value`: the persisted user layer wins there, not the
204
- // host-seeded entry field.
205
- var base = snapshot.base ?? {};
206
- var servedRevision =
207
- typeof base.checkRevision === "number"
208
- ? base.checkRevision
209
- : typeof value.checkRevision === "number"
210
- ? value.checkRevision
211
- : 0;
212
- this.store.set({
213
- available: true,
214
- writable: snapshot.writable === true,
215
- extraFlags: Array.isArray(value.extraFlags) ? value.extraFlags : [],
216
- lastError: typeof base.lastError === "string" ? base.lastError : (value.lastError ?? ""),
217
- chromeMissing: base.chromeMissing === undefined ? value.chromeMissing === true : base.chromeMissing === true,
218
- checkRevision: servedRevision,
219
- chromeVersion: typeof base.chromeVersion === "string" ? base.chromeVersion : (typeof value.chromeVersion === "string" ? value.chromeVersion : ""),
220
- actionError: ""
221
- });
222
- // Startup catch-up: the registration-time executable check lands
223
- // in the base layer *after* this first describe read, and a
224
- // module plugin has no push channel — poll the shared describe
225
- // mirror until the served revision advances (the same budget the
226
- // re-check waits with). A card attaching after the check already
227
- // ran sees a nonzero revision and does not poll.
228
- if (!this.checkWaitStarted) {
229
- this.checkWaitStarted = true;
230
- if (servedRevision === 0) this.waitForCheck(0, Date.now() + CHECK_POLL_TIMEOUT_MS);
231
- }
232
- }
233
- /**
234
- * Immediate write of the user's extra flags (persisted; the host's
235
- * validate hook rejects invalid lists here, and the next bridge
236
- * launch starts with them).
237
- */
238
- saveFlags(flags) {
239
- return this.scope.set("extraFlags", flags).catch((error) => this.noteError(error));
240
- }
241
- /**
242
- * Ask the host to re-run the Chrome executable/connection check,
243
- * then wait for the result: write `recheckNonce`, then poll the
244
- * shared describe mirror until the served `checkRevision` advances
245
- * past the click-time value — or the check timeout budget elapses.
246
- */
247
- recheck() {
248
- if (this.disposed) return Promise.resolve();
249
- var baseline = this.store.getSnapshot().checkRevision;
250
- var wait = this.waitForCheck(baseline, Date.now() + CHECK_POLL_TIMEOUT_MS);
251
- return this.scope
252
- .set("recheckNonce", Date.now())
253
- .catch((error) => this.noteError(error))
254
- .then(() => wait);
255
- }
256
- /** Re-describe the settings mirror every tick until the check revision advances or the deadline passes. */
257
- waitForCheck(baseline, deadline) {
258
- var controller = this;
259
- // A new wait supersedes any pending one (the startup catch-up
260
- // poll yielding to a manual re-check), so exactly one poll loop
261
- // runs and the superseded wait settles instead of dangling.
262
- var generation = ++controller.pollGeneration;
263
- return new Promise((resolve) => {
264
- var timer;
265
- var settled = false;
266
- var arm = (delay) => {
267
- timer = setTimeout(tick, delay);
268
- controller.pollTimer = timer;
269
- };
270
- var finish = () => {
271
- if (settled) return;
272
- settled = true;
273
- clearTimeout(timer);
274
- if (controller.pollTimer === timer) controller.pollTimer = null;
275
- resolve();
276
- };
277
- var satisfied = () =>
278
- controller.store.getSnapshot().checkRevision > baseline || Date.now() >= deadline;
279
- var stale = () => controller.disposed || generation !== controller.pollGeneration;
280
- var tick = () => {
281
- if (stale() || satisfied()) {
282
- finish();
283
- return;
284
- }
285
- var loaded;
286
- try {
287
- loaded = controller.mirror.load();
288
- } catch (error) {
289
- loaded = void 0; // mirror read failure: the next tick retries
290
- }
291
- Promise.resolve(loaded)
292
- .catch(() => {})
293
- .then(() => {
294
- if (stale() || satisfied()) {
295
- finish();
296
- return;
297
- }
298
- arm(CHECK_POLL_TICK_MS);
299
- });
300
- };
301
- arm(CHECK_POLL_TICK_MS);
302
- });
303
- }
304
- noteError(error) {
305
- if (this.disposed) return;
306
- var previous = this.store.getSnapshot();
307
- this.store.set({
308
- ...previous,
309
- actionError: error != null && typeof error.message === "string" ? error.message : String(error)
310
- });
311
- }
312
- /** The face the card's slot registration injects. */
313
- inject() {
314
- return {
315
- hooks: { chromeMcpCard: this.store },
316
- saveFlags: (flags) => this.saveFlags(flags),
317
- recheck: () => this.recheck(),
318
- /** One mirror re-read (used when the card opens). */
319
- refresh: () => this.refresh()
320
- };
321
- }
322
- /**
323
- * Re-read the shared describe mirror once so base-layer status
324
- * captured asynchronously by the host (bridge connection errors)
325
- * reaches an opened card without waiting for a write or a re-check.
326
- */
327
- refresh() {
328
- if (this.disposed) return Promise.resolve();
329
- var load;
330
- try {
331
- load = this.mirror.load();
332
- } catch (error) {
333
- load = void 0;
334
- }
335
- return Promise.resolve(load).catch(() => {});
336
- }
337
- dispose() {
338
- this.disposed = true;
339
- if (this.pollTimer !== null) {
340
- clearTimeout(this.pollTimer);
341
- this.pollTimer = null;
342
- }
343
- this.unsubscribe();
344
- }
345
- };
1038
+ // src/client/controller/controller.ts
1039
+ var ChromeMcpCardController = class {
1040
+ scope;
1041
+ mirror;
1042
+ disposed = false;
1043
+ pollTimer = null;
1044
+ pollGeneration = 0;
1045
+ waitStarted = false;
1046
+ store;
1047
+ unsubscribe;
1048
+ constructor(scope, mirror) {
1049
+ this.scope = scope;
1050
+ this.mirror = mirror;
1051
+ this.store = (0, import_dsh_client_store.createSnapshotStore)({
1052
+ available: false,
1053
+ writable: false,
1054
+ extraFlags: [],
1055
+ lastError: "",
1056
+ chromeMissing: false,
1057
+ executableDiscoveryRevision: 0,
1058
+ chromeVersion: "",
1059
+ executables: [],
1060
+ executableStatus: [],
1061
+ chromePath: "",
1062
+ effectiveChromePath: "",
1063
+ effectiveSource: "",
1064
+ stderrMode: "",
1065
+ rowStderr: "log",
1066
+ defaultExtraFlags: [],
1067
+ wslExtraFlags: [],
1068
+ wslWindowsExecutable: false,
1069
+ windowsChromeStatus: { state: "off", port: 0, error: "" },
1070
+ carriesConnectionMode: false,
1071
+ actionError: ""
1072
+ });
1073
+ this.unsubscribe = scope.subscribe(() => this.publish());
1074
+ this.publish();
1075
+ }
1076
+ /** Republish the latest scope snapshot into the card store. */
1077
+ publish() {
1078
+ if (this.disposed) return;
1079
+ const previous = this.store.getSnapshot();
1080
+ const snapshot = this.scope.getSnapshot();
1081
+ if (snapshot.status !== "ready" || snapshot.value === void 0) {
1082
+ this.store.set({
1083
+ available: false,
1084
+ writable: false,
1085
+ extraFlags: [],
1086
+ lastError: "",
1087
+ chromeMissing: false,
1088
+ executableDiscoveryRevision: 0,
1089
+ chromeVersion: "",
1090
+ executables: [],
1091
+ executableStatus: [],
1092
+ chromePath: "",
1093
+ effectiveChromePath: "",
1094
+ effectiveSource: "",
1095
+ stderrMode: "",
1096
+ rowStderr: "log",
1097
+ defaultExtraFlags: [],
1098
+ wslExtraFlags: [],
1099
+ wslWindowsExecutable: false,
1100
+ windowsChromeStatus: { state: "off", port: 0, error: "" },
1101
+ carriesConnectionMode: false,
1102
+ actionError: previous.actionError
1103
+ });
1104
+ return;
1105
+ }
1106
+ const value = snapshot.value;
1107
+ const base = snapshot.base ?? {};
1108
+ const servedRevision = readNumber(base.executableDiscoveryRevision, readNumber(value.executableDiscoveryRevision, 0));
1109
+ const servedVersion = readString(base.chromeVersion, readString(value.chromeVersion, ""));
1110
+ const servedExecutables = normalizeEntries(value.executables);
1111
+ const servedStatus = readStatus(base.executableStatus);
1112
+ const servedEffectivePath = readString(
1113
+ base.effectiveChromePath,
1114
+ readString(value.effectiveChromePath, readString(value.chromePath, ""))
1115
+ );
1116
+ const servedSource = base.effectiveSource === "selected" || base.effectiveSource === "row-config" ? base.effectiveSource : value.effectiveSource === "selected" || value.effectiveSource === "row-config" ? value.effectiveSource : "";
1117
+ const servedStderrMode = typeof value.stderrMode === "string" ? value.stderrMode : "";
1118
+ const servedRowStderr = typeof base.rowStderr === "string" ? base.rowStderr : typeof value.rowStderr === "string" ? value.rowStderr : "log";
1119
+ const servedDefaults = Array.isArray(base.defaultExtraFlags) ? base.defaultExtraFlags.filter((flag) => typeof flag === "string" && flag !== "") : Array.isArray(value.defaultExtraFlags) ? value.defaultExtraFlags.filter((flag) => typeof flag === "string" && flag !== "") : [];
1120
+ const servedWslFlags = Array.isArray(base.wslExtraFlags) ? base.wslExtraFlags.filter((flag) => typeof flag === "string" && flag !== "") : Array.isArray(value.wslExtraFlags) ? value.wslExtraFlags.filter((flag) => typeof flag === "string" && flag !== "") : [];
1121
+ const servedWslWindows = base.wslWindowsExecutable === void 0 ? value.wslWindowsExecutable === true : base.wslWindowsExecutable === true;
1122
+ const servedWindowsChrome = readWindowsChromeStatus(base.windowsChromeStatus !== void 0 ? base.windowsChromeStatus : value.windowsChromeStatus);
1123
+ const servedConnectMode = base.carriesConnectionMode === void 0 ? value.carriesConnectionMode === true : base.carriesConnectionMode === true;
1124
+ this.store.set({
1125
+ available: true,
1126
+ writable: snapshot.writable === true,
1127
+ extraFlags: Array.isArray(value.extraFlags) ? value.extraFlags : [],
1128
+ lastError: typeof base.lastError === "string" ? base.lastError : value.lastError ?? "",
1129
+ chromeMissing: base.chromeMissing === void 0 ? value.chromeMissing === true : base.chromeMissing === true,
1130
+ executableDiscoveryRevision: servedRevision,
1131
+ chromeVersion: servedVersion,
1132
+ executables: servedExecutables,
1133
+ executableStatus: servedStatus,
1134
+ chromePath: readString(value.chromePath, ""),
1135
+ effectiveChromePath: servedEffectivePath,
1136
+ effectiveSource: servedSource,
1137
+ stderrMode: servedStderrMode === "log" || servedStderrMode === "console" ? servedStderrMode : "",
1138
+ rowStderr: servedRowStderr === "console" ? "console" : "log",
1139
+ defaultExtraFlags: servedDefaults,
1140
+ wslExtraFlags: servedWslFlags,
1141
+ wslWindowsExecutable: servedWslWindows,
1142
+ windowsChromeStatus: servedWindowsChrome,
1143
+ carriesConnectionMode: servedConnectMode,
1144
+ actionError: ""
1145
+ });
1146
+ if (!this.waitStarted) {
1147
+ this.waitStarted = true;
1148
+ if (servedRevision === 0) void this.waitFor("executableDiscoveryRevision", 0, Date.now() + CHECK_POLL_TIMEOUT_MS);
1149
+ }
1150
+ }
1151
+ /**
1152
+ * Immediate write of the user's extra flags (persisted; the host's
1153
+ * validate hook rejects invalid lists here, and the next bridge
1154
+ * launch starts with them).
1155
+ */
1156
+ saveFlags(flags) {
1157
+ return this.scope.set("extraFlags", flags).catch((error) => this.noteError(error));
1158
+ }
1159
+ /**
1160
+ * Pick one saved executable. Writes the persisted `chromePath` (the host
1161
+ * restarts the bridge connection carrying it and re-runs the discovery), then
1162
+ * waits until the served revision advances past the click-time value.
1163
+ */
1164
+ selectExecutable(id) {
1165
+ if (this.disposed) return Promise.resolve();
1166
+ const target = typeof id === "string" ? id.trim() : "";
1167
+ if (target === "") return Promise.resolve();
1168
+ const baseline = this.store.getSnapshot().executableDiscoveryRevision;
1169
+ const wait = this.waitFor("executableDiscoveryRevision", baseline, Date.now() + CHECK_POLL_TIMEOUT_MS);
1170
+ return this.scope.set("chromePath", target).catch((error) => this.noteError(error)).then(() => wait);
1171
+ }
1172
+ /**
1173
+ * Save the whole rows list: the card owns the draft, the host re-normalizes
1174
+ * on the way in and re-runs the scan the new ids imply.
1175
+ */
1176
+ saveExecutables(entries) {
1177
+ if (this.disposed) return Promise.resolve();
1178
+ const rows = normalizeEntries(entries);
1179
+ const snapshot = this.store.getSnapshot();
1180
+ const unchanged = JSON.stringify(rows) === JSON.stringify(snapshot.executables);
1181
+ if (unchanged) return Promise.resolve();
1182
+ const baseline = snapshot.executableDiscoveryRevision;
1183
+ const wait = this.waitFor("executableDiscoveryRevision", baseline, Date.now() + CHECK_POLL_TIMEOUT_MS);
1184
+ return this.scope.set("executables", rows).catch((error) => this.noteError(error)).then(() => wait);
1185
+ }
1186
+ /**
1187
+ * Ask the host to re-probe the saved rows and fold what it finds into them,
1188
+ * then wait for the served revision to advance: write the trigger, then poll
1189
+ * the shared describe mirror until it moves — or the budget elapses. Resolves
1190
+ * with every candidate the run answered for and what it said about each, so
1191
+ * the dialog opens over the run's own answer list.
1192
+ */
1193
+ refreshExecutables() {
1194
+ if (this.disposed) return Promise.resolve([]);
1195
+ const baseline = this.store.getSnapshot().executableDiscoveryRevision;
1196
+ const wait = this.waitFor("executableDiscoveryRevision", baseline, Date.now() + CHECK_POLL_TIMEOUT_MS);
1197
+ return this.scope.set("refreshExecutablesNonce", Date.now()).catch((error) => this.noteError(error)).then(() => wait).then(() => {
1198
+ const served = this.store.getSnapshot();
1199
+ return Array.isArray(served.executableStatus) ? served.executableStatus : [];
1200
+ });
1201
+ }
1202
+ /**
1203
+ * Merge the ids picked in the dialog into the saved rows: every id already
1204
+ * there keeps its place (and its name), the missing ones append nameless.
1205
+ * The host re-normalizes on the way in and re-runs the scan the new ids
1206
+ * imply, so the same revision counter carries the wait.
1207
+ */
1208
+ addExecutables(ids) {
1209
+ if (this.disposed) return Promise.resolve();
1210
+ const served = this.store.getSnapshot();
1211
+ const rows = normalizeEntries(served.executables);
1212
+ const merged = rows.concat(
1213
+ (Array.isArray(ids) ? ids : []).filter((id) => typeof id === "string" && id.trim() !== "").map((id) => ({ id: id.trim(), name: "" }))
1214
+ );
1215
+ const next = normalizeEntries(merged);
1216
+ if (JSON.stringify(next) === JSON.stringify(rows)) return Promise.resolve();
1217
+ const baseline = served.executableDiscoveryRevision;
1218
+ const wait = this.waitFor("executableDiscoveryRevision", baseline, Date.now() + CHECK_POLL_TIMEOUT_MS);
1219
+ return this.scope.set("executables", next).catch((error) => this.noteError(error)).then(() => wait);
1220
+ }
1221
+ /**
1222
+ * Ask the host to drive a Windows-side Chrome in connect mode: write the
1223
+ * trigger, then wait for the run to settle (same revision counter every
1224
+ * executable run advances). What the button ends up reporting — launched,
1225
+ * already connected, prereq missing — is served through
1226
+ * `windowsChromeStatus`, not through the promise.
1227
+ */
1228
+ openWindowsChrome() {
1229
+ if (this.disposed) return Promise.resolve();
1230
+ const baseline = this.store.getSnapshot().executableDiscoveryRevision;
1231
+ const wait = this.waitFor("executableDiscoveryRevision", baseline, Date.now() + CHECK_POLL_TIMEOUT_MS);
1232
+ return this.scope.set("openWindowsChromeNonce", Date.now()).catch((error) => this.noteError(error)).then(() => wait);
1233
+ }
1234
+ /**
1235
+ * Flip the "Reduce log output" toggle: persists `stderrMode` as "log"
1236
+ * (capture stderr into the bridge log file) or "console" (echo it for
1237
+ * debugging), starting from what the host currently resolves — the served
1238
+ * `stderrMode` when set, else the row-config `rowStderr` fallback. The host
1239
+ * restarts the bridge connection on the change.
1240
+ */
1241
+ toggleStderr() {
1242
+ if (this.disposed) return Promise.resolve();
1243
+ const snapshot = this.store.getSnapshot();
1244
+ const effective = snapshot.stderrMode === "log" || snapshot.stderrMode === "console" ? snapshot.stderrMode : snapshot.rowStderr === "console" ? "console" : "log";
1245
+ const next = effective === "log" ? "console" : "log";
1246
+ return this.scope.set("stderrMode", next).catch((error) => this.noteError(error));
1247
+ }
1248
+ /** The face the card's slot registration injects. */
1249
+ inject() {
1250
+ return {
1251
+ hooks: { chromeMcpCard: this.store },
1252
+ saveFlags: (flags) => this.saveFlags(flags),
1253
+ toggleStderr: () => this.toggleStderr(),
1254
+ selectExecutable: (id) => this.selectExecutable(id),
1255
+ saveExecutables: (entries) => this.saveExecutables(entries),
1256
+ refreshExecutables: () => this.refreshExecutables(),
1257
+ addExecutables: (ids) => this.addExecutables(ids),
1258
+ openWindowsChrome: () => this.openWindowsChrome(),
1259
+ /** One mirror re-read (used when the card opens). */
1260
+ refresh: () => this.refresh()
1261
+ };
1262
+ }
1263
+ /**
1264
+ * Re-read the shared describe mirror once so base-layer status
1265
+ * captured asynchronously by the host (bridge connection errors)
1266
+ * reaches an opened card without waiting for a write or a run.
1267
+ */
1268
+ refresh() {
1269
+ if (this.disposed) return Promise.resolve();
1270
+ let load;
1271
+ try {
1272
+ load = this.mirror.load();
1273
+ } catch {
1274
+ load = void 0;
1275
+ }
1276
+ return Promise.resolve(load).catch(() => {
1277
+ });
1278
+ }
1279
+ dispose() {
1280
+ this.disposed = true;
1281
+ if (this.pollTimer !== null) {
1282
+ clearTimeout(this.pollTimer);
1283
+ this.pollTimer = null;
1284
+ }
1285
+ this.unsubscribe();
1286
+ }
1287
+ /**
1288
+ * Re-describe the settings mirror every tick until the given revision field
1289
+ * advances past `baseline`, or the deadline passes.
1290
+ */
1291
+ waitFor(field, baseline, deadline) {
1292
+ const generation = ++this.pollGeneration;
1293
+ const controller = this;
1294
+ return new Promise((resolve) => {
1295
+ let timer;
1296
+ let settled = false;
1297
+ const arm = (delay) => {
1298
+ timer = setTimeout(tick, delay);
1299
+ controller.pollTimer = timer ?? null;
1300
+ };
1301
+ const finish = () => {
1302
+ if (settled) return;
1303
+ settled = true;
1304
+ clearTimeout(timer);
1305
+ if (controller.pollTimer === (timer ?? null)) controller.pollTimer = null;
1306
+ resolve();
1307
+ };
1308
+ const satisfied = () => readNumber(controller.store.getSnapshot()[field], baseline) > baseline || Date.now() >= deadline;
1309
+ const stale = () => controller.disposed || generation !== controller.pollGeneration;
1310
+ const tick = () => {
1311
+ if (stale() || satisfied()) {
1312
+ finish();
1313
+ return;
1314
+ }
1315
+ let loaded;
1316
+ try {
1317
+ loaded = controller.mirror.load();
1318
+ } catch {
1319
+ loaded = void 0;
1320
+ }
1321
+ Promise.resolve(loaded).catch(() => ({})).then(() => {
1322
+ if (stale() || satisfied()) {
1323
+ finish();
1324
+ return;
1325
+ }
1326
+ arm(CHECK_POLL_TICK_MS);
1327
+ });
1328
+ };
1329
+ arm(CHECK_POLL_TICK_MS);
1330
+ });
1331
+ }
1332
+ /** Surface a rejected action on the card (store-only; never host state). */
1333
+ noteError(error) {
1334
+ if (this.disposed) return;
1335
+ const previous = this.store.getSnapshot();
1336
+ this.store.set({
1337
+ ...previous,
1338
+ actionError: error != null && typeof error.message === "string" ? error.message : String(error)
1339
+ });
1340
+ }
1341
+ };
346
1342
 
347
- // ── card component ─────────────────────────────────────────────────
348
- /** Chevron matching the shell's IconChevronDownOutline14 primitive. */
349
- var CHEVRON_PATH =
350
- "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z";
1343
+ // src/client/locales/en-US.ts
1344
+ var en = {
1345
+ title: "DSH Chrome MCP",
1346
+ description: "DSH Chrome MCP server (chrome-devtools-mcp) settings and status",
1347
+ executableLabel: "Chrome executable",
1348
+ executableHint: "The saved executable the bridge launches with. Selecting one restarts the connection.",
1349
+ executableNone: "No Chrome executable detected",
1350
+ executableRows: "Saved executables",
1351
+ executableIdLabel: "Executable path",
1352
+ executableIdPlaceholder: "Path to a Chrome executable",
1353
+ executableNameLabel: "Display name",
1354
+ namePlaceholder: "Display name",
1355
+ addExecutable: "Add executable",
1356
+ removeExecutable: "Delete executable",
1357
+ fetchExecutables: "Fetch executables",
1358
+ fetching: "Fetching\u2026",
1359
+ executableEmpty: "No executables saved. Use \u201CFetch executables\u201D, or add the Chrome path by hand.",
1360
+ dialogExecutablesTitle: "Choose executables to add",
1361
+ dialogExecutablesDescription: "These are the Chrome executables this host's last run answered for. Choose the ones to add.",
1362
+ dialogEmpty: "Nothing matches your search.",
1363
+ searchExecutables: "Search executables",
1364
+ selectAll: "Select all",
1365
+ addSelected: "Add selected",
1366
+ candidateSaved: "Saved",
1367
+ close: "Close",
1368
+ cancel: "Cancel",
1369
+ unavailableSuffix: "not runnable",
1370
+ wslWindowsTitle: "Windows executable under WSL",
1371
+ wslWindowsNote: "This host runs inside Windows Subsystem for Linux and the selected executable is the Windows Chrome, which the bridge cannot launch: the debug pipes it opens do not survive the WSL interop boundary, and its debugging port stays bound to the Windows loopback, unreachable over the WSL2 NAT bridge. It answers --version correctly, so the pill looks healthy until the first tool call fails. To drive the Windows browser, start Chrome yourself with --remote-debugging-port=9222 --user-data-dir=C:\\chrome-debug, add --browserUrl=http://127.0.0.1:9222 to the extra flags, and make Windows loopback reachable from WSL by setting networkingMode=mirrored in %USERPROFILE%\\.wslconfig followed by wsl --shutdown. Chrome installed inside the Linux distro needs none of this.",
1372
+ probing: "Checking the executables\u2026",
1373
+ versionLabel: "Probed version:",
1374
+ probeJustNow: "just now",
1375
+ probeSecondsAgo: "seconds ago",
1376
+ probeMinutesAgo: "minutes ago",
1377
+ probeHoursAgo: "hours ago",
1378
+ flagsTitle: "Extra flags",
1379
+ flagsHint: "The extra flags the bridge launches with. One flag per row \u2014 saving restarts the connection.",
1380
+ flagsEmpty: "No extra flags saved. Add one by hand, or use \u201CRestore defaults\u201D.",
1381
+ flagsRows: "Saved flags",
1382
+ flagsValueLabel: "Extra flag",
1383
+ flagsValuePlaceholder: "e.g. --headless",
1384
+ addFlag: "Add flag",
1385
+ removeFlag: "Delete flag",
1386
+ flagsRestore: "Restore defaults",
1387
+ flagsRestoreTitle: "Keep every flag the rows carry and append the defaults that are still missing",
1388
+ flagsWsl: "Flags WSL",
1389
+ flagsWslTitle: "A\xF1adir flags recomendados para WSL<->Windows",
1390
+ openWindowsChrome: "Prelaunch Windows Chrome",
1391
+ openWindowsChromeTitle: "Lanza el Chrome del lado Windows con --remote-debugging-port y deja el bridge en modo conexi\xF3n",
1392
+ openWindowsChromeBusy: "Iniciando Chrome de Windows\u2026",
1393
+ windowsChromeNotWsl: "Windows Chrome no aplica fuera de WSL.",
1394
+ windowsChromeNotFound: "No se encontr\xF3 Chrome en el lado Windows (Program Files / Program Files (x86)).",
1395
+ windowsChromeUnreachable: "Requiere networkingMode=mirrored en %USERPROFILE%\\.wslconfig, con wsl --shutdown: hoy el loopback de Windows no es alcanzable desde este host WSL.",
1396
+ connectModeLabel: "Connect mode",
1397
+ connectModeHint: "Modo conexi\xF3n: el bridge no lanza un Chrome local (conecta con --browserUrl / --wsEndpoint); la selecci\xF3n local queda a la espera.",
1398
+ reduceLabel: "Reduce log output",
1399
+ reduceTitle: "Capture the bridge's stderr into the plugin's log file",
1400
+ reduceHint: "Captures stderr into the plugin's log file. Turn it off to debug the raw bridge output; switching restarts the bridge connection.",
1401
+ docsMessage: "Find the complete list of server parameters (e.g., --headless, --isolated, --slim) and how to configure WebSocket connections in the Configuration Guide.",
1402
+ docsLinkLabel: "Configuration Guide",
1403
+ statusError: "Error",
1404
+ statusOk: "OK",
1405
+ chromeMissing: "Chrome executable not found",
1406
+ readOnly: "Settings are read-only in this deployment.",
1407
+ expand: "Expand",
1408
+ collapse: "Collapse"
1409
+ };
351
1410
 
352
- /**
353
- * Render the Chrome DevTools MCP settings/status card. Collapsible
354
- * like the stock plugin cards: the header is a disclosure button
355
- * (closed by default) and the controls live in the body.
356
- * @param props - locale copy, the card snapshot hook, and its actions.
357
- * @returns the card, or nothing until the namespace is served.
358
- */
359
- function ChromeMcpCard(props) {
360
- var t = props.t;
361
- var state = props.useChromeMcpCard((snapshot) => snapshot);
362
- var openState = react.useState(false);
363
- var open = openState[0];
364
- var setOpen = openState[1];
365
- var checkingState = react.useState(false);
366
- var isChecking = checkingState[0];
367
- var setChecking = checkingState[1];
368
- var bodyId = react.useId();
369
- var flagsRef = react.useRef(null);
370
- if (!state.available) return null;
371
- // Header status dot: the single error surface. Red while a
372
- // check/connection error, a rejected action, or the known
373
- // executable-missing flag (which survives bridge-update-success
374
- // clears of lastError) is present; hidden while healthy.
375
- var error = state.lastError || state.actionError || "";
376
- var tip = error !== "" ? error : state.chromeMissing === true ? t("chromeMissing") : "";
377
- var dotAria = tip !== "" ? t("statusError") : t("statusOk");
378
- return (0, reactJsx.jsx)("li", {
379
- className: CSS.card + (open ? " " + CSS.cardOpen : ""),
380
- children: [
381
- (0, reactJsx.jsxs)("button", {
382
- type: "button",
383
- className: CSS.header,
384
- "aria-expanded": open,
385
- "aria-label": t(open ? "collapse" : "expand") + ": " + t("title"),
386
- "aria-controls": bodyId,
387
- onClick: () => {
388
- setOpen(!open);
389
- if (!open && typeof props.refresh === "function") props.refresh();
390
- },
391
- children: [
392
- (0, reactJsx.jsxs)("span", {
393
- className: CSS.headText,
394
- children: [
395
- (0, reactJsx.jsxs)("div", {
396
- className: CSS.nameRow,
397
- children: [
398
- (0, reactJsx.jsx)("span", {
399
- className: CSS.name,
400
- children: t("title")
401
- }),
402
- tip !== ""
403
- ? (0, reactJsx.jsx)("span", {
404
- className: CSS.dot + " " + CSS.dotError,
405
- role: "img",
406
- "aria-label": dotAria,
407
- title: tip
408
- })
409
- : null
410
- ]
411
- }),
412
- (0, reactJsx.jsx)("span", { className: CSS.desc, children: t("description") })
413
- ]
414
- }),
415
- (0, reactJsx.jsx)("svg", {
416
- width: 14,
417
- height: 14,
418
- className: CSS.chevron + (open ? " " + CSS.chevronOpen : ""),
419
- viewBox: "0 0 14 14",
420
- fill: "none",
421
- xmlns: "http://www.w3.org/2000/svg",
422
- children: (0, reactJsx.jsx)("path", { d: CHEVRON_PATH, fill: "currentColor" })
423
- })
424
- ]
425
- }),
426
- open
427
- ? (0, reactJsx.jsxs)("div", {
428
- id: bodyId,
429
- className: CSS.body,
430
- children: [
431
- !state.writable
432
- ? (0, reactJsx.jsx)("p", { className: CSS.readOnly, role: "status", children: t("readOnly") })
433
- : null,
434
- (0, reactJsx.jsxs)("div", {
435
- className: CSS.field,
436
- children: [
437
- (0, reactJsx.jsxs)("div", {
438
- className: CSS.rowText,
439
- children: [
440
- (0, reactJsx.jsx)("div", { className: CSS.fieldTitle, children: t("flagsTitle") }),
441
- (0, reactJsx.jsx)("div", { className: CSS.fieldDesc, children: t("flagsHint") })
442
- ]
443
- })
444
- ]
445
- }),
446
- // Extra flags editor: one flag per line; the write goes
447
- // through props.saveFlags (host validate + fiber.update).
448
- // `key` re-mounts the textarea when the store value
449
- // changes underneath the editor (e.g. a save from another
450
- // window), so the shown value never drifts.
451
- state.writable
452
- ? (0, reactJsx.jsxs)("div", {
453
- className: CSS.editor,
454
- children: [
455
- (0, reactJsx.jsx)("textarea", {
456
- className: CSS.text,
457
- rows: Math.max(3, state.extraFlags.length),
458
- placeholder: t("flagsPlaceholder"),
459
- defaultValue: state.extraFlags.join("\n"),
460
- key: state.extraFlags.join("\n"),
461
- ref: flagsRef
462
- }),
463
- (0, reactJsx.jsx)("div", {
464
- className: CSS.row,
465
- children: (0, reactJsx.jsx)("button", {
466
- type: "button",
467
- className: CSS.button,
468
- disabled: !state.writable,
469
- onClick: () => {
470
- var raw = flagsRef.current && typeof flagsRef.current.value === "string" ? flagsRef.current.value : "";
471
- props.saveFlags(
472
- raw
473
- .split(/\r?\n/u)
474
- .map((line) => line.trim())
475
- .filter((line) => line !== "")
476
- );
477
- },
478
- children: t("flagsSave")
479
- })
480
- })
481
- ]
482
- })
483
- : (0, reactJsx.jsx)("p", {
484
- className: CSS.value,
485
- children:
486
- state.extraFlags.length > 0 ? state.extraFlags.join("\n") : t("flagsPlaceholder")
487
- }),
488
- (0, reactJsx.jsxs)("div", {
489
- className: CSS.row,
490
- children: [
491
- (0, reactJsx.jsx)("button", {
492
- type: "button",
493
- className: CSS.button,
494
- disabled: !state.writable || isChecking,
495
- onClick: () => {
496
- setChecking(true);
497
- var checkPromise;
498
- try {
499
- checkPromise = props.recheck();
500
- } catch (error) {
501
- checkPromise = Promise.reject(error);
502
- }
503
- if (checkPromise == null || typeof checkPromise.finally !== "function") {
504
- checkPromise = Promise.resolve(checkPromise);
505
- }
506
- checkPromise.finally(() => setChecking(false));
507
- },
508
- children: isChecking ? t("checking") : t("check")
509
- })
510
- ]
511
- }),
512
- // Executable version discovered by the last check.
513
- state.chromeVersion !== ""
514
- ? (0, reactJsx.jsxs)("div", {
515
- className: CSS.field,
516
- children: [
517
- (0, reactJsx.jsxs)("div", {
518
- className: CSS.rowText,
519
- children: [
520
- (0, reactJsx.jsx)("div", { className: CSS.fieldTitle, children: t("versionTitle") }),
521
- (0, reactJsx.jsx)("p", {
522
- className: CSS.value,
523
- children: state.chromeVersion
524
- })
525
- ]
526
- })
527
- ]
528
- })
529
- : null,
530
- // Product spec: the full configuration-guide message, with
531
- // the upstream URL rendered as the external link.
532
- (0, reactJsx.jsxs)("p", {
533
- className: CSS.docs,
534
- children: [
535
- t("docsMessage") + " ",
536
- (0, reactJsx.jsx)("a", {
537
- className: CSS.link,
538
- href: CONFIGURATION_URL,
539
- target: "_blank",
540
- rel: "noopener noreferrer",
541
- children: CONFIGURATION_URL
542
- })
543
- ]
544
- })
545
- ]
546
- })
547
- : null
548
- ]
549
- });
550
- }
1411
+ // src/client/locales/index.ts
1412
+ var DICTIONARIES = { en };
551
1413
 
552
- // ── plugin body ────────────────────────────────────────────────────
553
- /**
554
- * Mount the settings/status card.
555
- * @param ctx - the browser plugin context.
556
- */
557
- function apply(ctx) {
558
- ctx.effect(() => ctx.locale.register(LOCALE_NS, { en }), "chrome-mcp: dictionaries");
559
- var binder = ctx.settingsScope;
560
- var scope = binder.bind({ namespace: NS });
561
- // The shared describe mirror: the recheck action re-reads it while
562
- // the host's in-memory check status is en route.
563
- var controller = new ChromeMcpCardController(scope, binder.describe());
564
- ctx.effect(
565
- () => () => controller.dispose(),
566
- "chrome-mcp: card controller"
567
- );
568
- ctx.slots.inject("settings.plugin.item", () =>
569
- ctx.slots.register(
570
- {
571
- name: "settings.plugin.item",
572
- key: NS,
573
- locale: LOCALE_NS,
574
- inject: () => controller.inject()
575
- },
576
- ChromeMcpCard
577
- )
578
- );
579
- }
1414
+ // src/client/styles/index.ts
1415
+ var CARD_CSS_ID = PLUGIN_ID + "/ChromeMcpCard.module.css";
1416
+ function injectCardStyles() {
1417
+ if (typeof document === "undefined") return;
1418
+ if (document.querySelector('style[data-plugin-css="' + CARD_CSS_ID + '"]') !== null) return;
1419
+ const styleTag = document.createElement("style");
1420
+ styleTag.dataset.plugin = PLUGIN_ID;
1421
+ styleTag.dataset.pluginCss = CARD_CSS_ID;
1422
+ styleTag.textContent = cssText;
1423
+ document.head.appendChild(styleTag);
1424
+ }
580
1425
 
581
- exports.apply = apply;
582
- exports.inject = inject;
583
- return module.exports;
1426
+ // src/client/apply.ts
1427
+ function apply(ctx) {
1428
+ injectCardStyles();
1429
+ ctx.effect(() => ctx.locale.register(LOCALE_NAMESPACE, DICTIONARIES), `${SETTINGS_NAMESPACE}: dictionaries`);
1430
+ const binder = ctx.settingsScope;
1431
+ const scope = binder.bind({ namespace: SETTINGS_NAMESPACE });
1432
+ const controller = new ChromeMcpCardController(scope, binder.describe());
1433
+ ctx.effect(
1434
+ () => () => controller.dispose(),
1435
+ `${SETTINGS_NAMESPACE}: card controller`
1436
+ );
1437
+ ctx.slots.inject("settings.plugin.item", () => ctx.slots.register(
1438
+ {
1439
+ name: "settings.plugin.item",
1440
+ key: SETTINGS_NAMESPACE,
1441
+ locale: LOCALE_NAMESPACE,
1442
+ inject: () => controller.inject()
1443
+ },
1444
+ ChromeMcpCard
1445
+ ));
1446
+ }
1447
+ return module.exports;
584
1448
  }
585
1449
  });
1450
+ //# sourceMappingURL=client.js.map