opensafari-mcp 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +341 -0
  3. package/dist/115.index.js +2 -0
  4. package/dist/115.index.js.map +1 -0
  5. package/dist/67.index.js +2 -0
  6. package/dist/67.index.js.map +1 -0
  7. package/dist/679.index.js +2 -0
  8. package/dist/679.index.js.map +1 -0
  9. package/dist/681.index.js +2 -0
  10. package/dist/681.index.js.map +1 -0
  11. package/dist/838.index.js +2 -0
  12. package/dist/838.index.js.map +1 -0
  13. package/dist/auth/index.d.ts +3 -0
  14. package/dist/auth/index.d.ts.map +1 -0
  15. package/dist/auth/manager.d.ts +33 -0
  16. package/dist/auth/manager.d.ts.map +1 -0
  17. package/dist/cli/148.index.js +3 -0
  18. package/dist/cli/148.index.js.map +1 -0
  19. package/dist/cli/473.index.js +3 -0
  20. package/dist/cli/473.index.js.map +1 -0
  21. package/dist/cli/622.index.js +3 -0
  22. package/dist/cli/622.index.js.map +1 -0
  23. package/dist/cli/712.index.js +3 -0
  24. package/dist/cli/712.index.js.map +1 -0
  25. package/dist/cli/844.index.js +3 -0
  26. package/dist/cli/844.index.js.map +1 -0
  27. package/dist/cli/index.d.ts +3 -0
  28. package/dist/cli/index.d.ts.map +1 -0
  29. package/dist/cli/index.js +3 -0
  30. package/dist/cli/index.js.map +1 -0
  31. package/dist/comparison/cross-viewport.d.ts +36 -0
  32. package/dist/comparison/cross-viewport.d.ts.map +1 -0
  33. package/dist/comparison/index.d.ts +4 -0
  34. package/dist/comparison/index.d.ts.map +1 -0
  35. package/dist/comparison/report.d.ts +10 -0
  36. package/dist/comparison/report.d.ts.map +1 -0
  37. package/dist/config/defaults.d.ts +33 -0
  38. package/dist/config/defaults.d.ts.map +1 -0
  39. package/dist/config/global.d.ts +25 -0
  40. package/dist/config/global.d.ts.map +1 -0
  41. package/dist/config/index.d.ts +5 -0
  42. package/dist/config/index.d.ts.map +1 -0
  43. package/dist/config/tool-tiers.d.ts +7 -0
  44. package/dist/config/tool-tiers.d.ts.map +1 -0
  45. package/dist/errors/codes.d.ts +20 -0
  46. package/dist/errors/codes.d.ts.map +1 -0
  47. package/dist/errors/index.d.ts +4 -0
  48. package/dist/errors/index.d.ts.map +1 -0
  49. package/dist/errors/timeout.d.ts +20 -0
  50. package/dist/errors/timeout.d.ts.map +1 -0
  51. package/dist/index.d.ts +25 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +2 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/mcp-server.d.ts +41 -0
  56. package/dist/mcp-server.d.ts.map +1 -0
  57. package/dist/metrics/collector.d.ts +43 -0
  58. package/dist/metrics/collector.d.ts.map +1 -0
  59. package/dist/orchestration/index.d.ts +3 -0
  60. package/dist/orchestration/index.d.ts.map +1 -0
  61. package/dist/orchestration/workflow-engine.d.ts +90 -0
  62. package/dist/orchestration/workflow-engine.d.ts.map +1 -0
  63. package/dist/qa/audit.d.ts +42 -0
  64. package/dist/qa/audit.d.ts.map +1 -0
  65. package/dist/qa/detectors/auto-zoom.d.ts +4 -0
  66. package/dist/qa/detectors/auto-zoom.d.ts.map +1 -0
  67. package/dist/qa/detectors/dark-mode.d.ts +5 -0
  68. package/dist/qa/detectors/dark-mode.d.ts.map +1 -0
  69. package/dist/qa/detectors/fixed-stacking.d.ts +4 -0
  70. package/dist/qa/detectors/fixed-stacking.d.ts.map +1 -0
  71. package/dist/qa/detectors/horizontal-overflow.d.ts +4 -0
  72. package/dist/qa/detectors/horizontal-overflow.d.ts.map +1 -0
  73. package/dist/qa/detectors/hover-only.d.ts +4 -0
  74. package/dist/qa/detectors/hover-only.d.ts.map +1 -0
  75. package/dist/qa/detectors/index.d.ts +14 -0
  76. package/dist/qa/detectors/index.d.ts.map +1 -0
  77. package/dist/qa/detectors/input-type.d.ts +4 -0
  78. package/dist/qa/detectors/input-type.d.ts.map +1 -0
  79. package/dist/qa/detectors/keyboard-overlap.d.ts +4 -0
  80. package/dist/qa/detectors/keyboard-overlap.d.ts.map +1 -0
  81. package/dist/qa/detectors/orientation.d.ts +5 -0
  82. package/dist/qa/detectors/orientation.d.ts.map +1 -0
  83. package/dist/qa/detectors/pwa-meta.d.ts +4 -0
  84. package/dist/qa/detectors/pwa-meta.d.ts.map +1 -0
  85. package/dist/qa/detectors/safe-area.d.ts +4 -0
  86. package/dist/qa/detectors/safe-area.d.ts.map +1 -0
  87. package/dist/qa/detectors/scroll-lock.d.ts +4 -0
  88. package/dist/qa/detectors/scroll-lock.d.ts.map +1 -0
  89. package/dist/qa/detectors/touch-targets.d.ts +4 -0
  90. package/dist/qa/detectors/touch-targets.d.ts.map +1 -0
  91. package/dist/qa/detectors/vh100.d.ts +4 -0
  92. package/dist/qa/detectors/vh100.d.ts.map +1 -0
  93. package/dist/qa/history.d.ts +44 -0
  94. package/dist/qa/history.d.ts.map +1 -0
  95. package/dist/qa/index.d.ts +8 -0
  96. package/dist/qa/index.d.ts.map +1 -0
  97. package/dist/qa/report-markdown.d.ts +3 -0
  98. package/dist/qa/report-markdown.d.ts.map +1 -0
  99. package/dist/qa/types.d.ts +29 -0
  100. package/dist/qa/types.d.ts.map +1 -0
  101. package/dist/reliability/crash-watcher.d.ts +16 -0
  102. package/dist/reliability/crash-watcher.d.ts.map +1 -0
  103. package/dist/reliability/graceful-shutdown.d.ts +3 -0
  104. package/dist/reliability/graceful-shutdown.d.ts.map +1 -0
  105. package/dist/reliability/index.d.ts +4 -0
  106. package/dist/reliability/index.d.ts.map +1 -0
  107. package/dist/reliability/zombie-cleanup.d.ts +53 -0
  108. package/dist/reliability/zombie-cleanup.d.ts.map +1 -0
  109. package/dist/security/audit-logger.d.ts +2 -0
  110. package/dist/security/audit-logger.d.ts.map +1 -0
  111. package/dist/security/content-sanitizer.d.ts +32 -0
  112. package/dist/security/content-sanitizer.d.ts.map +1 -0
  113. package/dist/security/domain-guard.d.ts +16 -0
  114. package/dist/security/domain-guard.d.ts.map +1 -0
  115. package/dist/session-manager.d.ts +54 -0
  116. package/dist/session-manager.d.ts.map +1 -0
  117. package/dist/simulator/batch.d.ts +22 -0
  118. package/dist/simulator/batch.d.ts.map +1 -0
  119. package/dist/simulator/index.d.ts +15 -0
  120. package/dist/simulator/index.d.ts.map +1 -0
  121. package/dist/simulator/manager.d.ts +66 -0
  122. package/dist/simulator/manager.d.ts.map +1 -0
  123. package/dist/simulator/pool.d.ts +52 -0
  124. package/dist/simulator/pool.d.ts.map +1 -0
  125. package/dist/simulator/presets.d.ts +4 -0
  126. package/dist/simulator/presets.d.ts.map +1 -0
  127. package/dist/simulator/proxy.d.ts +78 -0
  128. package/dist/simulator/proxy.d.ts.map +1 -0
  129. package/dist/simulator/simctl.d.ts +12 -0
  130. package/dist/simulator/simctl.d.ts.map +1 -0
  131. package/dist/simulator/socket-finder.d.ts +22 -0
  132. package/dist/simulator/socket-finder.d.ts.map +1 -0
  133. package/dist/simulator/types.d.ts +21 -0
  134. package/dist/simulator/types.d.ts.map +1 -0
  135. package/dist/simulator/xcode-check.d.ts +15 -0
  136. package/dist/simulator/xcode-check.d.ts.map +1 -0
  137. package/dist/tools/appearance-toggle.d.ts +3 -0
  138. package/dist/tools/appearance-toggle.d.ts.map +1 -0
  139. package/dist/tools/auth.d.ts +3 -0
  140. package/dist/tools/auth.d.ts.map +1 -0
  141. package/dist/tools/batch-execute.d.ts +5 -0
  142. package/dist/tools/batch-execute.d.ts.map +1 -0
  143. package/dist/tools/batch-navigate.d.ts +5 -0
  144. package/dist/tools/batch-navigate.d.ts.map +1 -0
  145. package/dist/tools/batch-screenshot.d.ts +5 -0
  146. package/dist/tools/batch-screenshot.d.ts.map +1 -0
  147. package/dist/tools/click.d.ts +3 -0
  148. package/dist/tools/click.d.ts.map +1 -0
  149. package/dist/tools/cookies.d.ts +3 -0
  150. package/dist/tools/cookies.d.ts.map +1 -0
  151. package/dist/tools/cross-viewport-compare.d.ts +4 -0
  152. package/dist/tools/cross-viewport-compare.d.ts.map +1 -0
  153. package/dist/tools/device-boot.d.ts +3 -0
  154. package/dist/tools/device-boot.d.ts.map +1 -0
  155. package/dist/tools/device-list.d.ts +3 -0
  156. package/dist/tools/device-list.d.ts.map +1 -0
  157. package/dist/tools/device-rotate.d.ts +3 -0
  158. package/dist/tools/device-rotate.d.ts.map +1 -0
  159. package/dist/tools/device-shutdown.d.ts +3 -0
  160. package/dist/tools/device-shutdown.d.ts.map +1 -0
  161. package/dist/tools/dismiss-keyboard.d.ts +3 -0
  162. package/dist/tools/dismiss-keyboard.d.ts.map +1 -0
  163. package/dist/tools/index.d.ts +8 -0
  164. package/dist/tools/index.d.ts.map +1 -0
  165. package/dist/tools/inspect.d.ts +3 -0
  166. package/dist/tools/inspect.d.ts.map +1 -0
  167. package/dist/tools/javascript.d.ts +3 -0
  168. package/dist/tools/javascript.d.ts.map +1 -0
  169. package/dist/tools/long-press.d.ts +3 -0
  170. package/dist/tools/long-press.d.ts.map +1 -0
  171. package/dist/tools/navigate.d.ts +3 -0
  172. package/dist/tools/navigate.d.ts.map +1 -0
  173. package/dist/tools/orchestration-tools.d.ts +5 -0
  174. package/dist/tools/orchestration-tools.d.ts.map +1 -0
  175. package/dist/tools/press.d.ts +3 -0
  176. package/dist/tools/press.d.ts.map +1 -0
  177. package/dist/tools/qa-audit.d.ts +3 -0
  178. package/dist/tools/qa-audit.d.ts.map +1 -0
  179. package/dist/tools/qa-detectors.d.ts +3 -0
  180. package/dist/tools/qa-detectors.d.ts.map +1 -0
  181. package/dist/tools/query-dom.d.ts +3 -0
  182. package/dist/tools/query-dom.d.ts.map +1 -0
  183. package/dist/tools/read-page.d.ts +3 -0
  184. package/dist/tools/read-page.d.ts.map +1 -0
  185. package/dist/tools/screenshot.d.ts +3 -0
  186. package/dist/tools/screenshot.d.ts.map +1 -0
  187. package/dist/tools/scroll.d.ts +3 -0
  188. package/dist/tools/scroll.d.ts.map +1 -0
  189. package/dist/tools/select-option.d.ts +3 -0
  190. package/dist/tools/select-option.d.ts.map +1 -0
  191. package/dist/tools/swipe.d.ts +3 -0
  192. package/dist/tools/swipe.d.ts.map +1 -0
  193. package/dist/tools/type.d.ts +3 -0
  194. package/dist/tools/type.d.ts.map +1 -0
  195. package/dist/tools/wait-for.d.ts +3 -0
  196. package/dist/tools/wait-for.d.ts.map +1 -0
  197. package/dist/transports/http.d.ts +57 -0
  198. package/dist/transports/http.d.ts.map +1 -0
  199. package/dist/transports/index.d.ts +38 -0
  200. package/dist/transports/index.d.ts.map +1 -0
  201. package/dist/transports/stdio.d.ts +16 -0
  202. package/dist/transports/stdio.d.ts.map +1 -0
  203. package/dist/types/browser-backend.d.ts +84 -0
  204. package/dist/types/browser-backend.d.ts.map +1 -0
  205. package/dist/types/mcp.d.ts +63 -0
  206. package/dist/types/mcp.d.ts.map +1 -0
  207. package/dist/types/tool-manifest.d.ts +52 -0
  208. package/dist/types/tool-manifest.d.ts.map +1 -0
  209. package/dist/utils/format-age.d.ts +5 -0
  210. package/dist/utils/format-age.d.ts.map +1 -0
  211. package/dist/utils/format-error.d.ts +5 -0
  212. package/dist/utils/format-error.d.ts.map +1 -0
  213. package/dist/utils/logger.d.ts +10 -0
  214. package/dist/utils/logger.d.ts.map +1 -0
  215. package/dist/utils/rate-limiter.d.ts +72 -0
  216. package/dist/utils/rate-limiter.d.ts.map +1 -0
  217. package/dist/utils/request-queue.d.ts +37 -0
  218. package/dist/utils/request-queue.d.ts.map +1 -0
  219. package/dist/utils/schema-validator.d.ts +12 -0
  220. package/dist/utils/schema-validator.d.ts.map +1 -0
  221. package/dist/utils/url-utils.d.ts +5 -0
  222. package/dist/utils/url-utils.d.ts.map +1 -0
  223. package/dist/utils/with-timeout.d.ts +5 -0
  224. package/dist/utils/with-timeout.d.ts.map +1 -0
  225. package/dist/version.d.ts +5 -0
  226. package/dist/version.d.ts.map +1 -0
  227. package/dist/watchdog/event-loop-monitor.d.ts +86 -0
  228. package/dist/watchdog/event-loop-monitor.d.ts.map +1 -0
  229. package/dist/watchdog/simulator-monitor.d.ts +16 -0
  230. package/dist/watchdog/simulator-monitor.d.ts.map +1 -0
  231. package/dist/webkit/client.d.ts +106 -0
  232. package/dist/webkit/client.d.ts.map +1 -0
  233. package/dist/webkit/index.d.ts +3 -0
  234. package/dist/webkit/index.d.ts.map +1 -0
  235. package/package.json +84 -0
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ (()=>{var e,t={183(e,t,r){"use strict";r.d(t,{D:()=>o});const o={PARSE_ERROR:-32700,INVALID_REQUEST:-32600,METHOD_NOT_FOUND:-32601,INVALID_PARAMS:-32602,INTERNAL_ERROR:-32603}},322(e){function t(e){return Promise.resolve().then(()=>{var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t})}t.keys=()=>[],t.resolve=t,t.id=322,e.exports=t},943(e){"use strict";e.exports=require("fs/promises")},598(e){"use strict";e.exports=require("node:crypto")},67(e){"use strict";e.exports=require("node:http")},857(e){"use strict";e.exports=require("os")},928(e){"use strict";e.exports=require("path")},785(e){"use strict";e.exports=require("readline")}},r={};function o(e){var n=r[e];if(void 0!==n)return n.exports;var i=r[e]={exports:{}};return t[e](i,i.exports,o),i.exports}o.m=t,o.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return o.d(t,{a:t}),t},o.d=(e,t)=>{for(var r in t)o.o(t,r)&&!o.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},o.f={},o.e=e=>Promise.all(Object.keys(o.f).reduce((t,r)=>(o.f[r](e,t),t),[])),o.u=e=>"cli/"+e+".index.js",o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),e={792:1},o.f.require=(t,r)=>{if(!e[t]){var n=require("../"+o.u(t));e[t]||(t=>{var r=t.modules,n=t.ids,i=t.runtime;for(var s in r)o.o(r,s)&&(o.m[s]=r[s]);i&&i(o);for(var a=0;a<n.length;a++)e[n[a]]=1})(n)}},(()=>{"use strict";const e=require("commander");var t=o(183);const r={navigate:1,screenshot:1,javascript:1,read_page:1,click:1,type:1,scroll:1,query_dom:1,cookies:1,device_boot:1,device_shutdown:1,inspect:2,wait_for:2,long_press:2,swipe:2,press:2,dismiss_keyboard:2,select_option:2,device_list:2,device_rotate:2,appearance_toggle:2,device_snapshot:2,auth_save:3,auth_restore:3,auth_list:3,workflow_init:3,workflow_status:3,workflow_collect:3,workflow_collect_partial:3,workflow_cleanup:3,worker_update:3,worker_complete:3,qa_full_audit:3,batch_screenshot:3,batch_navigate:3,batch_execute:3,cross_viewport_compare:3},n=require("fs");var i=o(928),s=o(857);function a(e){try{return new URL(e).hostname}catch{return""}}let c=!1;const l=["password","cookie","token","secret","auth","credential","value","text"];function u(e){const t=e.toLowerCase();return l.some(e=>t.includes(e))}function d(e){const t={};for(const[r,o]of Object.entries(e))u(r)?t[r]="[REDACTED]":"string"==typeof o&&o.length>100?t[r]=o.slice(0,100)+"...":t[r]=o;return JSON.stringify(t)}function p(e,t,r,o){const l={timestamp:(new Date).toISOString(),tool:e,domain:(u=o||r.url,u&&a(u)||null),sessionId:t,args_summary:d(r)};var u;const p=i.join(s.homedir(),".opensafari","audit.log"),h=i.dirname(p);if(!c)try{n.mkdirSync(h,{recursive:!0}),c=!0}catch{return}const m=JSON.stringify(l)+"\n";n.appendFile(p,m,e=>{e&&console.error("[audit-logger] write failed:",e.code)})}let h=null;function m(){return h}function w(e){h=e}class y{tools=new Map;transport=null;currentTier=1;auditLogEnabled=!1;registerTool(e,t){const o=(n=e.name,r[n]??2);var n;this.tools.set(e.name,{definition:e,handler:t,tier:o})}getToolHandler(e){return this.tools.get(e)?.handler}getRegisteredTools(){return Array.from(this.tools.keys())}async start(e={}){const t=e.transport??"stdio";this.transport=await async function(e,t){if("http"===e){const{HTTPTransport:e}=await o.e(622).then(o.bind(o,622));return new e(t?.port||3100)}const{StdioTransport:r}=await o.e(473).then(o.bind(o,473));return new r}(t,{port:e.port}),this.transport.onMessage(e=>this.handleMessage(e)),this.transport.start(),console.error(`[OpenSafari] MCP server started (${t})`)}async stop(){this.transport&&(await this.transport.close(),this.transport=null),console.error("[OpenSafari] MCP server stopped")}setTier(e){this.currentTier=e}getTier(){return this.currentTier}enableAuditLog(){this.auditLogEnabled=!0}async handleMessage(e){const r="id"in e&&void 0!==e.id,o=e.id??0,n=e.method;if(!n)return r?{jsonrpc:"2.0",id:o,error:{code:t.D.INVALID_REQUEST,message:"Missing method field"}}:null;if(!r)return null;const i=e;switch(n){case"initialize":return this.handleInitialize(i);case"tools/list":return this.handleToolsList(i);case"tools/call":return this.handleToolsCall(i);default:return{jsonrpc:"2.0",id:o,error:{code:t.D.METHOD_NOT_FOUND,message:`Method not found: ${n}`}}}}handleInitialize(e){return{jsonrpc:"2.0",id:e.id,result:{protocolVersion:"2024-11-05",capabilities:{tools:{}},serverInfo:{name:"opensafari-mcp",version:"0.0.1"}}}}handleToolsList(e){const t=Array.from(this.tools.values()).filter(e=>e.tier<=this.currentTier).map(e=>e.definition);return{jsonrpc:"2.0",id:e.id,result:{tools:t}}}async handleToolsCall(e){const r=e.params,o=r?.name,n=r?.arguments??{};if(!o)return{jsonrpc:"2.0",id:e.id,error:{code:t.D.INVALID_PARAMS,message:"tools/call requires params.name"}};const i=this.tools.get(o);if(!i)return{jsonrpc:"2.0",id:e.id,error:{code:t.D.INVALID_PARAMS,message:`Unknown tool: ${o}`}};const s=e.params?._sessionId??"default";try{const t=await i.handler(s,n);return this.auditLogEnabled&&p(o,s,n),{jsonrpc:"2.0",id:e.id,result:t}}catch(t){this.auditLogEnabled&&p(o,s,n);const r=t instanceof Error?t.message:String(t);return{jsonrpc:"2.0",id:e.id,result:{content:[{type:"text",text:`Error: ${r}`}],isError:!0}}}}}let f=[];function g(e){e.registerTool({name:"navigate",description:"Navigate to a URL in real Safari on iOS Simulator",inputSchema:{type:"object",properties:{url:{type:"string",description:"URL to navigate to"},waitUntil:{type:"string",enum:["load","domcontentloaded","networkidle"],description:"Wait strategy"}},required:["url"]}},async(e,t)=>{const r=t.url;!function(e){if(!f||0===f.length)return;const t=function(e){if("about:blank"===e||e.startsWith("about:")||e.startsWith("safari:")||e.startsWith("safari-extension:"))return null;if(e.startsWith("data:"))return null;const t=a(e).toLowerCase();return t||a("https://"+e).toLowerCase()||null}(e);if(!t)return;const r=f.find(e=>function(e){if(e.length>253)throw new Error(`Domain pattern too long (${e.length} chars, max 253): "${e.slice(0,50)}..."`);const t=e.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*/g,"[^.]*");return new RegExp(`^${t}$`,"i")}(e).test(t));if(r)throw new Error(`Access to domain "${t}" is blocked by security policy (matched pattern: "${r}"). Configure blocked_domains in your OpenSafari security settings to change this.`)}(r);const o=m();if(!o)return{content:[{type:"text",text:"Error: Safari not connected"}],isError:!0};const n=await o.navigate({url:r,waitUntil:t.waitUntil});return{content:[{type:"text",text:JSON.stringify(n)}]}})}var v=o(943);const b=require("crypto"),k=require("child_process"),S=require("util"),x=(0,S.promisify)(k.execFile);class E{async exec(e,t){try{const{stdout:r}=await x("xcrun",["simctl",...e],{timeout:t?.timeout??3e4});return r}catch(t){const r=t;throw new T(`simctl ${e.join(" ")} failed: ${r.stderr||r.message}`,e,r.code)}}async execJson(e){const t=await this.exec([...e,"-j"]);try{return JSON.parse(t)}catch{throw new T(`Failed to parse JSON from simctl ${e.join(" ")}`,e)}}}class T extends Error{args;exitCode;constructor(e,t,r){super(e),this.args=t,this.exitCode=r,this.name="SimctlError"}}const $={"iphone-17e":{name:"iPhone 17e",w:390,h:844,dpr:3},"iphone-17":{name:"iPhone 17",w:402,h:874,dpr:3},"iphone-air":{name:"iPhone Air",w:420,h:912,dpr:3},"iphone-17-pro":{name:"iPhone 17 Pro",w:402,h:874,dpr:3},"iphone-17-pro-max":{name:"iPhone 17 Pro Max",w:440,h:956,dpr:3},"ipad-air":{name:"iPad Air 13-inch (M4)",w:1024,h:1366,dpr:2},"ipad-pro":{name:"iPad Pro 13-inch (M5)",w:1032,h:1376,dpr:2}};class P{simctl=new E;async listDevices(){const e=await this.simctl.execJson(["list","devices"]),t=[];for(const[r,o]of Object.entries(e.devices)){const e=r.match(/iOS-(\d+)-(\d+)/),n=e?`${e[1]}.${e[2]}`:"unknown";for(const e of o)e.isAvailable&&t.push({udid:e.udid,name:e.name,state:e.state,isAvailable:e.isAvailable,runtime:r,runtimeVersion:n})}return t}async listRuntimes(){return(await this.simctl.execJson(["list","runtimes"])).runtimes.filter(e=>e.isAvailable)}async listBooted(){return(await this.listDevices()).filter(e=>"Booted"===e.state)}async getDevice(e){return(await this.listDevices()).find(t=>t.udid===e)??null}async resolveDevice(e){const t=await this.listDevices(),r=t.find(t=>t.udid===e);if(r)return r;const o=$[e];if(o){const e=t.find(e=>e.name===o.name);if(e)return e}const n=e.toLowerCase(),i=t.find(e=>e.name.toLowerCase().includes(n));if(i)return i;const s=n.split(/[\s-]+/),a=t.find(e=>{const t=e.name.toLowerCase();return s.every(e=>t.includes(e))});if(a)return a;throw new I(e,t.map(e=>e.name))}async checkRuntimes(){const e=(await this.listRuntimes()).filter(e=>"iOS"===e.platform),t=[],r=[];return 0===e.length&&(t.push("No iOS Simulator runtimes found"),r.push("Run: xcodebuild -downloadPlatform iOS")),{installed:e,issues:t,suggestions:r}}async boot(e,t){const r=await this.resolveDevice(e);if("Booted"===r.state)return r;await this.simctl.exec(["boot",r.udid]);const o=t?.timeout??15e3,n=Date.now();for(;Date.now()-n<o;){const e=await this.getDevice(r.udid);if("Booted"===e?.state)return e;await new Promise(e=>setTimeout(e,1e3))}throw new O(r.udid,r.name,o)}async shutdown(e,t){const r=await this.getDevice(e);if(!r||"Shutdown"===r.state)return;try{await this.simctl.exec(["shutdown",e])}catch{}const o=t?.timeout??1e4,n=Date.now();for(;Date.now()-n<o;){const t=await this.getDevice(e);if(!t||"Shutdown"===t.state)return;await new Promise(e=>setTimeout(e,1e3))}try{await this.simctl.exec(["shutdown",e]),await new Promise(e=>setTimeout(e,5e3));const t=await this.getDevice(e);if(!t||"Shutdown"===t.state)return}catch{}console.error(`[SimulatorManager] Force erasing device ${e} after shutdown timeout`),await this.simctl.exec(["erase",e])}async bootPreset(e){return this.boot(e)}async openUrl(e,t){try{new URL(t)}catch{throw new Error(`Invalid URL: ${t}`)}const r=await this.getDevice(e);if(!r||"Booted"!==r.state)throw new D(e);await this.simctl.exec(["openurl",e,t]),await new Promise(e=>setTimeout(e,1e3))}async screenshot(e,t){const r=await this.getDevice(e);if(!r||"Booted"!==r.state)throw new D(e);const o=t?.format??"png",n=i.join(s.tmpdir(),`opensafari-screenshot-${(0,b.randomUUID)()}.${o}`);try{return await this.simctl.exec(["io",e,"screenshot",`--type=${o}`,n],{timeout:1e4}),await v.readFile(n)}finally{await v.unlink(n).catch(()=>{})}}async screenshotBase64(e,t){return(await this.screenshot(e,t)).toString("base64")}getSimctl(){return this.simctl}async setAppearance(e,t){const r=await this.getDevice(e);if(!r||"Booted"!==r.state)throw new D(e);await this.simctl.exec(["ui",e,"appearance",t])}async getAppearance(e){const t=await this.getDevice(e);if(!t||"Booted"!==t.state)throw new D(e);return"dark"===(await this.simctl.exec(["ui",e,"appearance"])).trim().toLowerCase()?"dark":"light"}async toggleAppearance(e){const t="light"===await this.getAppearance(e)?"dark":"light";return await this.setAppearance(e,t),t}async rotate(e){const t=await this.getDevice(e);if(!t||"Booted"!==t.state)throw new D(e);try{const e=(0,S.promisify)(k.execFile);await e("osascript",["-e",'tell application "Simulator" to activate',"-e","delay 0.5","-e",'tell application "System Events" to tell process "Simulator" to click menu item "Rotate Left" of menu "Device" of menu bar 1'],{timeout:1e4})}catch{console.error("[SimulatorManager] Rotation via AppleScript failed — use WebKit Protocol viewport override as fallback (Epic 1B)")}}async cloneDevice(e,t){return(await this.simctl.exec(["clone",e,t])).trim()}async deleteDevice(e){await this.simctl.exec(["delete",e])}async overrideStatusBar(e){const t=await this.getDevice(e);if(!t||"Booted"!==t.state)throw new D(e);await this.simctl.exec(["status_bar",e,"override","--time","9:41","--batteryLevel","100","--cellularBars","4"])}}class O extends Error{deviceId;deviceName;timeoutMs;constructor(e,t,r){super(`Simulator boot timeout: "${t}" (${e}) did not boot within ${r}ms`),this.deviceId=e,this.deviceName=t,this.timeoutMs=r,this.name="BootTimeoutError"}}Error;class I extends Error{requested;available;constructor(e,t){super(`Device not found: "${e}". Available: ${t.slice(0,5).join(", ")}${t.length>5?"...":""}`),this.requested=e,this.available=t,this.name="DeviceNotFoundError"}}class D extends Error{deviceId;constructor(e){super(`Device ${e} is not booted. Call boot() first.`),this.deviceId=e,this.name="DeviceNotBootedError"}}Error;const _=require("http");var C=o.n(_);const R=require("net"),A=(0,S.promisify)(k.execFile),N="com.apple.webinspectord_sim.socket",j=["/private/var/tmp","/private/tmp"];async function M(e){return await async function(e){try{const{stdout:t}=await A("lsof",["-U"],{timeout:5e3}),r=t.split("\n"),o=[];for(const e of r){if(!e.startsWith("launchd_s")||!e.includes(N))continue;const t=e.split(/\s+/),r=parseInt(t[1],10),n=t[t.length-1];n&&!o.some(e=>e.socketPath===n)&&o.push({pid:r,socketPath:n})}if(0===o.length)return null;if(e){for(const{pid:t,socketPath:r}of o)try{const{stdout:o}=await A("ps",["-p",String(t),"-o","args="]);if(o.includes(e)&&await L(r))return r}catch{continue}return null}for(const{socketPath:e}of o)if(await L(e))return e;return null}catch{return null}}(e?.targetUdid)||(e?.targetUdid?null:async function(){const e=[];for(const t of j){let r;try{r=await v.readdir(t)}catch{continue}for(const o of r){if(!o.startsWith("com.apple.launchd."))continue;const r=i.join(t,o,N);try{const t=await v.stat(r);e.push({socketPath:r,mtimeMs:t.mtimeMs})}catch{continue}}}if(0===e.length)return null;e.sort((e,t)=>t.mtimeMs-e.mtimeMs);const t=[];for(const{socketPath:r}of e){if(await L(r))return t.length>0&&q(t),r;t.push(r)}return t.length>0&&q(t),null}())}function L(e){return new Promise(t=>{const r=R.connect({path:e});r.setTimeout(2e3),r.once("connect",()=>{r.destroy(),t(!0)}),r.once("error",()=>{r.destroy(),t(!1)}),r.once("timeout",()=>{r.destroy(),t(!1)})})}function q(e){for(const t of e){const e=i.dirname(t);v.rm(e,{recursive:!0,force:!0}).catch(t=>{console.error(`[socket-finder] Failed to clean stale socket dir ${e}: ${t.message}`)})}console.error(`[socket-finder] Cleaned ${e.length} stale socket(s)`)}const W=(0,S.promisify)(k.execFile),B=[9321,9221],F=[9322,9222];function U(e,t){return new Promise(r=>{const o=_.get(`http://localhost:${e}`,{timeout:2e3},e=>{let o="";e.on("data",e=>{o+=e.toString()}),e.on("end",()=>{r(o.includes(t))}),e.on("error",()=>r(!1))});o.on("error",()=>r(!1)),o.on("timeout",()=>{o.destroy(),r(!1)})})}const J=require("events"),z=require("ws");var H=o.n(z);class K extends J.EventEmitter{options;ws=null;messageId=0;pendingRequests=new Map;enabledDomains=new Set;connected=!1;heartbeatTimer=null;lastUrl="";reconnecting=!1;activeTargetId=null;innerMessageId=0;innerPendingRequests=new Map;targetReady=null;targetReadyResolve=null;constructor(e){super(),this.options=e}async connect(e){const t=e?.retries??0,r=e?.retryDelay??2e3;let o=null;for(let e=0;e<=t;e++)try{const e=await this.listTargets(),t=this.options.targetIndex??0;if(0===e.length)throw new V("No Safari targets found. Is Safari open in the simulator?");const r=e[Math.min(t,e.length-1)];return void await this.connectToTarget(r.webSocketDebuggerUrl)}catch(n){o=n,e<t&&(console.error(`[WebKitClient] Connect attempt ${e+1} failed, retrying in ${r}ms...`),await new Promise(e=>setTimeout(e,r)))}throw o??new Error("WebKit connection failed")}async disconnect(){this.stopHeartbeat(),this.clearPendingRequests(),this.ws&&(this.ws.removeAllListeners(),this.ws.readyState===H().OPEN&&this.ws.close(),this.ws=null),this.connected=!1,this.enabledDomains.clear(),this.activeTargetId=null,this.targetReady=null,this.targetReadyResolve=null}isConnected(){return this.connected&&this.ws?.readyState===H().OPEN}async listTargets(){const e=`http://${this.options.host}:${this.options.port}/json`,t=await this.httpGet(e);return JSON.parse(t)}async send(e,t){if(!this.ws||this.ws.readyState!==H().OPEN)throw new V("WebSocket not connected");if(!this.activeTargetId)throw new V("No active target. Is Safari open in the simulator?");const r=++this.innerMessageId,o=++this.messageId,n=this.options.sendTimeout??15e3,i=new Promise((t,o)=>{const i=setTimeout(()=>{this.innerPendingRequests.delete(r),o(new G(`${e} timed out after ${n}ms`))},n);this.innerPendingRequests.set(r,{resolve:t,reject:o,timer:i})}),s=setTimeout(()=>{this.pendingRequests.delete(o)},n);this.pendingRequests.set(o,{resolve:()=>{},reject:e=>{const t=this.innerPendingRequests.get(r);t&&(clearTimeout(t.timer),this.innerPendingRequests.delete(r),t.reject(e))},timer:s});const a=JSON.stringify({id:r,method:e,params:t});return this.ws.send(JSON.stringify({id:o,method:"Target.sendMessageToTarget",params:{targetId:this.activeTargetId,message:a}})),i}handleMessage(e){let t;try{t=JSON.parse(e)}catch{return}if("Target.targetCreated"===t.method){const e=t.params?.targetInfo;if("page"===e?.type){this.activeTargetId=e.targetId;const t=[...this.enabledDomains];return this.enabledDomains.clear(),void Promise.all(t.map(e=>this.enableDomain(e).catch(t=>{console.error(`[WebKitClient] Failed to re-enable ${e} on new target: ${t.message}`)}))).then(()=>{this.targetReadyResolve?.()})}return}if("Target.targetDestroyed"!==t.method){if("Target.dispatchMessageFromTarget"===t.method){let e;try{e=JSON.parse(t.params.message)}catch{return}if(void 0!==e.id){const t=this.innerPendingRequests.get(e.id);t&&(clearTimeout(t.timer),this.innerPendingRequests.delete(e.id),e.error?t.reject(new X(e.error.message??JSON.stringify(e.error),e.error.code)):t.resolve(e.result))}else e.method&&this.emit(e.method,e.params);return}if(void 0!==t.id){const e=this.pendingRequests.get(t.id);e&&(clearTimeout(e.timer),this.pendingRequests.delete(t.id),t.error&&e.reject(new X(t.error.message??JSON.stringify(t.error),t.error.code)))}else t.method&&this.emit(t.method,t.params)}else t.params?.targetId===this.activeTargetId&&(this.activeTargetId=null)}async enableDomain(e){this.enabledDomains.has(e)||(await this.send(`${e}.enable`),this.enabledDomains.add(e))}startHeartbeat(){const e=this.options.heartbeatInterval??3e4;this.heartbeatTimer=setInterval(async()=>{try{await this.send("Runtime.evaluate",{expression:"1"})}catch{this.handleDisconnect()}},e)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}async handleDisconnect(){if(this.reconnecting)return;this.reconnecting=!0,this.connected=!1;for(const[,e]of this.innerPendingRequests)clearTimeout(e.timer),e.reject(new V("Connection lost during reconnect"));this.innerPendingRequests.clear(),this.activeTargetId=null,this.stopHeartbeat();let e=0;for(;e<10;){e++;const t=Math.min(1e3*Math.pow(2,e-1),3e4);console.error(`[WebKitClient] Reconnection attempt ${e}/10 in ${t}ms`),await new Promise(e=>setTimeout(e,t));try{await this.connect(),console.error("[WebKitClient] Reconnected successfully");const e=[...this.enabledDomains];this.enabledDomains.clear();for(const t of e)await this.enableDomain(t);return this.lastUrl&&await this.navigate({url:this.lastUrl}),this.reconnecting=!1,void this.emit("reconnected")}catch{}}this.reconnecting=!1,this.emit("disconnect"),console.error("[WebKitClient] Failed to reconnect after 10 attempts")}async navigate(e){const t=Date.now();this.lastUrl=e.url,await this.enableDomain("Page"),await this.enableDomain("Network");try{await this.send("Page.navigate",{url:e.url})}catch(t){if(!(t instanceof X&&-32601===t.code))throw t;await this.evaluate(`window.location.href = ${JSON.stringify(e.url)}`)}const r=e.waitUntil,o=Date.now(),n=e.timeout??3e4;for(;Date.now()-o<n;){await new Promise(e=>setTimeout(e,300));try{const e=await this.evaluate("document.readyState");if("networkidle"===r){if("complete"===e){await new Promise(e=>setTimeout(e,500));break}}else if("domcontentloaded"===r){if("interactive"===e||"complete"===e)break}else if("load"===r){if("complete"===e)break}else if("complete"===e)break}catch{continue}}const i=await this.evaluate("document.readyState").catch(()=>"");if("complete"!==i&&i!==("domcontentloaded"===r?"interactive":"complete"))throw new G(`Navigation timeout after ${n}ms (readyState: ${i})`);return{url:await this.evaluate("document.URL").catch(()=>e.url),status:await this.evaluate("(function() { try { var e = performance.getEntriesByType('navigation')[0]; return e ? e.responseStatus || 200 : 200; } catch(ex) { return 200; } })()").catch(()=>200),loadTime:Date.now()-t}}async screenshot(e){try{const t=await this.evaluate("({w: window.innerWidth, h: window.innerHeight})"),r=e?.clip??{x:0,y:0,width:t.w,height:t.h},o=(await this.send("Page.snapshotRect",{x:r.x,y:r.y,width:r.width,height:r.height,coordinateSystem:"Viewport"})).dataURL.split(",")[1];if(!o)throw new Error("Invalid dataURL from Page.snapshotRect");return Buffer.from(o,"base64")}catch{throw new Error("Screenshot failed — use SimulatorManager.screenshot() as fallback")}}async evaluate(e){const t=await this.send("Runtime.evaluate",{expression:e,returnByValue:!1,emulateUserGesture:!0});if(t.wasThrown)throw new Q(t.result?.description??"Evaluation failed");if("object"===t.result?.type&&t.result?.objectId&&("promise"===t.result?.subtype||"Promise"===t.result?.className)){const e=await this.send("Runtime.awaitPromise",{promiseObjectId:t.result.objectId,returnByValue:!0});if(e.wasThrown)throw new Q(e.result?.description??"Promise rejected");return e.result?.value}if(t.result?.objectId&&void 0===t.result?.value){const e=await this.send("Runtime.callFunctionOn",{objectId:t.result.objectId,functionDeclaration:"function() { return this; }",returnByValue:!0});return e.result?.value}return t.result?.value}async readPage(){return this.evaluate("\n (function() {\n const walker = document.createTreeWalker(\n document.body,\n NodeFilter.SHOW_TEXT,\n {\n acceptNode(node) {\n return node.textContent && node.textContent.trim()\n ? NodeFilter.FILTER_ACCEPT\n : NodeFilter.FILTER_REJECT;\n }\n }\n );\n const parts = [];\n let node;\n while (node = walker.nextNode()) {\n parts.push(node.textContent.trim());\n }\n return parts.join('\\n');\n })()\n ")}async getCookies(e){try{const t=await this.send("Page.getCookies");if(t?.cookies)return t.cookies.filter(t=>!e||(t.domain||"").includes(e)).map(e=>({name:e.name||"",value:e.value||"",domain:e.domain||"",path:e.path||"/",expires:"number"==typeof e.expires?e.expires:-1,httpOnly:!!e.httpOnly,secure:!!e.secure,...e.sameSite?{sameSite:e.sameSite}:{}}))}catch{}const t=await this.evaluate("document.cookie");return t?t.split(";").map(t=>{const[r,...o]=t.trim().split("=");return{name:r.trim(),value:o.join("="),domain:e??"",path:"/",expires:-1,httpOnly:!1,secure:!1}}).filter(e=>e.name):[]}async setCookies(e){for(const t of e)try{await this.send("Page.setCookie",{name:t.name,value:t.value,domain:t.domain||void 0,path:t.path||"/",expires:t.expires>0?t.expires:void 0,httpOnly:t.httpOnly||void 0,secure:t.secure||void 0,sameSite:t.sameSite||void 0})}catch{const e=[`${t.name}=${t.value}`];t.path&&e.push(`path=${t.path}`),t.domain&&e.push(`domain=${t.domain}`),t.secure&&e.push("secure"),t.expires&&t.expires>0&&e.push(`expires=${new Date(1e3*t.expires).toUTCString()}`),await this.evaluate(`document.cookie = ${JSON.stringify(e.join("; "))}`)}}async clearCookies(){try{const e=await this.getCookies();for(const t of e)await this.send("Page.deleteCookie",{cookieName:t.name,url:`https://${t.domain}${t.path}`});return}catch{}await this.evaluate("\n document.cookie.split(';').forEach(function(c) {\n var name = c.trim().split('=')[0];\n document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';\n });\n ")}async click(e){let t,r;if("string"==typeof e){const o=await this.getElementCenter(e);if(!o)throw new Error(`Element not found: ${e}`);t=o.x,r=o.y}else t=e.x,r=e.y;await this.evaluate(`\n (function(x, y) {\n var el = document.elementFromPoint(x, y);\n if (!el) return;\n var touch = document.createTouch(window, el, 1, x, y, x, y);\n var touchList = document.createTouchList(touch);\n var emptyList = document.createTouchList();\n el.dispatchEvent(new TouchEvent('touchstart', { touches: touchList, changedTouches: touchList, bubbles: true }));\n el.dispatchEvent(new TouchEvent('touchend', { touches: emptyList, changedTouches: touchList, bubbles: true }));\n el.click();\n })(${t}, ${r})\n `)}async type(e,t,r){if(await this.evaluate(`\n (function() {\n var el = document.querySelector(${JSON.stringify(e)});\n if (el && typeof el.focus === 'function') el.focus();\n })()\n `),r?.delay)for(const o of t)await this.evaluate(`\n (function() {\n var el = document.querySelector(${JSON.stringify(e)});\n if (!el) return;\n var ev = new KeyboardEvent('keydown', { key: ${JSON.stringify(o)}, bubbles: true });\n el.dispatchEvent(ev);\n el.dispatchEvent(new KeyboardEvent('keypress', { key: ${JSON.stringify(o)}, bubbles: true }));\n // Walk the element's own prototype chain to find the value setter.\n // Using window.HTMLInputElement.prototype would resolve to the\n // inspector realm's prototype, causing a cross-realm TypeError.\n var p = Object.getPrototypeOf(el);\n while (p && !Object.getOwnPropertyDescriptor(p, 'value')) {\n p = Object.getPrototypeOf(p);\n }\n var desc = p ? Object.getOwnPropertyDescriptor(p, 'value') : null;\n if (desc && desc.set) {\n desc.set.call(el, el.value + ${JSON.stringify(o)});\n } else {\n el.value += ${JSON.stringify(o)};\n }\n el.dispatchEvent(new Event('input', { bubbles: true }));\n el.dispatchEvent(new KeyboardEvent('keyup', { key: ${JSON.stringify(o)}, bubbles: true }));\n })()\n `),await new Promise(e=>setTimeout(e,r.delay));else await this.evaluate(`\n (function() {\n var el = document.querySelector(${JSON.stringify(e)});\n if (!el) return;\n // Walk the element's own prototype chain to find the value setter.\n // Using window.HTMLInputElement.prototype would resolve to the\n // inspector realm's prototype, causing a cross-realm TypeError.\n var p = Object.getPrototypeOf(el);\n while (p && !Object.getOwnPropertyDescriptor(p, 'value')) {\n p = Object.getPrototypeOf(p);\n }\n var desc = p ? Object.getOwnPropertyDescriptor(p, 'value') : null;\n if (desc && desc.set) {\n desc.set.call(el, ${JSON.stringify(t)});\n } else {\n el.value = ${JSON.stringify(t)};\n }\n el.dispatchEvent(new Event('input', { bubbles: true }));\n el.dispatchEvent(new Event('change', { bubbles: true }));\n })()\n `)}async scroll(e,t){const r={up:`window.scrollBy(0, -${t})`,down:`window.scrollBy(0, ${t})`,left:`window.scrollBy(-${t}, 0)`,right:`window.scrollBy(${t}, 0)`};await this.evaluate(r[e])}async longPress(e,t){const r=await this.getElementCenter(e);if(!r)throw new Error(`Element not found: ${e}`);const o=t??500;await this.evaluate(`\n (async function(x, y, duration) {\n var el = document.elementFromPoint(x, y);\n if (!el) return;\n var touch = document.createTouch(window, el, 1, x, y, x, y);\n var touchList = document.createTouchList(touch);\n el.dispatchEvent(new TouchEvent('touchstart', { touches: touchList, changedTouches: touchList, bubbles: true }));\n await new Promise(function(r) { setTimeout(r, duration); });\n var emptyList = document.createTouchList();\n el.dispatchEvent(new TouchEvent('touchend', { touches: emptyList, changedTouches: touchList, bubbles: true }));\n })(${r.x}, ${r.y}, ${o})\n `)}async swipe(e,t){const r=await this.getViewportSize(),o=r.width/2,n=r.height/2,i=.4*r.height,s=t??10,a={up:{sx:o,sy:n+i/2,ex:o,ey:n-i/2},down:{sx:o,sy:n-i/2,ex:o,ey:n+i/2},left:{sx:o+i/2,sy:n,ex:o-i/2,ey:n},right:{sx:o-i/2,sy:n,ex:o+i/2,ey:n}},{sx:c,sy:l,ex:u,ey:d}=a[e];await this.evaluate(`\n (async function(sx, sy, ex, ey, steps) {\n var el = document.elementFromPoint(sx, sy);\n if (!el) return;\n var makeTouch = function(x, y) { return document.createTouch(window, el, 1, x, y, x, y); };\n var startTouch = makeTouch(sx, sy);\n var startList = document.createTouchList(startTouch);\n el.dispatchEvent(new TouchEvent('touchstart', { touches: startList, changedTouches: startList, bubbles: true }));\n for (var i = 1; i <= steps; i++) {\n var x = sx + (ex - sx) * (i / steps);\n var y = sy + (ey - sy) * (i / steps);\n var moveTouch = makeTouch(x, y);\n var moveList = document.createTouchList(moveTouch);\n el.dispatchEvent(new TouchEvent('touchmove', { touches: moveList, changedTouches: moveList, bubbles: true }));\n await new Promise(function(r) { setTimeout(r, 16); });\n }\n var endTouch = makeTouch(ex, ey);\n var endList = document.createTouchList(endTouch);\n var emptyList = document.createTouchList();\n el.dispatchEvent(new TouchEvent('touchend', { touches: emptyList, changedTouches: endList, bubbles: true }));\n })(${c}, ${l}, ${u}, ${d}, ${s})\n `)}async press(e){const t={Enter:{key:"Enter",code:"Enter",keyCode:13},Tab:{key:"Tab",code:"Tab",keyCode:9},Escape:{key:"Escape",code:"Escape",keyCode:27},Backspace:{key:"Backspace",code:"Backspace",keyCode:8},ArrowUp:{key:"ArrowUp",code:"ArrowUp",keyCode:38},ArrowDown:{key:"ArrowDown",code:"ArrowDown",keyCode:40},ArrowLeft:{key:"ArrowLeft",code:"ArrowLeft",keyCode:37},ArrowRight:{key:"ArrowRight",code:"ArrowRight",keyCode:39},Space:{key:" ",code:"Space",keyCode:32}}[e]??{key:e,code:"Key"+e.toUpperCase(),keyCode:e.charCodeAt(0)},r=JSON.stringify(t.key),o=JSON.stringify(t.code);await this.evaluate(`\n (function() {\n var el = document.activeElement || document.body;\n el.dispatchEvent(new KeyboardEvent('keydown', { key: ${r}, code: ${o}, keyCode: ${t.keyCode}, bubbles: true }));\n el.dispatchEvent(new KeyboardEvent('keypress', { key: ${r}, code: ${o}, keyCode: ${t.keyCode}, bubbles: true }));\n el.dispatchEvent(new KeyboardEvent('keyup', { key: ${r}, code: ${o}, keyCode: ${t.keyCode}, bubbles: true }));\n })()\n `)}async dismissKeyboard(){await this.evaluate("document.activeElement && document.activeElement.blur()")}async selectOption(e,t){await this.evaluate(`\n (function() {\n var el = document.querySelector(${JSON.stringify(e)});\n if (!el || el.tagName !== 'SELECT') return;\n // Walk the element's own prototype chain to find the value setter,\n // avoiding cross-realm TypeError with window.HTMLSelectElement.prototype.\n var p = Object.getPrototypeOf(el);\n while (p && !Object.getOwnPropertyDescriptor(p, 'value')) {\n p = Object.getPrototypeOf(p);\n }\n var desc = p ? Object.getOwnPropertyDescriptor(p, 'value') : null;\n if (desc && desc.set) {\n desc.set.call(el, ${JSON.stringify(t)});\n } else {\n el.value = ${JSON.stringify(t)};\n }\n el.dispatchEvent(new Event('input', { bubbles: true }));\n el.dispatchEvent(new Event('change', { bubbles: true }));\n })()\n `)}async querySelector(e){return this.evaluate(`\n (function() {\n var el = document.querySelector(${JSON.stringify(e)});\n if (!el) return null;\n var rect = el.getBoundingClientRect();\n var style = window.getComputedStyle(el);\n return {\n selector: ${JSON.stringify(e)},\n tag: el.tagName.toLowerCase(),\n text: (el.textContent || '').trim().substring(0, 200),\n attributes: Object.fromEntries(Array.from(el.attributes).map(function(a) { return [a.name, a.value]; })),\n boundingBox: rect.width > 0 && rect.height > 0\n ? { x: rect.x, y: rect.y, width: rect.width, height: rect.height }\n : null,\n computedStyles: {\n display: style.display,\n visibility: style.visibility,\n opacity: style.opacity,\n fontSize: style.fontSize,\n color: style.color,\n backgroundColor: style.backgroundColor,\n position: style.position,\n zIndex: style.zIndex,\n overflow: style.overflow\n },\n isVisible: rect.width > 0 && rect.height > 0\n && style.display !== 'none'\n && style.visibility !== 'hidden'\n && parseFloat(style.opacity) > 0\n };\n })()\n `)}async querySelectorAll(e){return this.evaluate(`\n (function() {\n var elements = document.querySelectorAll(${JSON.stringify(e)});\n return Array.from(elements).slice(0, 100).map(function(el) {\n var rect = el.getBoundingClientRect();\n var style = window.getComputedStyle(el);\n return {\n selector: ${JSON.stringify(e)},\n tag: el.tagName.toLowerCase(),\n text: (el.textContent || '').trim().substring(0, 200),\n attributes: Object.fromEntries(Array.from(el.attributes).map(function(a) { return [a.name, a.value]; })),\n boundingBox: rect.width > 0 && rect.height > 0\n ? { x: rect.x, y: rect.y, width: rect.width, height: rect.height }\n : null,\n computedStyles: {\n display: style.display, visibility: style.visibility, opacity: style.opacity,\n fontSize: style.fontSize, position: style.position\n },\n isVisible: rect.width > 0 && rect.height > 0 && style.display !== 'none' && style.visibility !== 'hidden' && parseFloat(style.opacity) > 0\n };\n });\n })()\n `)}async inspect(e){return this.evaluate(`\n (function() {\n var el = document.querySelector(${JSON.stringify(e)});\n if (!el) return null;\n var rect = el.getBoundingClientRect();\n var style = window.getComputedStyle(el);\n return {\n tag: el.tagName.toLowerCase(),\n id: el.id,\n className: el.className,\n text: (el.textContent || '').trim().substring(0, 500),\n innerHTML: el.innerHTML.substring(0, 1000),\n boundingBox: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },\n styles: {\n display: style.display, position: style.position,\n width: style.width, height: style.height,\n margin: style.margin, padding: style.padding,\n fontSize: style.fontSize, fontWeight: style.fontWeight,\n color: style.color, backgroundColor: style.backgroundColor,\n border: style.border, borderRadius: style.borderRadius,\n overflow: style.overflow, zIndex: style.zIndex,\n opacity: style.opacity, visibility: style.visibility\n },\n accessibility: {\n role: el.getAttribute('role'),\n ariaLabel: el.getAttribute('aria-label'),\n ariaHidden: el.getAttribute('aria-hidden'),\n tabIndex: el.tabIndex\n },\n childCount: el.children.length,\n children: Array.from(el.children).slice(0, 10).map(function(c) {\n return { tag: c.tagName.toLowerCase(), text: (c.textContent || '').trim().substring(0, 50) };\n })\n };\n })()\n `)}async waitFor(e,t){const r=t?.timeout??1e4,o=Date.now();for(;Date.now()-o<r;){const r=await this.querySelector(e);if(r&&(!t?.visible||r.isVisible))return;await new Promise(e=>setTimeout(e,200))}throw new G(`waitFor("${e}") timed out after ${r}ms`)}onConsole(e){this.enableDomain("Console").then(()=>{this.on("Console.messageAdded",t=>{e({type:t.message?.level??t.message?.type??"log",text:t.message?.text??""})})})}onPageLoad(e){this.enableDomain("Page").then(()=>{this.on("Page.loadEventFired",e)})}onRequest(e){this.enableDomain("Network").then(()=>{this.on("Network.requestWillBeSent",t=>{e({url:t.request?.url??"",method:t.request?.method??"GET"})})})}onResponse(e){this.enableDomain("Network").then(()=>{this.on("Network.responseReceived",t=>{e({url:t.response?.url??"",status:t.response?.status??0})})})}async getElementCenter(e){return this.evaluate(`\n (function() {\n const el = document.querySelector(${JSON.stringify(e)});\n if (!el) return null;\n const rect = el.getBoundingClientRect();\n return { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 };\n })()\n `)}async getViewportSize(){return this.evaluate("({width: window.innerWidth, height: window.innerHeight})")}async connectToTarget(e){this.activeTargetId=null,this.targetReady=new Promise(e=>{this.targetReadyResolve=e}),await new Promise((t,r)=>{const o=this.options.connectTimeout??1e4,n=setTimeout(()=>{r(new V(`Connection timeout after ${o}ms`))},o),i=new(H())(e);i.on("open",()=>{clearTimeout(n),this.ws=i,this.connected=!0,this.startHeartbeat(),t()}),i.on("message",e=>{this.handleMessage(e.toString())}),i.on("close",()=>{this.connected&&!this.reconnecting&&(this.connected=!1,this.handleDisconnect())}),i.on("error",e=>{clearTimeout(n),this.connected||r(new V(`WebSocket error: ${e.message}`))})});const t=this.options.connectTimeout??1e4;let r;const o=new Promise((e,o)=>{r=setTimeout(()=>o(new V("No page target discovered — is Safari open in the simulator?")),t)});try{await Promise.race([this.targetReady,o])}finally{clearTimeout(r)}}clearPendingRequests(){for(const[,e]of this.pendingRequests)clearTimeout(e.timer),e.reject(new V("Connection closed"));this.pendingRequests.clear();for(const[,e]of this.innerPendingRequests)clearTimeout(e.timer),e.reject(new V("Connection closed"));this.innerPendingRequests.clear()}httpGet(e){return new Promise((t,r)=>{C().get(e,e=>{let r="";e.on("data",e=>r+=e),e.on("end",()=>t(r))}).on("error",r)})}}class V extends Error{constructor(e){super(e),this.name="ConnectionError"}}class G extends Error{constructor(e){super(e),this.name="TimeoutError"}}class X extends Error{code;constructor(e,t){super(e),this.code=t,this.name="ProtocolError"}}class Q extends Error{constructor(e){super(e),this.name="EvaluationError"}}class Y{authDir;constructor(e){this.authDir=e??i.join(s.homedir(),".opensafari","auth")}async save(e,t,r){const o=r??await t.getCookies(),n=await t.evaluate("\n (function() {\n var data = {};\n for (var i = 0; i < window.localStorage.length; i++) {\n var key = window.localStorage.key(i);\n if (key) data[key] = window.localStorage.getItem(key) || '';\n }\n return data;\n })()\n "),s=await t.evaluate("\n (function() {\n var data = {};\n for (var i = 0; i < window.sessionStorage.length; i++) {\n var key = window.sessionStorage.key(i);\n if (key) data[key] = window.sessionStorage.getItem(key) || '';\n }\n return data;\n })()\n "),a=await t.evaluate("window.location.href"),c={site:e,savedAt:(new Date).toISOString(),currentUrl:a,cookies:o,localStorage:n??{},sessionStorage:s??{}};await v.mkdir(this.authDir,{recursive:!0});const l=i.join(this.authDir,this.sanitizeSite(e)+".json");return await v.writeFile(l,JSON.stringify(c,null,2)),l}async restore(e,t){const r=i.join(this.authDir,this.sanitizeSite(e)+".json"),o=JSON.parse(await v.readFile(r,"utf-8"));await t.navigate({url:"https://"+e,waitUntil:"domcontentloaded"}),await t.setCookies(o.cookies),o.localStorage&&Object.keys(o.localStorage).length>0&&await t.evaluate(`\n (function(data) {\n Object.entries(data).forEach(function(entry) {\n window.localStorage.setItem(entry[0], entry[1]);\n });\n })(${JSON.stringify(o.localStorage)})\n `),await t.navigate({url:o.currentUrl??"https://"+e,waitUntil:"load"})}async list(){try{const e=await v.readdir(this.authDir),t=[];for(const r of e)if(r.endsWith(".json"))try{const e=JSON.parse(await v.readFile(i.join(this.authDir,r),"utf-8"));t.push({site:e.site,savedAt:e.savedAt,cookieCount:e.cookies.length})}catch{}return t}catch{return[]}}async delete(e){const t=i.join(this.authDir,this.sanitizeSite(e)+".json");await v.unlink(t)}async checkExpiry(e){const t=i.join(this.authDir,this.sanitizeSite(e)+".json"),r=JSON.parse(await v.readFile(t,"utf-8")),o=Date.now()/1e3,n=r.cookies.filter(e=>e.expires>0&&e.expires<o),s=r.cookies.filter(e=>e.expires>0&&e.expires>=o&&e.expires-o<300);return{totalCookies:r.cookies.length,expiredCount:n.length,expiringCount:s.length,earliestExpiry:r.cookies.filter(e=>e.expires>0).reduce((e,t)=>Math.min(e,t.expires),1/0),isExpired:n.length>0,isExpiring:s.length>0}}async loadProfile(e){const t=i.join(this.authDir,this.sanitizeSite(e)+".json");return JSON.parse(await v.readFile(t,"utf-8"))}sanitizeSite(e){return e.replace(/[^a-zA-Z0-9.-]/g,"_")}}const Z=(0,S.promisify)(k.execFile),ee="/tmp/opensafari-managed-devices.json";function te(e){try{const t=oe();t[String(process.pid)]={udids:e,startedAt:(new Date).toISOString()},ne(t),console.error(`[DeviceRegistry] Registered ${e.length} device(s) for PID ${process.pid}`)}catch(e){console.error(`[DeviceRegistry] Failed to register devices: ${e}`)}}function re(){try{const e=oe();delete e[String(process.pid)],ne(e),console.error(`[DeviceRegistry] Unregistered PID ${process.pid}`)}catch(e){console.error(`[DeviceRegistry] Failed to unregister devices: ${e}`)}}function oe(){try{const e=n.readFileSync(ee,"utf-8");return JSON.parse(e)}catch{return{}}}function ne(e){const t=i.dirname(ee);n.existsSync(t)||n.mkdirSync(t,{recursive:!0}),n.writeFileSync(ee,JSON.stringify(e,null,2),"utf-8")}function ie(e){try{return process.kill(e,0),!0}catch(e){return!(!e||"object"!=typeof e||!("code"in e)||"EPERM"!==e.code)}}async function se(e){return e?async function(e){let t=0;try{const{stdout:r}=await Z("xcrun",["simctl","list","devices","booted","--json"]),o=JSON.parse(r),n=function(){const e=oe(),t=new Set,r=[];for(const o of Object.keys(e))if(ie(Number(o)))for(const r of e[o].udids)t.add(r);else r.push(o);if(r.length>0){for(const t of r)delete e[t];ne(e),console.error(`[DeviceRegistry] Pruned ${r.length} stale PID(s)`)}return t}(),i=new Set([...e,...n]);for(const e of Object.values(o.devices))for(const r of e)if("Booted"===r.state&&!i.has(r.udid))try{await Z("xcrun",["simctl","shutdown",r.udid]),t++,console.error(`[ZombieCleanup] Shut down orphaned simulator: ${r.name} (${r.udid})`)}catch{}}catch{}return t}(e):async function(){try{const{stdout:e}=await Z("pgrep",["-f","CoreSimulator"]),t=e.trim().split("\n").filter(Boolean);return t.length>0&&console.error(`[ZombieCleanup] Found ${t.length} CoreSimulator processes`),t.length}catch{return 0}}()}let ae=null,ce=null;const le=(0,S.promisify)(k.execFile);let ue=!1;class de extends J.EventEmitter{pool=new Map;manager;maxSimulators;concurrencyLimit;webkitBasePort;devicePorts=new Map;nextPort;idleCheckInterval=null;resourceCheckInterval=null;idleTimeout;memoryWarnMB;memoryKillMB;constructor(e){super(),this.manager=new P,this.maxSimulators=e?.max??5,this.concurrencyLimit=e?.concurrency??3,this.webkitBasePort=e?.webkitBasePort??9322,this.nextPort=this.webkitBasePort,this.idleTimeout=3e5,this.memoryWarnMB=400,this.memoryKillMB=600,ue||(ue=!0,process.on("exit",()=>{re()}))}async checkResources(e){const t=2048*e,r=Math.floor(s.freemem()/1024/1024);if(r<t)throw new pe(`Need ~${t}MB free RAM for ${e} simulators, but only ${r}MB available. Reduce device count or close other apps.`)}async bootAll(e){if(e.length>this.maxSimulators)throw new Error(`Cannot boot ${e.length} simulators (max: ${this.maxSimulators})`);await this.checkResources(e.length);const t=[],r=[];for(let t=0;t<e.length;t+=this.concurrencyLimit)r.push(e.slice(t,t+this.concurrencyLimit));for(const e of r){const r=await Promise.all(e.map(async e=>{const t=await this.manager.boot(e);await this.manager.openUrl(t.udid,"https://example.com");const r=this.getPortForDevice(t.udid),o=new K({host:"localhost",port:r});try{await o.connect()}catch{console.error(`[SimulatorPool] WebKit connection failed for ${e} on port ${r} — proxy may not be running`)}const n=$[e],i={device:{...t,viewport:{width:n?.w??390,height:n?.h??844}},client:o,preset:e,bootedAt:Date.now(),lastActivity:Date.now()};return this.pool.set(t.udid,i),i}));t.push(...r)}return te(t.map(e=>e.device.udid)),t}getAll(){return Array.from(this.pool.values())}get(e){return this.pool.get(e)??null}getByPreset(e){return this.getAll().find(t=>t.preset===e)??null}markActivity(e){const t=this.pool.get(e);t&&(t.lastActivity=Date.now())}getManager(){return this.manager}async shutdownAll(){this.stopIdleMonitor(),this.stopResourceMonitor(),await Promise.allSettled(this.getAll().map(async e=>{try{await e.client.disconnect()}catch{}try{await this.manager.shutdown(e.device.udid)}catch{}this.pool.delete(e.device.udid)})),this.devicePorts.clear(),this.nextPort=this.webkitBasePort,re()}async shutdownOne(e){const t=this.pool.get(e);if(!t)return;try{await t.client.disconnect()}catch{}try{await this.manager.shutdown(e)}catch{}this.pool.delete(e),this.devicePorts.delete(e);const r=Array.from(this.pool.keys());r.length>0?te(r):re()}startIdleMonitor(){this.idleCheckInterval||(this.idleCheckInterval=setInterval(()=>{const e=Date.now();for(const[t,r]of this.pool)e-r.lastActivity>this.idleTimeout&&(console.error(`[SimulatorPool] Auto-shutting down idle device: ${r.preset}`),this.shutdownOne(t).catch(()=>{}),this.emit("simulator:shutdown",{deviceId:t,preset:r.preset,reason:"idle"}))},6e4))}stopIdleMonitor(){this.idleCheckInterval&&(clearInterval(this.idleCheckInterval),this.idleCheckInterval=null)}startResourceMonitor(){this.resourceCheckInterval||(this.resourceCheckInterval=setInterval(async()=>{for(const[e,t]of this.pool)try{const r=await this.getSimulatorMemory(e);r>this.memoryKillMB?this.emit("simulator:memory-critical",{deviceId:e,preset:t.preset,memMB:r,threshold:this.memoryKillMB}):r>this.memoryWarnMB&&this.emit("simulator:memory-warning",{deviceId:e,preset:t.preset,memMB:r,threshold:this.memoryWarnMB})}catch{}},3e4))}stopResourceMonitor(){this.resourceCheckInterval&&(clearInterval(this.resourceCheckInterval),this.resourceCheckInterval=null)}async injectAuth(e){const t=new Y,r=await t.loadProfile(e);for(const e of this.getAll())if(e.client.isConnected())try{await e.client.setCookies(r.cookies),r.localStorage&&Object.keys(r.localStorage).length>0&&await e.client.evaluate(`\n (function(data) {\n Object.entries(data).forEach(function(e) { localStorage.setItem(e[0], e[1]); });\n })(${JSON.stringify(r.localStorage)})\n `)}catch(t){console.error(`[SimulatorPool] Auth injection failed for ${e.preset}: ${t}`)}}get size(){return this.pool.size}getPortForDevice(e){return this.devicePorts.has(e)||this.devicePorts.set(e,this.nextPort++),this.devicePorts.get(e)}async getSimulatorMemory(e){try{const{stdout:t}=await le("pgrep",["-f",e]),r=t.trim().split("\n").filter(Boolean);let o=0;for(const e of r)try{const{stdout:t}=await le("ps",["-o","rss=","-p",e]);o+=parseInt(t.trim(),10)||0}catch{}return Math.floor(o/1024)}catch{return 0}}}class pe extends Error{constructor(e){super(e),this.name="InsufficientResourcesError"}}const he=(0,S.promisify)(k.execFile);class me{options;process=null;_port;_deviceListPort;_running=!1;_reusing=!1;constructor(e={}){this.options=e;const t=process.env.OPENSAFARI_PROXY_PORT?parseInt(process.env.OPENSAFARI_PROXY_PORT,10):void 0,r=void 0===t||Number.isNaN(t)?void 0:t,o=process.env.OPENSAFARI_PROXY_DEVICE_LIST_PORT?parseInt(process.env.OPENSAFARI_PROXY_DEVICE_LIST_PORT,10):void 0,n=void 0===o||Number.isNaN(o)?void 0:o;this._port=e.port??r??9322,this._deviceListPort=e.deviceListPort??n??this._port-1}async findSocketPath(){return M()}async start(){if(this._running)return;if(await this.isPortInUse(this._deviceListPort)){if(await this.isProxyHealthy())return console.error(`[WebInspectorProxy] Reusing existing proxy on port ${this._deviceListPort}`),this._running=!0,this._reusing=!0,this.registerRefSync(),void await this.waitForForwarding();throw new Error(`Port ${this._deviceListPort} already in use by a non-proxy process. Use a different port (e.g. OPENSAFARI_PROXY_PORT=${this._port+100}) or stop the existing process.`)}if(await this.isPortInUse(this._port))throw new Error(`Port ${this._port} already in use. Use a different port (e.g. OPENSAFARI_PROXY_PORT=${this._port+100}) or stop the existing process.`);try{await he("which",["ios_webkit_debug_proxy"])}catch{throw new Error("ios_webkit_debug_proxy not found. Install: brew install ios-webkit-debug-proxy")}const e=await this.findSocketPath();if(!e)throw new Error("Web Inspector socket not found. Is a simulator booted?");const t=["-s",`unix:${e}`,"-c",`null:${this._deviceListPort},:${this._port}-${this._port+100}`,"-F",...this.options.extraArgs??[]];this.process=(0,k.spawn)("ios_webkit_debug_proxy",t,{stdio:["ignore","pipe","pipe"],detached:!1}),this._running=!0,this._reusing=!1,this.registerRefSync(),this.process.stderr?.on("data",e=>{console.error(`[WebInspectorProxy] ${e.toString().trim()}`)}),this.process.on("error",e=>{console.error(`[WebInspectorProxy] process error: ${e.message}`),this._running=!1,this.process=null}),this.process.on("exit",e=>{console.error(`[WebInspectorProxy] exited with code ${e}`),this._running=!1,this.process=null}),await this.waitForReady(),await this.waitForForwarding()}async stop(){const e=this.unregisterRefSync();if(this._reusing)return this._running=!1,void(this._reusing=!1);if(!this.process)return void(this._running=!1);if(e>0)return console.error(`[WebInspectorProxy] ${e} other session(s) still using proxy, not killing`),this.process=null,void(this._running=!1);const t=this.process;return new Promise(e=>{const r=setTimeout(()=>{try{t.kill("SIGKILL")}catch{}this.process=null,this._running=!1,e()},3e3);t.once("exit",()=>{clearTimeout(r),this.process=null,this._running=!1,e()}),t.kill("SIGTERM")})}get running(){return this._running}get port(){return this._port}get deviceListPort(){return this._deviceListPort}get pid(){return this.process?.pid??null}get reusing(){return this._reusing}getRefFilePath(){return`/tmp/opensafari-proxy-${this._deviceListPort}.refs`}registerRefSync(){const e=this.getRefFilePath();let t=[];try{t=(0,n.readFileSync)(e,"utf-8").trim().split("\n").map(Number).filter(Boolean)}catch{}t=t.filter(e=>{try{return process.kill(e,0),!0}catch{return!1}}),t.includes(process.pid)||t.push(process.pid),(0,n.writeFileSync)(e,t.join("\n")+"\n")}unregisterRefSync(){const e=this.getRefFilePath();let t=[];try{t=(0,n.readFileSync)(e,"utf-8").trim().split("\n").map(Number).filter(Boolean)}catch{return 0}if(t=t.filter(e=>{if(e===process.pid)return!1;try{return process.kill(e,0),!0}catch{return!1}}),t.length>0)(0,n.writeFileSync)(e,t.join("\n")+"\n");else try{(0,n.unlinkSync)(e)}catch{}return t.length}async waitForReady(e=5e3){const t=Date.now();for(;Date.now()-t<e;){try{if((await this.httpGet(`http://localhost:${this._deviceListPort}`)).includes("iOS Devices"))return}catch{}await new Promise(e=>setTimeout(e,500))}throw new Error(`WebInspectorProxy did not become ready within ${e}ms`)}async waitForForwarding(e=15e3){const t=Date.now();for(;Date.now()-t<e;){try{if((await this.httpGet(`http://localhost:${this._port}/json`)).startsWith("["))return}catch{}await new Promise(e=>setTimeout(e,1e3))}console.error("[WebInspectorProxy] Forwarding port not ready within timeout — Safari may not be open")}isPortInUse(e){return new Promise(t=>{const r=R.connect({port:e,host:"127.0.0.1"});r.setTimeout(2e3),r.once("connect",()=>{r.destroy(),t(!0)}),r.once("timeout",()=>{r.destroy(),t(!1)}),r.once("error",()=>{r.destroy(),t(!1)})})}async isProxyHealthy(){try{return(await this.httpGet(`http://localhost:${this._deviceListPort}`)).includes("iOS Devices")}catch{return!1}}httpGet(e){return new Promise((t,r)=>{const o=_.get(e,{timeout:3e3},e=>{let r="";e.on("data",e=>{r+=e}),e.on("end",()=>t(r))});o.on("error",r),o.on("timeout",()=>{o.destroy(),r(new Error("HTTP request timed out"))})})}}let we=null;function ye(){return we||(we=new me),we}process.on("exit",()=>{if(we){const e=we.unregisterRefSync();if(!we.reusing&&we.running&&0===e)try{we.process?.kill("SIGTERM")}catch{}}});let fe=null,ge=null,ve=null,be=null;function ke(e){be=e}function Se(e){return{content:[{type:"text",text:`Error: ${e}`}],isError:!0}}function xe(e){return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}let Ee=null;function Te(e){g(e),function(e){e.registerTool({name:"screenshot",description:"Take a screenshot of the current Safari page",inputSchema:{type:"object",properties:{format:{type:"string",enum:["png"],description:"Image format"},fullPage:{type:"boolean",description:"Capture full page"}},required:[]}},async(e,t)=>{const r=m();if(!r)return{content:[{type:"text",text:"Error: Safari not connected"}],isError:!0};let o;try{o=await r.screenshot({format:t.format,fullPage:t.fullPage})}catch{const e=new P,t=await e.listBooted();if(0===t.length)return{content:[{type:"text",text:"Error: No booted simulator for screenshot fallback"}],isError:!0};o=await e.screenshot(t[0].udid)}return{content:[{type:"image",data:o.toString("base64"),mimeType:"image/png"}]}})}(e),function(e){e.registerTool({name:"javascript",description:"Execute JavaScript in the current Safari page",inputSchema:{type:"object",properties:{expression:{type:"string",description:"JavaScript expression to evaluate"}},required:["expression"]}},async(e,t)=>{const r=m();if(!r)return{content:[{type:"text",text:"Error: Safari not connected"}],isError:!0};const o=await r.evaluate(t.expression);return{content:[{type:"text",text:JSON.stringify(o)}]}})}(e),function(e){e.registerTool({name:"read_page",description:"Read the current page content from Safari",inputSchema:{type:"object",properties:{},required:[]}},async(e,t)=>{const r=m();if(!r)return{content:[{type:"text",text:"Error: Safari not connected"}],isError:!0};const o=await r.readPage();return{content:[{type:"text",text:JSON.stringify(o)}]}})}(e),function(e){e.registerTool({name:"click",description:"Click on an element or coordinates in Safari",inputSchema:{type:"object",properties:{selector:{type:"string",description:"CSS selector of element to click"},x:{type:"number",description:"X coordinate to click"},y:{type:"number",description:"Y coordinate to click"}},required:[]}},async(e,t)=>{const r=m();if(!r)return{content:[{type:"text",text:"Error: Safari not connected"}],isError:!0};const o=t.selector?t.selector:{x:t.x,y:t.y};return await r.click(o),{content:[{type:"text",text:"clicked"}]}})}(e),function(e){e.registerTool({name:"type",description:"Type text into an element in Safari",inputSchema:{type:"object",properties:{selector:{type:"string",description:"CSS selector of element to type into"},text:{type:"string",description:"Text to type"},delay:{type:"number",description:"Delay between keystrokes in ms"}},required:["selector","text"]}},async(e,t)=>{const r=m();return r?(await r.type(t.selector,t.text,void 0!==t.delay?{delay:t.delay}:void 0),{content:[{type:"text",text:"typed"}]}):{content:[{type:"text",text:"Error: Safari not connected"}],isError:!0}})}(e),function(e){e.registerTool({name:"scroll",description:"Scroll the page in Safari",inputSchema:{type:"object",properties:{direction:{type:"string",enum:["up","down","left","right"],description:"Scroll direction"},amount:{type:"number",description:"Amount to scroll in pixels"}},required:["direction","amount"]}},async(e,t)=>{const r=m();return r?(await r.scroll(t.direction,t.amount),{content:[{type:"text",text:"scrolled"}]}):{content:[{type:"text",text:"Error: Safari not connected"}],isError:!0}})}(e),function(e){e.registerTool({name:"query_dom",description:"Query DOM elements in Safari",inputSchema:{type:"object",properties:{selector:{type:"string",description:"CSS selector to query"}},required:["selector"]}},async(e,t)=>{const r=m();if(!r)return{content:[{type:"text",text:"Error: Safari not connected"}],isError:!0};const o=await r.querySelectorAll(t.selector);return{content:[{type:"text",text:JSON.stringify(o)}]}})}(e),function(e){e.registerTool({name:"cookies",description:"Get, set, or clear cookies in Safari",inputSchema:{type:"object",properties:{action:{type:"string",enum:["get","set","clear"],description:"Cookie action"},cookies:{type:"array",description:"Cookies to set (for action=set)"},domain:{type:"string",description:"Domain to filter cookies (for action=get)"}},required:["action"]}},async(e,t)=>{const r=m();if(!r)return{content:[{type:"text",text:"Error: Safari not connected"}],isError:!0};const o=t.action;if("get"===o){const e=await r.getCookies(t.domain);return{content:[{type:"text",text:JSON.stringify(e)}]}}return"set"===o?(await r.setCookies(t.cookies??[]),{content:[{type:"text",text:"cookies set"}]}):(await r.clearCookies(),{content:[{type:"text",text:"cookies cleared"}]})})}(e),function(e){e.registerTool({name:"device_boot",description:"Boot an iOS Simulator device",inputSchema:{type:"object",properties:{device:{type:"string",description:"Device name, preset key, or UDID to boot"}},required:["device"]}},async(e,t)=>{const r=new P,o=await r.boot(t.device);!function(e){try{const t=oe(),r=t[String(process.pid)];r?r.udids.includes(e)||r.udids.push(e):t[String(process.pid)]={udids:[e],startedAt:(new Date).toISOString()},ne(t),console.error(`[DeviceRegistry] Added device ${e} for PID ${process.pid}`)}catch(e){console.error(`[DeviceRegistry] Failed to add device: ${e}`)}}(o.udid);let n={running:!1,pid:null};try{const e=ye();await e.start(),n={running:e.running,pid:e.pid};try{const t=m();if(t)try{await t.disconnect()}catch{}let n=5;for(;n>0;)try{await r.openUrl(o.udid,"https://example.com");break}catch(e){if(n--,0===n)throw e;await new Promise(e=>setTimeout(e,2e3))}const i=new K({host:"localhost",port:e.port});await i.connect({retries:5,retryDelay:2e3}),w(i)}catch(e){console.error(`[device_boot] WebKit connection failed (proxy running, tools may not work): ${e}`)}}catch(e){console.error(`[device_boot] Failed to start WebInspector proxy: ${e}`)}return{content:[{type:"text",text:JSON.stringify({...o,proxy:n})}]}})}(e),function(e){e.registerTool({name:"device_shutdown",description:"Shutdown an iOS Simulator device",inputSchema:{type:"object",properties:{deviceId:{type:"string",description:"Device UDID to shutdown"}},required:[]}},async(e,t)=>{const r=new P,o=await r.listBooted(),n=t.deviceId??o[0]?.udid;if(!n)return{content:[{type:"text",text:"Error: no booted device found"}],isError:!0};const i=m();if(i)try{await i.disconnect()}catch(e){console.error(`[device_shutdown] WebKit disconnect failed: ${e}`)}w(null);const s=ye();let a=!1;if(s.running)try{await s.stop(),a=!0}catch(e){console.error(`[device_shutdown] Failed to stop proxy: ${e}`)}return await r.shutdown(n),{content:[{type:"text",text:JSON.stringify({shutdown:!0,deviceId:n,proxyStopped:a})}]}})}(e),function(e){e.registerTool({name:"inspect",description:"Inspect a DOM element in Safari",inputSchema:{type:"object",properties:{selector:{type:"string",description:"CSS selector of element to inspect"}},required:["selector"]}},async(e,t)=>{const r=m();if(!r)return{content:[{type:"text",text:"Error: Safari not connected"}],isError:!0};const o=await r.inspect(t.selector);return{content:[{type:"text",text:JSON.stringify(o)}]}})}(e),function(e){e.registerTool({name:"wait_for",description:"Wait for an element to appear in Safari",inputSchema:{type:"object",properties:{selector:{type:"string",description:"CSS selector to wait for"},visible:{type:"boolean",description:"Wait until element is visible"},timeout:{type:"number",description:"Timeout in milliseconds"}},required:["selector"]}},async(e,t)=>{const r=m();return r?(await r.waitFor(t.selector,{visible:t.visible,timeout:t.timeout}),{content:[{type:"text",text:"element found"}]}):{content:[{type:"text",text:"Error: Safari not connected"}],isError:!0}})}(e),function(e){e.registerTool({name:"long_press",description:"Long press on an element in Safari",inputSchema:{type:"object",properties:{selector:{type:"string",description:"CSS selector of element to long press"},duration:{type:"number",description:"Press duration in milliseconds"}},required:["selector"]}},async(e,t)=>{const r=m();return r?(await r.longPress(t.selector,t.duration),{content:[{type:"text",text:"long pressed"}]}):{content:[{type:"text",text:"Error: Safari not connected"}],isError:!0}})}(e),function(e){e.registerTool({name:"swipe",description:"Swipe gesture in Safari",inputSchema:{type:"object",properties:{direction:{type:"string",enum:["up","down","left","right"],description:"Swipe direction"},speed:{type:"number",description:"Swipe speed"}},required:["direction"]}},async(e,t)=>{const r=m();return r?(await r.swipe(t.direction,t.speed),{content:[{type:"text",text:"swiped"}]}):{content:[{type:"text",text:"Error: Safari not connected"}],isError:!0}})}(e),function(e){e.registerTool({name:"press",description:"Press a key in Safari",inputSchema:{type:"object",properties:{key:{type:"string",description:"Key to press (e.g. Enter, Escape, Tab)"}},required:["key"]}},async(e,t)=>{const r=m();return r?(await r.press(t.key),{content:[{type:"text",text:"pressed"}]}):{content:[{type:"text",text:"Error: Safari not connected"}],isError:!0}})}(e),function(e){e.registerTool({name:"dismiss_keyboard",description:"Dismiss the on-screen keyboard in Safari on iOS Simulator",inputSchema:{type:"object",properties:{},required:[]}},async(e,t)=>{const r=m();return r?(await r.dismissKeyboard(),{content:[{type:"text",text:"keyboard dismissed"}]}):{content:[{type:"text",text:"Error: Safari not connected"}],isError:!0}})}(e),function(e){e.registerTool({name:"select_option",description:"Select an option from a dropdown in Safari",inputSchema:{type:"object",properties:{selector:{type:"string",description:"CSS selector of the select element"},value:{type:"string",description:"Value to select"}},required:["selector","value"]}},async(e,t)=>{const r=m();return r?(await r.selectOption(t.selector,t.value),{content:[{type:"text",text:"option selected"}]}):{content:[{type:"text",text:"Error: Safari not connected"}],isError:!0}})}(e),function(e){e.registerTool({name:"device_list",description:"List available iOS Simulator devices",inputSchema:{type:"object",properties:{},required:[]}},async(e,t)=>{const r=new P,o=await r.listDevices();return{content:[{type:"text",text:JSON.stringify(o)}]}})}(e),function(e){e.registerTool({name:"device_rotate",description:"Rotate an iOS Simulator device",inputSchema:{type:"object",properties:{deviceId:{type:"string",description:"Device UDID to rotate"}},required:[]}},async(e,t)=>{const r=new P,o=await r.listBooted(),n=t.deviceId??o[0]?.udid;return n?(await r.rotate(n),{content:[{type:"text",text:JSON.stringify({rotated:!0,deviceId:n})}]}):{content:[{type:"text",text:"Error: no booted device found"}],isError:!0}})}(e),function(e){e.registerTool({name:"appearance_toggle",description:"Toggle light/dark appearance on an iOS Simulator device",inputSchema:{type:"object",properties:{deviceId:{type:"string",description:"Device UDID"},mode:{type:"string",enum:["light","dark"],description:"Appearance mode to set"}},required:[]}},async(e,t)=>{const r=new P,o=await r.listBooted(),n=t.deviceId??o[0]?.udid;if(!n)return{content:[{type:"text",text:"Error: no booted device found"}],isError:!0};let i;return t.mode?(await r.setAppearance(n,t.mode),i=t.mode):i=await r.toggleAppearance(n),{content:[{type:"text",text:JSON.stringify({appearance:i,deviceId:n})}]}})}(e),function(e){e.registerTool({name:"batch_navigate",description:"Navigate all active simulators to the same URL simultaneously",inputSchema:{type:"object",properties:{url:{type:"string",description:"URL to navigate to"},waitUntil:{type:"string",enum:["load","domcontentloaded","networkidle"]}},required:["url"]}},async(e,t)=>{if(!fe)return{content:[{type:"text",text:"Error: No simulator pool active"}],isError:!0};const r=await fe.batchNavigate(t.url,t.waitUntil);return{content:[{type:"text",text:JSON.stringify(r,null,2)}]}})}(e),function(e){e.registerTool({name:"batch_screenshot",description:"Take a screenshot on all active simulators simultaneously",inputSchema:{type:"object",properties:{format:{type:"string",enum:["png"],description:"Image format"},fullPage:{type:"boolean",description:"Capture full page"}},required:[]}},async(e,t)=>{if(!ge)return{content:[{type:"text",text:"Error: No simulator pool active"}],isError:!0};const r=await ge.batchScreenshot({format:t.format,fullPage:t.fullPage});return{content:[{type:"text",text:JSON.stringify(r,null,2)}]}})}(e),function(e){e.registerTool({name:"batch_execute",description:"Execute a JavaScript expression on all active simulators simultaneously",inputSchema:{type:"object",properties:{expression:{type:"string",description:"JavaScript expression to evaluate"}},required:["expression"]}},async(e,t)=>{if(!ve)return{content:[{type:"text",text:"Error: No simulator pool active"}],isError:!0};const r=await ve.batchExecute(t.expression);return{content:[{type:"text",text:JSON.stringify(r,null,2)}]}})}(e),function(e){e.registerTool({name:"workflow_init",description:"Initialize a multi-device QA workflow. Boots simulators, injects auth, and returns per-worker prompts.",inputSchema:{type:"object",properties:{devices:{type:"array",items:{type:"string"},description:'Device preset names (e.g. ["iphone-17","ipad-pro"])'},url:{type:"string",description:"URL to navigate all devices to"},authProfile:{type:"string",description:"Auth profile name to inject"},taskDescription:{type:"string",description:"Task description for worker prompts"},workerNames:{type:"array",items:{type:"string"},description:"Custom worker names"}},required:["devices"]}},async(e,t)=>be?xe(await be.initWorkflow(t)):Se("Workflow engine not initialized")),e.registerTool({name:"workflow_status",description:"Get the current status of a running workflow including per-worker progress.",inputSchema:{type:"object",properties:{workflowId:{type:"string",description:"Workflow ID returned by workflow_init"}},required:["workflowId"]}},async(e,t)=>be?xe(be.getStatus(t.workflowId)):Se("Workflow engine not initialized")),e.registerTool({name:"workflow_collect",description:"Collect final results from all workers in a completed workflow.",inputSchema:{type:"object",properties:{workflowId:{type:"string",description:"Workflow ID"}},required:["workflowId"]}},async(e,t)=>be?xe(be.collectResults(t.workflowId)):Se("Workflow engine not initialized")),e.registerTool({name:"workflow_collect_partial",description:"Collect results from completed/failed workers only (workflow may still be running).",inputSchema:{type:"object",properties:{workflowId:{type:"string",description:"Workflow ID"}},required:["workflowId"]}},async(e,t)=>be?xe(be.collectPartialResults(t.workflowId)):Se("Workflow engine not initialized")),e.registerTool({name:"workflow_cleanup",description:"Clean up a workflow: remove state and shut down all simulators.",inputSchema:{type:"object",properties:{workflowId:{type:"string",description:"Workflow ID"}},required:["workflowId"]}},async(e,t)=>be?(await be.cleanupWorkflow(t.workflowId),xe({success:!0,message:"Workflow cleaned up and simulators shut down"})):Se("Workflow engine not initialized")),e.registerTool({name:"worker_update",description:"Report progress from a worker. Updates the worker status to active.",inputSchema:{type:"object",properties:{workflowId:{type:"string",description:"Workflow ID"},workerName:{type:"string",description:"Worker name"},update:{type:"string",description:"Progress update message"}},required:["workflowId","workerName","update"]}},async(e,t)=>be?(await be.updateWorker(t.workflowId,t.workerName,t.update),xe({success:!0})):Se("Workflow engine not initialized")),e.registerTool({name:"worker_complete",description:"Mark a worker as completed with its final results.",inputSchema:{type:"object",properties:{workflowId:{type:"string",description:"Workflow ID"},workerName:{type:"string",description:"Worker name"},results:{type:"object",description:"Final results object from the worker"}},required:["workflowId","workerName","results"]}},async(e,t)=>be?(await be.completeWorker(t.workflowId,t.workerName,t.results),xe({success:!0})):Se("Workflow engine not initialized"))}(e),function(e){e.registerTool({name:"cross_viewport_compare",description:"Capture the same page across all active simulators for visual comparison. Returns screenshots with device/viewport/breakpoint metadata.",inputSchema:{type:"object",properties:{url:{type:"string",description:"URL to capture on all devices"},waitUntil:{type:"string",enum:["load","domcontentloaded","networkidle"]},settleTime:{type:"number",description:"Ms to wait after load for dynamic content"}},required:["url"]}},async(e,t)=>Ee?{content:function(e){const t=[];t.push({type:"text",text:`Cross-viewport comparison of ${e.length} devices. Analyze each screenshot for layout consistency, broken elements, overflow, and responsive issues.\n\n`+e.map(e=>`- ${e.device}: ${e.viewport.w}x${e.viewport.h} (Tailwind: ${e.breakpoint})`).join("\n")});for(const r of e)r.error?t.push({type:"text",text:`[${r.device} failed: ${r.error}]`}):(t.push({type:"text",text:`--- ${r.device} (${r.viewport.w}x${r.viewport.h}, ${r.breakpoint}) ---`}),t.push({type:"image",data:r.screenshot,mimeType:"image/png"}));return t}(await Ee.capture(t.url,{waitUntil:t.waitUntil,settleTime:t.settleTime})).map(e=>({type:e.type??"text",text:e.text,...e.data?{data:e.data,mimeType:e.mimeType}:{}}))}:{content:[{type:"text",text:"Error: Cross-viewport capture not initialized"}],isError:!0})}(e),function(e){const t=[{name:"qa_auto_zoom",desc:"Detect inputs triggering iOS auto-zoom",mod:"auto-zoom",fn:"detectAutoZoom"},{name:"qa_touch_targets",desc:"Find elements below 44x44px",mod:"touch-targets",fn:"detectTouchTargets"},{name:"qa_hover_only",desc:"Find hover-only interactions",mod:"hover-only",fn:"detectHoverOnly"},{name:"qa_input_type",desc:"Validate input type/inputMode",mod:"input-type",fn:"detectInputType"},{name:"qa_safe_area",desc:"Check content behind notch",mod:"safe-area",fn:"detectSafeArea"},{name:"qa_keyboard_overlap",desc:"Detect fixed elements hidden by keyboard",mod:"keyboard-overlap",fn:"detectKeyboardOverlap"},{name:"qa_horizontal_overflow",desc:"Find horizontal scroll causes",mod:"horizontal-overflow",fn:"detectHorizontalOverflow"},{name:"qa_100vh",desc:"Detect 100vh inconsistency",mod:"vh100",fn:"detect100vh"},{name:"qa_fixed_stacking",desc:"Find z-index conflicts",mod:"fixed-stacking",fn:"detectFixedStacking"},{name:"qa_scroll_lock",desc:"Verify body scroll restored",mod:"scroll-lock",fn:"detectScrollLock"},{name:"qa_dark_mode",desc:"Compare light vs dark mode",mod:"dark-mode",fn:"detectDarkMode"},{name:"qa_orientation",desc:"Check layout on rotation",mod:"orientation",fn:"detectOrientation"},{name:"qa_pwa_meta",desc:"Validate PWA meta tags",mod:"pwa-meta",fn:"detectPwaMeta"}];for(const r of t)e.registerTool({name:r.name,description:r.desc,inputSchema:{type:"object",properties:{}}},async(e,t)=>{const n=m();if(!n)return{content:[{type:"text",text:"Error: Safari not connected"}],isError:!0};try{const e=await o(322)(`./${r.mod}.js`),t=await e[r.fn](n);return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error running ${r.name}: ${e}`}],isError:!0}}})}(e),function(e){e.registerTool({name:"qa_full_audit",description:"Run all 13 iOS QA detectors and generate a scored report",inputSchema:{type:"object",properties:{url:{type:"string",description:"URL to audit (optional — uses current page if not set)"}}}},async(e,t)=>{const r=m();if(!r)return{content:[{type:"text",text:"Error: Safari not connected"}],isError:!0};const{QAAudit:n}=await o.e(148).then(o.bind(o,148)),{generateAuditMarkdown:i}=await o.e(712).then(o.bind(o,712)),{QAHistory:s}=await o.e(844).then(o.bind(o,844)),a=new n(r),c=await a.runFullAudit(t.url),l=i(c),u=new s;return await u.save(c),{content:[{type:"text",text:l+"\n\n---\n\nJSON Report:\n```json\n"+JSON.stringify(c,null,2)+"\n```"}]}})}(e),function(e){const t=new Y;e.registerTool({name:"auth_save",description:"Save the current Safari session (cookies, localStorage) for a site",inputSchema:{type:"object",properties:{site:{type:"string",description:'Site domain to save auth for (e.g. "github.com")'}},required:["site"]}},async(e,r)=>{const o=m();if(!o)return{content:[{type:"text",text:"Error: Safari not connected"}],isError:!0};try{const e=r.site,n=(await o.getCookies()).filter(t=>{const r=t.domain.startsWith(".")?t.domain.slice(1):t.domain;return r===e||e.endsWith("."+r)});if(0===n.length)return{content:[{type:"text",text:`Error: No cookies found for domain "${e}"`}],isError:!0};const i=await t.save(e,o,n);return{content:[{type:"text",text:JSON.stringify({saved:!0,site:e,cookieCount:n.length,filePath:i})}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:String(e)}`}],isError:!0}}}),e.registerTool({name:"auth_restore",description:"Restore a previously saved auth session (cookies, localStorage) for a site",inputSchema:{type:"object",properties:{site:{type:"string",description:'Site domain to restore auth for (e.g. "github.com")'}},required:["site"]}},async(e,r)=>{const o=m();if(!o)return{content:[{type:"text",text:"Error: Safari not connected"}],isError:!0};try{const e=r.site;let n;try{const r=await t.checkExpiry(e);r.isExpired?n=`Warning: ${r.expiredCount} of ${r.totalCookies} cookies have expired`:r.isExpiring&&(n=`Warning: ${r.expiringCount} of ${r.totalCookies} cookies are expiring soon`)}catch{}await t.restore(e,o);const i={restored:!0,site:e};return n&&(i.warning=n),{content:[{type:"text",text:JSON.stringify(i)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:String(e)}`}],isError:!0}}}),e.registerTool({name:"auth_list",description:"List all saved auth profiles",inputSchema:{type:"object",properties:{},required:[]}},async()=>{try{const e=await t.list();return{content:[{type:"text",text:JSON.stringify(e)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:String(e)}`}],isError:!0}}})}(e)}class $e{pool;constructor(e){this.pool=e}async executeOnAll(e){const t=this.pool.getAll();return(await Promise.allSettled(t.map(async t=>{const r=Date.now();this.pool.markActivity(t.device.udid);try{const o=await e(t);return{device:t.preset,deviceId:t.device.udid,viewport:{w:t.device.viewport?.width??390,h:t.device.viewport?.height??844},result:o,timing:Date.now()-r}}catch(e){return{device:t.preset,deviceId:t.device.udid,viewport:{w:t.device.viewport?.width??390,h:t.device.viewport?.height??844},error:e instanceof Error?e.message:String(e),timing:Date.now()-r}}}))).map(e=>"fulfilled"===e.status?e.value:{device:"unknown",deviceId:"unknown",viewport:{w:0,h:0},error:"Promise rejected",timing:0})}async batchNavigate(e,t){return this.executeOnAll(async r=>r.client.navigate({url:e,waitUntil:t??"load"}))}async batchScreenshot(e){return this.executeOnAll(async t=>(await t.client.screenshot(e)).toString("base64"))}async batchExecute(e){return this.executeOnAll(async t=>t.client.evaluate(e))}}class Pe{locked=!1;queue=[];async acquire(){if(this.locked)return new Promise(e=>{this.queue.push(e)});this.locked=!0}release(){this.queue.length>0?this.queue.shift()():this.locked=!1}}class Oe extends J.EventEmitter{pool;authManager;workflows=new Map;completionLock=new Pe;constructor(e,t){super(),this.pool=e,this.authManager=t}async initWorkflow(e){const t=`wf-${Date.now()}`,r=await this.pool.bootAll(e.devices);if(e.authProfile&&await this.pool.injectAuth(e.authProfile),e.url){const t=new $e(this.pool);await t.batchNavigate(e.url)}const o=r.map((t,r)=>({name:e.workerNames?.[r]??`worker-${t.preset}`,deviceId:t.device.udid,preset:t.preset,status:"pending",startedAt:Date.now()})),n={id:t,status:"running",workers:o,startedAt:Date.now(),options:e};this.workflows.set(t,n);const i=o.map(t=>({workerName:t.name,prompt:this.generateWorkerPrompt(t,e)}));return{workflowId:t,workers:o.map(e=>({name:e.name,device:e.preset})),prompts:i}}async updateWorker(e,t,r){const o=this.workflows.get(e);if(!o)throw new Error(`Workflow not found: ${e}`);const n=o.workers.find(e=>e.name===t);if(!n)throw new Error(`Worker not found: ${t}`);n.status="active",n.lastUpdate=r,n.lastUpdateAt=Date.now()}async completeWorker(e,t,r){await this.completionLock.acquire();try{const o=this.workflows.get(e);if(!o)throw new Error(`Workflow not found: ${e}`);const n=o.workers.find(e=>e.name===t);if(!n)throw new Error(`Worker not found: ${t}`);n.status="completed",n.results=r,n.completedAt=Date.now(),this.checkWorkflowCompletion(o)}finally{this.completionLock.release()}}async failWorker(e,t,r){await this.completionLock.acquire();try{const o=this.workflows.get(e);if(!o)throw new Error(`Workflow not found: ${e}`);const n=o.workers.find(e=>e.name===t);if(!n)throw new Error(`Worker not found: ${t}`);n.status="failed",n.error=r,n.completedAt=Date.now(),this.checkWorkflowCompletion(o)}finally{this.completionLock.release()}}getStatus(e){const t=this.workflows.get(e);if(!t)throw new Error(`Workflow not found: ${e}`);return{id:e,status:t.status,workers:t.workers.map(e=>({name:e.name,device:e.preset,status:e.status,lastUpdate:e.lastUpdate,lastUpdateAt:e.lastUpdateAt})),completedCount:t.workers.filter(e=>"completed"===e.status||"failed"===e.status).length,totalCount:t.workers.length,elapsed:Date.now()-t.startedAt}}collectResults(e){const t=this.workflows.get(e);if(!t)throw new Error(`Workflow not found: ${e}`);return{id:e,status:t.status,duration:(t.completedAt??Date.now())-t.startedAt,workers:t.workers.map(e=>{const t=$[e.preset];return{name:e.name,device:e.preset,viewport:t?{width:t.w,height:t.h}:void 0,status:e.status,results:e.results,error:e.error,duration:(e.completedAt??Date.now())-e.startedAt}})}}collectPartialResults(e){const t=this.collectResults(e);return t.workers=t.workers.filter(e=>"completed"===e.status||"failed"===e.status),t}async cleanupWorkflow(e){this.workflows.delete(e),await this.pool.shutdownAll()}checkWorkflowCompletion(e){if(e.workers.every(e=>"completed"===e.status||"failed"===e.status)){const t=e.workers.some(e=>"failed"===e.status);e.status=t?"partial":"completed",e.completedAt=Date.now(),this.emit("workflow:completed",{id:e.id,status:e.status})}}generateWorkerPrompt(e,t){const r=$[e.preset];return[`You are worker "${e.name}" testing on ${e.preset}.`,r?`Device: ${r.name} (${r.w}x${r.h})`:`Device: ${e.preset}`,`URL: ${t.url??"Not set"}`,`Task: ${t.taskDescription??"General QA"}`,"","Use these tools with your assigned device:","- navigate, screenshot, click, type, query_dom, javascript, read_page","- Report findings via worker_update","- When done, call worker_complete with your results"].join("\n")}}class Ie{pool;batch;constructor(e,t){this.pool=e,this.batch=t}async capture(e,t){await this.batch.batchNavigate(e,t?.waitUntil??"load"),t?.settleTime&&await new Promise(e=>setTimeout(e,t.settleTime));const r=await this.batch.batchScreenshot({format:t?.format??"png"}),o=await this.batch.batchExecute("\n (function() {\n return {\n title: document.title,\n scrollHeight: document.documentElement.scrollHeight,\n scrollWidth: document.documentElement.scrollWidth,\n innerWidth: window.innerWidth,\n innerHeight: window.innerHeight,\n devicePixelRatio: window.devicePixelRatio,\n hasHorizontalOverflow: document.documentElement.scrollWidth > window.innerWidth\n };\n })()\n ");return r.map((e,t)=>({device:e.device,viewport:e.viewport,breakpoint:this.mapBreakpoint(e.viewport.w),screenshot:e.result??"",metadata:o[t]?.result??null,error:e.error??o[t]?.error,timing:e.timing}))}mapBreakpoint(e){return e<640||e<768?"sm":e<1024?"md":e<1280?"lg":"xl"}}class De extends J.EventEmitter{pool;authProfile;interval=null;knownStates=new Map;constructor(e,t){super(),this.pool=e,this.authProfile=t}start(e=1e4){if(!this.interval){for(const e of this.pool.getAll())this.knownStates.set(e.device.udid,"Booted");this.interval=setInterval(()=>this.check(),e)}}stop(){this.interval&&(clearInterval(this.interval),this.interval=null)}async check(){const e=this.pool.getManager();for(const[t,r]of this.knownStates)try{const o=await e.getDevice(t);o&&"Booted"===o.state||"Booted"===r&&(console.error(`[CrashWatcher] Simulator ${t} crashed (was Booted, now ${o?.state??"gone"})`),this.emit("crash",{deviceId:t}),await this.recover(t))}catch{}}async recover(e){const t=Date.now();try{const r=this.pool.getManager(),o=this.pool.get(e);if(!o)return;console.error(`[CrashWatcher] Recovering ${o.preset}...`),await r.boot(o.preset);try{await o.client.connect()}catch{console.error(`[CrashWatcher] WebKit reconnect failed for ${o.preset}`)}if(this.authProfile)try{await this.pool.injectAuth(this.authProfile)}catch{console.error(`[CrashWatcher] Auth restore failed for ${o.preset}`)}this.knownStates.set(e,"Booted");const n=Date.now()-t;console.error(`[CrashWatcher] Recovered ${o.preset} in ${n}ms`),this.emit("recovered",{deviceId:e,duration:n})}catch(t){console.error(`[CrashWatcher] Recovery failed for ${e}: ${t}`),this.emit("recovery-failed",{deviceId:e,error:t})}}addDevice(e){this.knownStates.set(e,"Booted")}removeDevice(e){this.knownStates.delete(e)}}class _e extends J.EventEmitter{timer=null;checkIntervalMs;warnThresholdMs;fatalThresholdMs;heavyOpThresholdMs;lastCheckAt=0;maxDriftObserved=0;warnCount=0;heavyOpCount=0;constructor(e){super(),this.checkIntervalMs=e?.checkIntervalMs??200,this.warnThresholdMs=e?.warnThresholdMs??2e3,this.fatalThresholdMs=e?.fatalThresholdMs??0,this.heavyOpThresholdMs=e?.heavyOpFatalThresholdMs??12e4}start(){this.stop(),this.lastCheckAt=Date.now(),this.timer=setInterval(()=>{const e=Date.now(),t=e-this.lastCheckAt-this.checkIntervalMs;this.lastCheckAt=e,t>this.maxDriftObserved&&(this.maxDriftObserved=t);const r=0===this.fatalThresholdMs?0:this.heavyOpCount>0?this.heavyOpThresholdMs:this.fatalThresholdMs;r>0&&t>r?(console.error(`[EventLoopMonitor] FATAL: Event loop blocked for ${t}ms (threshold: ${r}ms${this.heavyOpCount>0?", heavy-op mode":""})`),this.emit("fatal",{driftMs:t,timestamp:e})):t>this.warnThresholdMs&&(this.warnCount++,console.error(`[EventLoopMonitor] WARN: Event loop blocked for ${t}ms (warn #${this.warnCount})`),this.emit("warn",{driftMs:t,timestamp:e}))},this.checkIntervalMs),this.timer.unref()}stop(){this.timer&&(clearInterval(this.timer),this.timer=null)}isRunning(){return null!==this.timer}beginHeavyOperation(){this.heavyOpCount++}endHeavyOperation(){this.heavyOpCount>0&&this.heavyOpCount--}getStats(){return{maxDriftMs:this.maxDriftObserved,warnCount:this.warnCount,isRunning:this.isRunning()}}resetStats(){this.maxDriftObserved=0,this.warnCount=0}}let Ce=null;const Re=(0,S.promisify)(k.execFile);class Ae extends J.EventEmitter{interval=null;warnMB;killMB;checkIntervalMs;constructor(e){super(),this.warnMB=e?.warnMB??400,this.killMB=e?.killMB??600,this.checkIntervalMs=e?.intervalMs??3e4}start(){this.interval||(this.interval=setInterval(()=>this.check(),this.checkIntervalMs))}stop(){this.interval&&(clearInterval(this.interval),this.interval=null)}async check(){try{const{stdout:e}=await Re("pgrep",["-f","SimulatorTrampoline"]),t=e.trim().split("\n").filter(Boolean);for(const e of t)try{const{stdout:t}=await Re("ps",["-o","rss=","-p",e]),r=Math.floor(parseInt(t.trim(),10)/1024);r>this.killMB?this.emit("critical",{pid:e,rssMB:r,threshold:this.killMB}):r>this.warnMB&&this.emit("warn",{pid:e,rssMB:r,threshold:this.warnMB})}catch{}}catch{}}}const Ne=(new e.Command).name("opensafari").description("iOS Safari automation MCP server via Xcode Simulator").version("0.0.1");Ne.command("serve").description("Start OpenSafari MCP server").option("--http [port]","Use HTTP transport (default: stdio)").option("--devices <presets>","Auto-boot devices (comma-separated)").option("--auth <path>","Auth profile to auto-restore").option("--all-tools","Expose all tool tiers immediately").option("--blocked-domains <domains>","Block navigation to these domains").option("--audit-log","Enable tool call audit logging").option("--no-zombie-cleanup","Disable periodic zombie simulator cleanup").action(async e=>{const t=new y;Te(t),e.allTools&&t.setTier(3);const r=new de({max:5}),o=new $e(r),n=new Y,i=new Oe(r,n),s=new Ie(r,o);ke(i),Ee=s,fe=o,ge=o,function(e){ve=e}(o),function(e){const t=async t=>{console.error(`[OpenSafari] Received ${t}, shutting down gracefully...`);try{await e.shutdownAll()}catch(e){console.error(`[OpenSafari] Error during shutdown: ${e}`)}process.exit(0)};process.on("SIGTERM",()=>t("SIGTERM")),process.on("SIGINT",()=>t("SIGINT"))}(r);const a=new De(r);var c;a.on("crash",({deviceId:e})=>{console.error(`[OpenSafari] Crash detected on device ${e}`)}),a.on("recovered",({deviceId:e})=>{console.error(`[OpenSafari] Device ${e} recovered from crash`)}),a.on("recovery-failed",({deviceId:e,error:t})=>{console.error(`[OpenSafari] Device ${e} recovery failed: ${t}`)}),a.start(),se().then(e=>{e>0&&console.error(`[OpenSafari] Found ${e} orphaned simulator process(es)`)}).catch(()=>{}),!1!==e.zombieCleanup&&function(e,t=6e4,r=3e4){ce&&(clearTimeout(ce),ce=null),ae&&(clearInterval(ae),ae=null);const o=async()=>{const t=e(),r=await se(t);r>0&&console.error(`[ZombieCleanup] Periodic cleanup removed ${r} orphaned simulator(s)`)};ce=setTimeout(()=>{ce=null,o().catch(()=>{}),ae=setInterval(()=>{o().catch(()=>{})},t),ae.unref()},r),ce.unref?.()}(()=>{const e=new Set;for(const t of r.getAll())e.add(t.device.udid);return e}),e.blockedDomains&&(c=e.blockedDomains.split(",").map(e=>e.trim()),f=c,console.error(`[OpenSafari] Domain guard active: ${e.blockedDomains}`)),e.auditLog&&(t.enableAuditLog(),console.error("[OpenSafari] Audit logging enabled"));const l=new _e;Ce=l,l.on("warn",({driftMs:e})=>{console.error(`[OpenSafari] Event loop drift warning: ${e}ms`)}),l.start();const u=new Ae;u.on("warn",({pid:e,rssMB:t})=>{console.error(`[OpenSafari] Simulator memory warning: PID ${e} using ${t}MB`)}),u.on("critical",({pid:e,rssMB:t})=>{console.error(`[OpenSafari] Simulator memory critical: PID ${e} using ${t}MB`)}),u.start();const d=e.http?"http":"stdio",p="string"==typeof e.http?parseInt(e.http,10):3100;t.start({transport:d,port:p}),console.error("[OpenSafari] MCP server running")});const je=Ne.command("auth").description("Manage login persistence profiles");je.command("save").argument("<site>","Site domain").action(async e=>{const t=m();t||(console.error('Error: No active Safari connection. Run "opensafari serve" first.'),process.exit(1));const r=new Y,o=await r.save(e,t);console.log(`Auth saved: ${o}`)}),je.command("list").action(async()=>{const e=new Y,t=await e.list();if(0!==t.length){console.log("Saved auth profiles:\n");for(const e of t)console.log(` ${e.site.padEnd(30)} ${e.cookieCount} cookies (saved: ${e.savedAt})`)}else console.log("No auth profiles saved.")}),je.command("delete").argument("<site>","Site domain to delete").action(async e=>{const t=new Y;await t.delete(e),console.log(`Auth deleted: ${e}`)}),Ne.command("doctor").description("Verify installation and diagnose issues").action(async()=>{console.log("OpenSafari Doctor\n");const e=await async function(){const e={installed:!1,simulatorAvailable:!1,iosRuntimes:[],proxyReachable:!1,devicePortReachable:!1,issues:[],suggestions:[]};if("darwin"!==process.platform)return e.issues.push("OpenSafari requires macOS (Xcode Simulator is macOS only)"),e.suggestions.push("Run on a Mac with Xcode installed"),e;try{await W("xcrun",["--version"]),e.installed=!0}catch{return e.issues.push("xcrun not found — Xcode or Command Line Tools not installed"),e.suggestions.push("Install Xcode from the App Store, or run: xcode-select --install"),e}try{const{stdout:t}=await W("xcodebuild",["-version"]),r=t.match(/Xcode (\d+\.\d+)/);r&&(e.version=r[1])}catch{e.issues.push("xcodebuild not available — Xcode may not be fully installed"),e.suggestions.push("Install Xcode from the App Store")}try{await W("xcrun",["simctl","list","-j"]),e.simulatorAvailable=!0}catch{e.issues.push("Simulator runtime not available"),e.suggestions.push("Open Xcode and install iOS Simulator components")}try{const{stdout:t}=await W("xcrun",["simctl","list","runtimes","-j"]),r=JSON.parse(t).runtimes??[];e.iosRuntimes=r.filter(e=>e.isAvailable&&"iOS"===e.platform).map(e=>`iOS ${e.version}`),0===e.iosRuntimes.length&&(e.issues.push("No iOS Simulator runtimes installed"),e.suggestions.push("Run: xcodebuild -downloadPlatform iOS"))}catch{e.issues.push("Could not list simulator runtimes")}const[t]=process.version.slice(1).split(".").map(Number);t<18&&(e.issues.push(`Node.js ${process.version} detected — requires >= 18`),e.suggestions.push("Upgrade Node.js to v18 or later"));try{await W("which",["ios_webkit_debug_proxy"])}catch{e.issues.push("ios_webkit_debug_proxy not found"),e.suggestions.push("Install with: brew install ios-webkit-debug-proxy")}e.webInspectorSocket=await async function(){return await M()??void 0}(),e.webInspectorSocket||e.issues.push("Web Inspector socket not found — is a simulator booted?");const r=await async function(){for(const e of B)if(await U(e,"iOS Devices"))return{reachable:!0,port:e};return{reachable:!1}}();if(e.proxyReachable=r.reachable,e.proxyPort=r.port,e.proxyReachable||e.suggestions.push("Start proxy with: ios_webkit_debug_proxy or use opensafari device_boot"),e.proxyReachable){const t=await async function(){for(const e of F)if(await U(e,"["))return{reachable:!0,port:e};return{reachable:!1}}();e.devicePortReachable=t.reachable,e.devicePort=t.port,e.devicePortReachable||e.suggestions.push("Proxy device-list is reachable but device port is not responding. A simulator device may not be connected, or the device port range may be misconfigured.")}return e}(),t=[{name:"macOS",ok:"darwin"===process.platform},{name:"Xcode",ok:e.installed,detail:e.version?`v${e.version}`:void 0},{name:"Simulator",ok:e.simulatorAvailable},{name:"iOS Runtimes",ok:e.iosRuntimes.length>0,detail:e.iosRuntimes.join(", ")},{name:"Node.js >= 18",ok:parseInt(process.version.slice(1))>=18,detail:process.version},{name:"WebInspector Socket",ok:!!e.webInspectorSocket,detail:e.webInspectorSocket},{name:"Proxy Reachable",ok:e.proxyReachable},...e.proxyReachable?[{name:"Device Port",ok:e.devicePortReachable,detail:e.devicePort?`port ${e.devicePort}`:void 0}]:[]];for(const e of t){const t=e.ok?"✓":"✗",r=e.detail?` (${e.detail})`:"";console.log(` ${t} ${e.name}${r}`)}if(e.issues.length>0){console.log("\nIssues:");for(const t of e.issues)console.log(` ! ${t}`)}if(e.suggestions.length>0){console.log("\nSuggestions:");for(const t of e.suggestions)console.log(` → ${t}`)}const r=t.every(e=>e.ok);process.exit(r?0:1)}),Ne.command("devices").description("List available device presets").action(()=>{console.log("Available Device Presets:\n");for(const[e,t]of Object.entries($))console.log(` ${e.padEnd(22)} ${t.name.padEnd(35)} ${t.w}×${t.h} @${t.dpr}x`)}),Ne.parse()})()})();
3
+ //# sourceMappingURL=index.js.map