onbuzz 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (506) hide show
  1. package/LICENSE +267 -0
  2. package/README.md +425 -0
  3. package/bin/cli.js +556 -0
  4. package/bin/loxia-terminal-v2.js +162 -0
  5. package/bin/loxia-terminal.js +90 -0
  6. package/bin/start-with-terminal.js +200 -0
  7. package/node_modules/@isaacs/balanced-match/LICENSE.md +23 -0
  8. package/node_modules/@isaacs/balanced-match/README.md +60 -0
  9. package/node_modules/@isaacs/balanced-match/dist/commonjs/index.d.ts +9 -0
  10. package/node_modules/@isaacs/balanced-match/dist/commonjs/index.d.ts.map +1 -0
  11. package/node_modules/@isaacs/balanced-match/dist/commonjs/index.js +59 -0
  12. package/node_modules/@isaacs/balanced-match/dist/commonjs/index.js.map +1 -0
  13. package/node_modules/@isaacs/balanced-match/dist/commonjs/package.json +3 -0
  14. package/node_modules/@isaacs/balanced-match/dist/esm/index.d.ts +9 -0
  15. package/node_modules/@isaacs/balanced-match/dist/esm/index.d.ts.map +1 -0
  16. package/node_modules/@isaacs/balanced-match/dist/esm/index.js +54 -0
  17. package/node_modules/@isaacs/balanced-match/dist/esm/index.js.map +1 -0
  18. package/node_modules/@isaacs/balanced-match/dist/esm/package.json +3 -0
  19. package/node_modules/@isaacs/balanced-match/package.json +79 -0
  20. package/node_modules/@isaacs/brace-expansion/LICENSE +23 -0
  21. package/node_modules/@isaacs/brace-expansion/README.md +97 -0
  22. package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.d.ts +6 -0
  23. package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.d.ts.map +1 -0
  24. package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.js +199 -0
  25. package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.js.map +1 -0
  26. package/node_modules/@isaacs/brace-expansion/dist/commonjs/package.json +3 -0
  27. package/node_modules/@isaacs/brace-expansion/dist/esm/index.d.ts +6 -0
  28. package/node_modules/@isaacs/brace-expansion/dist/esm/index.d.ts.map +1 -0
  29. package/node_modules/@isaacs/brace-expansion/dist/esm/index.js +195 -0
  30. package/node_modules/@isaacs/brace-expansion/dist/esm/index.js.map +1 -0
  31. package/node_modules/@isaacs/brace-expansion/dist/esm/package.json +3 -0
  32. package/node_modules/@isaacs/brace-expansion/package.json +60 -0
  33. package/node_modules/glob/LICENSE.md +63 -0
  34. package/node_modules/glob/README.md +1177 -0
  35. package/node_modules/glob/dist/commonjs/glob.d.ts +388 -0
  36. package/node_modules/glob/dist/commonjs/glob.d.ts.map +1 -0
  37. package/node_modules/glob/dist/commonjs/glob.js +247 -0
  38. package/node_modules/glob/dist/commonjs/glob.js.map +1 -0
  39. package/node_modules/glob/dist/commonjs/has-magic.d.ts +14 -0
  40. package/node_modules/glob/dist/commonjs/has-magic.d.ts.map +1 -0
  41. package/node_modules/glob/dist/commonjs/has-magic.js +27 -0
  42. package/node_modules/glob/dist/commonjs/has-magic.js.map +1 -0
  43. package/node_modules/glob/dist/commonjs/ignore.d.ts +24 -0
  44. package/node_modules/glob/dist/commonjs/ignore.d.ts.map +1 -0
  45. package/node_modules/glob/dist/commonjs/ignore.js +119 -0
  46. package/node_modules/glob/dist/commonjs/ignore.js.map +1 -0
  47. package/node_modules/glob/dist/commonjs/index.d.ts +97 -0
  48. package/node_modules/glob/dist/commonjs/index.d.ts.map +1 -0
  49. package/node_modules/glob/dist/commonjs/index.js +68 -0
  50. package/node_modules/glob/dist/commonjs/index.js.map +1 -0
  51. package/node_modules/glob/dist/commonjs/index.min.js +4 -0
  52. package/node_modules/glob/dist/commonjs/index.min.js.map +7 -0
  53. package/node_modules/glob/dist/commonjs/package.json +3 -0
  54. package/node_modules/glob/dist/commonjs/pattern.d.ts +76 -0
  55. package/node_modules/glob/dist/commonjs/pattern.d.ts.map +1 -0
  56. package/node_modules/glob/dist/commonjs/pattern.js +219 -0
  57. package/node_modules/glob/dist/commonjs/pattern.js.map +1 -0
  58. package/node_modules/glob/dist/commonjs/processor.d.ts +59 -0
  59. package/node_modules/glob/dist/commonjs/processor.d.ts.map +1 -0
  60. package/node_modules/glob/dist/commonjs/processor.js +301 -0
  61. package/node_modules/glob/dist/commonjs/processor.js.map +1 -0
  62. package/node_modules/glob/dist/commonjs/walker.d.ts +97 -0
  63. package/node_modules/glob/dist/commonjs/walker.d.ts.map +1 -0
  64. package/node_modules/glob/dist/commonjs/walker.js +387 -0
  65. package/node_modules/glob/dist/commonjs/walker.js.map +1 -0
  66. package/node_modules/glob/dist/esm/glob.d.ts +388 -0
  67. package/node_modules/glob/dist/esm/glob.d.ts.map +1 -0
  68. package/node_modules/glob/dist/esm/glob.js +243 -0
  69. package/node_modules/glob/dist/esm/glob.js.map +1 -0
  70. package/node_modules/glob/dist/esm/has-magic.d.ts +14 -0
  71. package/node_modules/glob/dist/esm/has-magic.d.ts.map +1 -0
  72. package/node_modules/glob/dist/esm/has-magic.js +23 -0
  73. package/node_modules/glob/dist/esm/has-magic.js.map +1 -0
  74. package/node_modules/glob/dist/esm/ignore.d.ts +24 -0
  75. package/node_modules/glob/dist/esm/ignore.d.ts.map +1 -0
  76. package/node_modules/glob/dist/esm/ignore.js +115 -0
  77. package/node_modules/glob/dist/esm/ignore.js.map +1 -0
  78. package/node_modules/glob/dist/esm/index.d.ts +97 -0
  79. package/node_modules/glob/dist/esm/index.d.ts.map +1 -0
  80. package/node_modules/glob/dist/esm/index.js +55 -0
  81. package/node_modules/glob/dist/esm/index.js.map +1 -0
  82. package/node_modules/glob/dist/esm/index.min.js +4 -0
  83. package/node_modules/glob/dist/esm/index.min.js.map +7 -0
  84. package/node_modules/glob/dist/esm/package.json +3 -0
  85. package/node_modules/glob/dist/esm/pattern.d.ts +76 -0
  86. package/node_modules/glob/dist/esm/pattern.d.ts.map +1 -0
  87. package/node_modules/glob/dist/esm/pattern.js +215 -0
  88. package/node_modules/glob/dist/esm/pattern.js.map +1 -0
  89. package/node_modules/glob/dist/esm/processor.d.ts +59 -0
  90. package/node_modules/glob/dist/esm/processor.d.ts.map +1 -0
  91. package/node_modules/glob/dist/esm/processor.js +294 -0
  92. package/node_modules/glob/dist/esm/processor.js.map +1 -0
  93. package/node_modules/glob/dist/esm/walker.d.ts +97 -0
  94. package/node_modules/glob/dist/esm/walker.d.ts.map +1 -0
  95. package/node_modules/glob/dist/esm/walker.js +381 -0
  96. package/node_modules/glob/dist/esm/walker.js.map +1 -0
  97. package/node_modules/glob/node_modules/minimatch/LICENSE.md +55 -0
  98. package/node_modules/glob/node_modules/minimatch/README.md +453 -0
  99. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts +2 -0
  100. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts.map +1 -0
  101. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js +14 -0
  102. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js.map +1 -0
  103. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts +20 -0
  104. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts.map +1 -0
  105. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js +591 -0
  106. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js.map +1 -0
  107. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.d.ts +8 -0
  108. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.d.ts.map +1 -0
  109. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js +152 -0
  110. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js.map +1 -0
  111. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts +15 -0
  112. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts.map +1 -0
  113. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js +30 -0
  114. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js.map +1 -0
  115. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts +94 -0
  116. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts.map +1 -0
  117. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js +1029 -0
  118. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js.map +1 -0
  119. package/node_modules/glob/node_modules/minimatch/dist/commonjs/package.json +3 -0
  120. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts +22 -0
  121. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts.map +1 -0
  122. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js +38 -0
  123. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js.map +1 -0
  124. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts +2 -0
  125. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts.map +1 -0
  126. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.js +10 -0
  127. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.js.map +1 -0
  128. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts +20 -0
  129. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts.map +1 -0
  130. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js +587 -0
  131. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js.map +1 -0
  132. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.d.ts +8 -0
  133. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.d.ts.map +1 -0
  134. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js +148 -0
  135. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js.map +1 -0
  136. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts +15 -0
  137. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts.map +1 -0
  138. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js +26 -0
  139. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js.map +1 -0
  140. package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts +94 -0
  141. package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts.map +1 -0
  142. package/node_modules/glob/node_modules/minimatch/dist/esm/index.js +1016 -0
  143. package/node_modules/glob/node_modules/minimatch/dist/esm/index.js.map +1 -0
  144. package/node_modules/glob/node_modules/minimatch/dist/esm/package.json +3 -0
  145. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts +22 -0
  146. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts.map +1 -0
  147. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js +34 -0
  148. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js.map +1 -0
  149. package/node_modules/glob/node_modules/minimatch/package.json +67 -0
  150. package/node_modules/glob/package.json +101 -0
  151. package/node_modules/minipass/LICENSE +15 -0
  152. package/node_modules/minipass/README.md +825 -0
  153. package/node_modules/minipass/dist/commonjs/index.d.ts +549 -0
  154. package/node_modules/minipass/dist/commonjs/index.d.ts.map +1 -0
  155. package/node_modules/minipass/dist/commonjs/index.js +1028 -0
  156. package/node_modules/minipass/dist/commonjs/index.js.map +1 -0
  157. package/node_modules/minipass/dist/commonjs/package.json +3 -0
  158. package/node_modules/minipass/dist/esm/index.d.ts +549 -0
  159. package/node_modules/minipass/dist/esm/index.d.ts.map +1 -0
  160. package/node_modules/minipass/dist/esm/index.js +1018 -0
  161. package/node_modules/minipass/dist/esm/index.js.map +1 -0
  162. package/node_modules/minipass/dist/esm/package.json +3 -0
  163. package/node_modules/minipass/package.json +82 -0
  164. package/node_modules/package-json-from-dist/LICENSE.md +63 -0
  165. package/node_modules/package-json-from-dist/README.md +110 -0
  166. package/node_modules/package-json-from-dist/dist/commonjs/index.d.ts +89 -0
  167. package/node_modules/package-json-from-dist/dist/commonjs/index.d.ts.map +1 -0
  168. package/node_modules/package-json-from-dist/dist/commonjs/index.js +134 -0
  169. package/node_modules/package-json-from-dist/dist/commonjs/index.js.map +1 -0
  170. package/node_modules/package-json-from-dist/dist/commonjs/package.json +3 -0
  171. package/node_modules/package-json-from-dist/dist/esm/index.d.ts +89 -0
  172. package/node_modules/package-json-from-dist/dist/esm/index.d.ts.map +1 -0
  173. package/node_modules/package-json-from-dist/dist/esm/index.js +129 -0
  174. package/node_modules/package-json-from-dist/dist/esm/index.js.map +1 -0
  175. package/node_modules/package-json-from-dist/dist/esm/package.json +3 -0
  176. package/node_modules/package-json-from-dist/package.json +68 -0
  177. package/node_modules/path-scurry/LICENSE.md +55 -0
  178. package/node_modules/path-scurry/README.md +636 -0
  179. package/node_modules/path-scurry/dist/commonjs/index.d.ts +1115 -0
  180. package/node_modules/path-scurry/dist/commonjs/index.d.ts.map +1 -0
  181. package/node_modules/path-scurry/dist/commonjs/index.js +2018 -0
  182. package/node_modules/path-scurry/dist/commonjs/index.js.map +1 -0
  183. package/node_modules/path-scurry/dist/commonjs/package.json +3 -0
  184. package/node_modules/path-scurry/dist/esm/index.d.ts +1115 -0
  185. package/node_modules/path-scurry/dist/esm/index.d.ts.map +1 -0
  186. package/node_modules/path-scurry/dist/esm/index.js +1983 -0
  187. package/node_modules/path-scurry/dist/esm/index.js.map +1 -0
  188. package/node_modules/path-scurry/dist/esm/package.json +3 -0
  189. package/node_modules/path-scurry/node_modules/lru-cache/LICENSE.md +55 -0
  190. package/node_modules/path-scurry/node_modules/lru-cache/README.md +383 -0
  191. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts +1323 -0
  192. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts.map +1 -0
  193. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js +1589 -0
  194. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js.map +1 -0
  195. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js +2 -0
  196. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js.map +7 -0
  197. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/package.json +3 -0
  198. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts +1323 -0
  199. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts.map +1 -0
  200. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js +1585 -0
  201. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js.map +1 -0
  202. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js +2 -0
  203. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js.map +7 -0
  204. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/package.json +3 -0
  205. package/node_modules/path-scurry/node_modules/lru-cache/package.json +101 -0
  206. package/node_modules/path-scurry/package.json +88 -0
  207. package/node_modules/rimraf/LICENSE.md +55 -0
  208. package/node_modules/rimraf/README.md +226 -0
  209. package/node_modules/rimraf/dist/commonjs/default-tmp.d.ts +3 -0
  210. package/node_modules/rimraf/dist/commonjs/default-tmp.d.ts.map +1 -0
  211. package/node_modules/rimraf/dist/commonjs/default-tmp.js +58 -0
  212. package/node_modules/rimraf/dist/commonjs/default-tmp.js.map +1 -0
  213. package/node_modules/rimraf/dist/commonjs/error.d.ts +6 -0
  214. package/node_modules/rimraf/dist/commonjs/error.d.ts.map +1 -0
  215. package/node_modules/rimraf/dist/commonjs/error.js +10 -0
  216. package/node_modules/rimraf/dist/commonjs/error.js.map +1 -0
  217. package/node_modules/rimraf/dist/commonjs/fix-eperm.d.ts +3 -0
  218. package/node_modules/rimraf/dist/commonjs/fix-eperm.d.ts.map +1 -0
  219. package/node_modules/rimraf/dist/commonjs/fix-eperm.js +38 -0
  220. package/node_modules/rimraf/dist/commonjs/fix-eperm.js.map +1 -0
  221. package/node_modules/rimraf/dist/commonjs/fs.d.ts +15 -0
  222. package/node_modules/rimraf/dist/commonjs/fs.d.ts.map +1 -0
  223. package/node_modules/rimraf/dist/commonjs/fs.js +33 -0
  224. package/node_modules/rimraf/dist/commonjs/fs.js.map +1 -0
  225. package/node_modules/rimraf/dist/commonjs/ignore-enoent.d.ts +3 -0
  226. package/node_modules/rimraf/dist/commonjs/ignore-enoent.d.ts.map +1 -0
  227. package/node_modules/rimraf/dist/commonjs/ignore-enoent.js +24 -0
  228. package/node_modules/rimraf/dist/commonjs/ignore-enoent.js.map +1 -0
  229. package/node_modules/rimraf/dist/commonjs/index.d.ts +50 -0
  230. package/node_modules/rimraf/dist/commonjs/index.d.ts.map +1 -0
  231. package/node_modules/rimraf/dist/commonjs/index.js +78 -0
  232. package/node_modules/rimraf/dist/commonjs/index.js.map +1 -0
  233. package/node_modules/rimraf/dist/commonjs/opt-arg.d.ts +34 -0
  234. package/node_modules/rimraf/dist/commonjs/opt-arg.d.ts.map +1 -0
  235. package/node_modules/rimraf/dist/commonjs/opt-arg.js +53 -0
  236. package/node_modules/rimraf/dist/commonjs/opt-arg.js.map +1 -0
  237. package/node_modules/rimraf/dist/commonjs/package.json +3 -0
  238. package/node_modules/rimraf/dist/commonjs/path-arg.d.ts +4 -0
  239. package/node_modules/rimraf/dist/commonjs/path-arg.d.ts.map +1 -0
  240. package/node_modules/rimraf/dist/commonjs/path-arg.js +48 -0
  241. package/node_modules/rimraf/dist/commonjs/path-arg.js.map +1 -0
  242. package/node_modules/rimraf/dist/commonjs/readdir-or-error.d.ts +3 -0
  243. package/node_modules/rimraf/dist/commonjs/readdir-or-error.d.ts.map +1 -0
  244. package/node_modules/rimraf/dist/commonjs/readdir-or-error.js +19 -0
  245. package/node_modules/rimraf/dist/commonjs/readdir-or-error.js.map +1 -0
  246. package/node_modules/rimraf/dist/commonjs/retry-busy.d.ts +8 -0
  247. package/node_modules/rimraf/dist/commonjs/retry-busy.d.ts.map +1 -0
  248. package/node_modules/rimraf/dist/commonjs/retry-busy.js +65 -0
  249. package/node_modules/rimraf/dist/commonjs/retry-busy.js.map +1 -0
  250. package/node_modules/rimraf/dist/commonjs/rimraf-manual.d.ts +3 -0
  251. package/node_modules/rimraf/dist/commonjs/rimraf-manual.d.ts.map +1 -0
  252. package/node_modules/rimraf/dist/commonjs/rimraf-manual.js +8 -0
  253. package/node_modules/rimraf/dist/commonjs/rimraf-manual.js.map +1 -0
  254. package/node_modules/rimraf/dist/commonjs/rimraf-move-remove.d.ts +4 -0
  255. package/node_modules/rimraf/dist/commonjs/rimraf-move-remove.d.ts.map +1 -0
  256. package/node_modules/rimraf/dist/commonjs/rimraf-move-remove.js +138 -0
  257. package/node_modules/rimraf/dist/commonjs/rimraf-move-remove.js.map +1 -0
  258. package/node_modules/rimraf/dist/commonjs/rimraf-native.d.ts +4 -0
  259. package/node_modules/rimraf/dist/commonjs/rimraf-native.d.ts.map +1 -0
  260. package/node_modules/rimraf/dist/commonjs/rimraf-native.js +24 -0
  261. package/node_modules/rimraf/dist/commonjs/rimraf-native.js.map +1 -0
  262. package/node_modules/rimraf/dist/commonjs/rimraf-posix.d.ts +4 -0
  263. package/node_modules/rimraf/dist/commonjs/rimraf-posix.d.ts.map +1 -0
  264. package/node_modules/rimraf/dist/commonjs/rimraf-posix.js +103 -0
  265. package/node_modules/rimraf/dist/commonjs/rimraf-posix.js.map +1 -0
  266. package/node_modules/rimraf/dist/commonjs/rimraf-windows.d.ts +4 -0
  267. package/node_modules/rimraf/dist/commonjs/rimraf-windows.d.ts.map +1 -0
  268. package/node_modules/rimraf/dist/commonjs/rimraf-windows.js +159 -0
  269. package/node_modules/rimraf/dist/commonjs/rimraf-windows.js.map +1 -0
  270. package/node_modules/rimraf/dist/commonjs/use-native.d.ts +4 -0
  271. package/node_modules/rimraf/dist/commonjs/use-native.d.ts.map +1 -0
  272. package/node_modules/rimraf/dist/commonjs/use-native.js +18 -0
  273. package/node_modules/rimraf/dist/commonjs/use-native.js.map +1 -0
  274. package/node_modules/rimraf/dist/esm/bin.d.mts +3 -0
  275. package/node_modules/rimraf/dist/esm/bin.d.mts.map +1 -0
  276. package/node_modules/rimraf/dist/esm/bin.mjs +250 -0
  277. package/node_modules/rimraf/dist/esm/bin.mjs.map +1 -0
  278. package/node_modules/rimraf/dist/esm/default-tmp.d.ts +3 -0
  279. package/node_modules/rimraf/dist/esm/default-tmp.d.ts.map +1 -0
  280. package/node_modules/rimraf/dist/esm/default-tmp.js +55 -0
  281. package/node_modules/rimraf/dist/esm/default-tmp.js.map +1 -0
  282. package/node_modules/rimraf/dist/esm/error.d.ts +6 -0
  283. package/node_modules/rimraf/dist/esm/error.d.ts.map +1 -0
  284. package/node_modules/rimraf/dist/esm/error.js +5 -0
  285. package/node_modules/rimraf/dist/esm/error.js.map +1 -0
  286. package/node_modules/rimraf/dist/esm/fix-eperm.d.ts +3 -0
  287. package/node_modules/rimraf/dist/esm/fix-eperm.d.ts.map +1 -0
  288. package/node_modules/rimraf/dist/esm/fix-eperm.js +33 -0
  289. package/node_modules/rimraf/dist/esm/fix-eperm.js.map +1 -0
  290. package/node_modules/rimraf/dist/esm/fs.d.ts +15 -0
  291. package/node_modules/rimraf/dist/esm/fs.d.ts.map +1 -0
  292. package/node_modules/rimraf/dist/esm/fs.js +18 -0
  293. package/node_modules/rimraf/dist/esm/fs.js.map +1 -0
  294. package/node_modules/rimraf/dist/esm/ignore-enoent.d.ts +3 -0
  295. package/node_modules/rimraf/dist/esm/ignore-enoent.d.ts.map +1 -0
  296. package/node_modules/rimraf/dist/esm/ignore-enoent.js +19 -0
  297. package/node_modules/rimraf/dist/esm/ignore-enoent.js.map +1 -0
  298. package/node_modules/rimraf/dist/esm/index.d.ts +50 -0
  299. package/node_modules/rimraf/dist/esm/index.d.ts.map +1 -0
  300. package/node_modules/rimraf/dist/esm/index.js +70 -0
  301. package/node_modules/rimraf/dist/esm/index.js.map +1 -0
  302. package/node_modules/rimraf/dist/esm/opt-arg.d.ts +34 -0
  303. package/node_modules/rimraf/dist/esm/opt-arg.d.ts.map +1 -0
  304. package/node_modules/rimraf/dist/esm/opt-arg.js +46 -0
  305. package/node_modules/rimraf/dist/esm/opt-arg.js.map +1 -0
  306. package/node_modules/rimraf/dist/esm/package.json +3 -0
  307. package/node_modules/rimraf/dist/esm/path-arg.d.ts +4 -0
  308. package/node_modules/rimraf/dist/esm/path-arg.d.ts.map +1 -0
  309. package/node_modules/rimraf/dist/esm/path-arg.js +46 -0
  310. package/node_modules/rimraf/dist/esm/path-arg.js.map +1 -0
  311. package/node_modules/rimraf/dist/esm/readdir-or-error.d.ts +3 -0
  312. package/node_modules/rimraf/dist/esm/readdir-or-error.d.ts.map +1 -0
  313. package/node_modules/rimraf/dist/esm/readdir-or-error.js +14 -0
  314. package/node_modules/rimraf/dist/esm/readdir-or-error.js.map +1 -0
  315. package/node_modules/rimraf/dist/esm/retry-busy.d.ts +8 -0
  316. package/node_modules/rimraf/dist/esm/retry-busy.d.ts.map +1 -0
  317. package/node_modules/rimraf/dist/esm/retry-busy.js +60 -0
  318. package/node_modules/rimraf/dist/esm/retry-busy.js.map +1 -0
  319. package/node_modules/rimraf/dist/esm/rimraf-manual.d.ts +3 -0
  320. package/node_modules/rimraf/dist/esm/rimraf-manual.d.ts.map +1 -0
  321. package/node_modules/rimraf/dist/esm/rimraf-manual.js +5 -0
  322. package/node_modules/rimraf/dist/esm/rimraf-manual.js.map +1 -0
  323. package/node_modules/rimraf/dist/esm/rimraf-move-remove.d.ts +4 -0
  324. package/node_modules/rimraf/dist/esm/rimraf-move-remove.d.ts.map +1 -0
  325. package/node_modules/rimraf/dist/esm/rimraf-move-remove.js +133 -0
  326. package/node_modules/rimraf/dist/esm/rimraf-move-remove.js.map +1 -0
  327. package/node_modules/rimraf/dist/esm/rimraf-native.d.ts +4 -0
  328. package/node_modules/rimraf/dist/esm/rimraf-native.d.ts.map +1 -0
  329. package/node_modules/rimraf/dist/esm/rimraf-native.js +19 -0
  330. package/node_modules/rimraf/dist/esm/rimraf-native.js.map +1 -0
  331. package/node_modules/rimraf/dist/esm/rimraf-posix.d.ts +4 -0
  332. package/node_modules/rimraf/dist/esm/rimraf-posix.d.ts.map +1 -0
  333. package/node_modules/rimraf/dist/esm/rimraf-posix.js +98 -0
  334. package/node_modules/rimraf/dist/esm/rimraf-posix.js.map +1 -0
  335. package/node_modules/rimraf/dist/esm/rimraf-windows.d.ts +4 -0
  336. package/node_modules/rimraf/dist/esm/rimraf-windows.d.ts.map +1 -0
  337. package/node_modules/rimraf/dist/esm/rimraf-windows.js +154 -0
  338. package/node_modules/rimraf/dist/esm/rimraf-windows.js.map +1 -0
  339. package/node_modules/rimraf/dist/esm/use-native.d.ts +4 -0
  340. package/node_modules/rimraf/dist/esm/use-native.d.ts.map +1 -0
  341. package/node_modules/rimraf/dist/esm/use-native.js +15 -0
  342. package/node_modules/rimraf/dist/esm/use-native.js.map +1 -0
  343. package/node_modules/rimraf/package.json +92 -0
  344. package/package.json +152 -0
  345. package/scripts/install-scanners.js +258 -0
  346. package/scripts/watchdog.js +147 -0
  347. package/src/analyzers/CSSAnalyzer.js +297 -0
  348. package/src/analyzers/ConfigValidator.js +690 -0
  349. package/src/analyzers/ESLintAnalyzer.js +320 -0
  350. package/src/analyzers/JavaScriptAnalyzer.js +261 -0
  351. package/src/analyzers/PrettierFormatter.js +247 -0
  352. package/src/analyzers/PythonAnalyzer.js +283 -0
  353. package/src/analyzers/SecurityAnalyzer.js +729 -0
  354. package/src/analyzers/SparrowAnalyzer.js +341 -0
  355. package/src/analyzers/TypeScriptAnalyzer.js +247 -0
  356. package/src/analyzers/codeCloneDetector/analyzer.js +344 -0
  357. package/src/analyzers/codeCloneDetector/detector.js +250 -0
  358. package/src/analyzers/codeCloneDetector/index.js +192 -0
  359. package/src/analyzers/codeCloneDetector/parser.js +199 -0
  360. package/src/analyzers/codeCloneDetector/reporter.js +148 -0
  361. package/src/analyzers/codeCloneDetector/scanner.js +88 -0
  362. package/src/core/agentPool.js +1957 -0
  363. package/src/core/agentScheduler.js +3212 -0
  364. package/src/core/contextManager.js +709 -0
  365. package/src/core/flowExecutor.js +928 -0
  366. package/src/core/messageProcessor.js +808 -0
  367. package/src/core/orchestrator.js +584 -0
  368. package/src/core/stateManager.js +1500 -0
  369. package/src/index.js +972 -0
  370. package/src/interfaces/cli.js +553 -0
  371. package/src/interfaces/terminal/__tests__/smoke/advancedFeatures.test.js +208 -0
  372. package/src/interfaces/terminal/__tests__/smoke/agentControl.test.js +236 -0
  373. package/src/interfaces/terminal/__tests__/smoke/agents.test.js +138 -0
  374. package/src/interfaces/terminal/__tests__/smoke/components.test.js +137 -0
  375. package/src/interfaces/terminal/__tests__/smoke/connection.test.js +350 -0
  376. package/src/interfaces/terminal/__tests__/smoke/enhancements.test.js +156 -0
  377. package/src/interfaces/terminal/__tests__/smoke/imports.test.js +332 -0
  378. package/src/interfaces/terminal/__tests__/smoke/messages.test.js +256 -0
  379. package/src/interfaces/terminal/__tests__/smoke/tools.test.js +388 -0
  380. package/src/interfaces/terminal/api/apiClient.js +299 -0
  381. package/src/interfaces/terminal/api/messageRouter.js +262 -0
  382. package/src/interfaces/terminal/api/session.js +266 -0
  383. package/src/interfaces/terminal/api/websocket.js +497 -0
  384. package/src/interfaces/terminal/components/AgentCreator.js +705 -0
  385. package/src/interfaces/terminal/components/AgentEditor.js +678 -0
  386. package/src/interfaces/terminal/components/AgentSwitcher.js +330 -0
  387. package/src/interfaces/terminal/components/ErrorBoundary.js +92 -0
  388. package/src/interfaces/terminal/components/ErrorPanel.js +264 -0
  389. package/src/interfaces/terminal/components/Header.js +28 -0
  390. package/src/interfaces/terminal/components/HelpPanel.js +231 -0
  391. package/src/interfaces/terminal/components/InputBox.js +118 -0
  392. package/src/interfaces/terminal/components/Layout.js +603 -0
  393. package/src/interfaces/terminal/components/LoadingSpinner.js +71 -0
  394. package/src/interfaces/terminal/components/MessageList.js +281 -0
  395. package/src/interfaces/terminal/components/MultilineTextInput.js +251 -0
  396. package/src/interfaces/terminal/components/SearchPanel.js +265 -0
  397. package/src/interfaces/terminal/components/SettingsPanel.js +415 -0
  398. package/src/interfaces/terminal/components/StatusBar.js +65 -0
  399. package/src/interfaces/terminal/components/TextInput.js +127 -0
  400. package/src/interfaces/terminal/config/agentEditorConstants.js +227 -0
  401. package/src/interfaces/terminal/config/constants.js +393 -0
  402. package/src/interfaces/terminal/index.js +168 -0
  403. package/src/interfaces/terminal/state/useAgentControl.js +496 -0
  404. package/src/interfaces/terminal/state/useAgents.js +537 -0
  405. package/src/interfaces/terminal/state/useConnection.js +444 -0
  406. package/src/interfaces/terminal/state/useMessages.js +630 -0
  407. package/src/interfaces/terminal/state/useTools.js +554 -0
  408. package/src/interfaces/terminal/utils/debugLogger.js +44 -0
  409. package/src/interfaces/terminal/utils/settingsStorage.js +232 -0
  410. package/src/interfaces/terminal/utils/theme.js +85 -0
  411. package/src/interfaces/webServer.js +5457 -0
  412. package/src/modules/fileExplorer/controller.js +413 -0
  413. package/src/modules/fileExplorer/index.js +37 -0
  414. package/src/modules/fileExplorer/middleware.js +92 -0
  415. package/src/modules/fileExplorer/routes.js +158 -0
  416. package/src/modules/fileExplorer/types.js +44 -0
  417. package/src/services/agentActivityService.js +399 -0
  418. package/src/services/aiService.js +2618 -0
  419. package/src/services/apiKeyManager.js +334 -0
  420. package/src/services/benchmarkService.js +196 -0
  421. package/src/services/budgetService.js +565 -0
  422. package/src/services/contextInjectionService.js +268 -0
  423. package/src/services/conversationCompactionService.js +1103 -0
  424. package/src/services/credentialVault.js +685 -0
  425. package/src/services/errorHandler.js +810 -0
  426. package/src/services/fileAttachmentService.js +547 -0
  427. package/src/services/flowContextService.js +189 -0
  428. package/src/services/memoryService.js +521 -0
  429. package/src/services/modelRouterService.js +365 -0
  430. package/src/services/modelsService.js +323 -0
  431. package/src/services/ollamaService.js +452 -0
  432. package/src/services/portRegistry.js +336 -0
  433. package/src/services/portTracker.js +223 -0
  434. package/src/services/projectDetector.js +404 -0
  435. package/src/services/promptService.js +372 -0
  436. package/src/services/qualityInspector.js +796 -0
  437. package/src/services/scheduleService.js +725 -0
  438. package/src/services/serviceRegistry.js +386 -0
  439. package/src/services/skillsService.js +486 -0
  440. package/src/services/telegramService.js +920 -0
  441. package/src/services/tokenCountingService.js +316 -0
  442. package/src/services/visualEditorBridge.js +1033 -0
  443. package/src/services/visualEditorServer.js +1727 -0
  444. package/src/services/whatsappService.js +663 -0
  445. package/src/tools/__tests__/webTool.e2e.test.js +569 -0
  446. package/src/tools/__tests__/webTool.unit.test.js +195 -0
  447. package/src/tools/agentCommunicationTool.js +1343 -0
  448. package/src/tools/agentDelayTool.js +498 -0
  449. package/src/tools/asyncToolManager.js +604 -0
  450. package/src/tools/baseTool.js +887 -0
  451. package/src/tools/browserTool.js +897 -0
  452. package/src/tools/cloneDetectionTool.js +581 -0
  453. package/src/tools/codeMapTool.js +857 -0
  454. package/src/tools/dependencyResolverTool.js +1212 -0
  455. package/src/tools/docxTool.js +623 -0
  456. package/src/tools/excelTool.js +636 -0
  457. package/src/tools/fileContentReplaceTool.js +840 -0
  458. package/src/tools/fileTreeTool.js +833 -0
  459. package/src/tools/filesystemTool.js +1217 -0
  460. package/src/tools/helpTool.js +198 -0
  461. package/src/tools/imageTool.js +1034 -0
  462. package/src/tools/importAnalyzerTool.js +1056 -0
  463. package/src/tools/jobDoneTool.js +388 -0
  464. package/src/tools/memoryTool.js +554 -0
  465. package/src/tools/pdfTool.js +627 -0
  466. package/src/tools/seekTool.js +883 -0
  467. package/src/tools/skillsTool.js +276 -0
  468. package/src/tools/staticAnalysisTool.js +2146 -0
  469. package/src/tools/taskManagerTool.js +2836 -0
  470. package/src/tools/terminalTool.js +2486 -0
  471. package/src/tools/userPromptTool.js +474 -0
  472. package/src/tools/videoTool.js +1139 -0
  473. package/src/tools/visionTool.js +507 -0
  474. package/src/tools/visualEditorTool.js +1175 -0
  475. package/src/tools/webTool.js +3114 -0
  476. package/src/tools/whatsappTool.js +457 -0
  477. package/src/types/agent.js +519 -0
  478. package/src/types/contextReference.js +972 -0
  479. package/src/types/conversation.js +730 -0
  480. package/src/types/toolCommand.js +747 -0
  481. package/src/utilities/attachmentValidator.js +288 -0
  482. package/src/utilities/browserStealth.js +630 -0
  483. package/src/utilities/configManager.js +618 -0
  484. package/src/utilities/constants.js +870 -0
  485. package/src/utilities/directoryAccessManager.js +566 -0
  486. package/src/utilities/fileProcessor.js +307 -0
  487. package/src/utilities/humanBehavior.js +453 -0
  488. package/src/utilities/jsonRepair.js +242 -0
  489. package/src/utilities/logger.js +436 -0
  490. package/src/utilities/platformUtils.js +255 -0
  491. package/src/utilities/platformUtils.test.js +98 -0
  492. package/src/utilities/stealthConstants.js +377 -0
  493. package/src/utilities/structuredFileValidator.js +699 -0
  494. package/src/utilities/tagParser.js +878 -0
  495. package/src/utilities/toolConstants.js +415 -0
  496. package/src/utilities/userDataDir.js +300 -0
  497. package/web-ui/build/brands/autopilot/favicon.svg +1 -0
  498. package/web-ui/build/brands/autopilot/logo.webp +0 -0
  499. package/web-ui/build/brands/onbuzz/favicon.svg +1 -0
  500. package/web-ui/build/brands/onbuzz/logo-text.webp +0 -0
  501. package/web-ui/build/brands/onbuzz/logo.webp +0 -0
  502. package/web-ui/build/index.html +15 -0
  503. package/web-ui/build/logo.png +0 -0
  504. package/web-ui/build/logo2.png +0 -0
  505. package/web-ui/build/static/index-SmQFfvBs.js +746 -0
  506. package/web-ui/build/static/index-V2ySwjHp.css +1 -0
@@ -0,0 +1,1727 @@
1
+ /**
2
+ * Visual Editor Server
3
+ *
4
+ * Runs on port 4000 and provides:
5
+ * - Health check endpoint
6
+ * - Editor HTML page for iframe embedding
7
+ * - Proxy to user's running app
8
+ * - WebSocket for backend bridge communication
9
+ * - Element picker overlay injection
10
+ */
11
+
12
+ import express from 'express';
13
+ import { createProxyMiddleware } from 'http-proxy-middleware';
14
+ import { WebSocketServer } from 'ws';
15
+ import path from 'path';
16
+ import { fileURLToPath } from 'url';
17
+ import { Transform } from 'stream';
18
+ import https from 'https';
19
+ import http from 'http';
20
+ import zlib from 'zlib';
21
+
22
+ // Import service registry for port allocation and registration
23
+ import registry, { findFreePort } from './serviceRegistry.js';
24
+
25
+ // Lazy getter for bridge to avoid circular dependency
26
+ // (visualEditorBridge imports from this file)
27
+ let bridgeGetter = null;
28
+ function setBridgeGetter(getter) {
29
+ bridgeGetter = getter;
30
+ }
31
+ function getBridge() {
32
+ if (!bridgeGetter) {
33
+ // Fallback: try dynamic import (async, only for initialization)
34
+ return null;
35
+ }
36
+ return bridgeGetter();
37
+ }
38
+
39
+ const __filename = fileURLToPath(import.meta.url);
40
+ const __dirname = path.dirname(__filename);
41
+
42
+ // Service name for registry
43
+ const SERVICE_NAME = 'visualEditor';
44
+
45
+ // Config manager reference (set when initialized from main app)
46
+ let configManagerRef = null;
47
+
48
+ /**
49
+ * Set the config manager reference for reading configuration
50
+ * @param {ConfigManager} configManager - The config manager instance
51
+ */
52
+ export function setConfigManager(configManager) {
53
+ configManagerRef = configManager;
54
+ }
55
+
56
+ /**
57
+ * Get configuration value with fallback chain:
58
+ * 1. Config manager (from config file)
59
+ * 2. Environment variable
60
+ * 3. Default value
61
+ */
62
+ function getConfigValue(configPath, envVar, defaultValue) {
63
+ // Try config manager first
64
+ if (configManagerRef) {
65
+ const configValue = configManagerRef.get(configPath);
66
+ if (configValue !== undefined) {
67
+ return configValue;
68
+ }
69
+ }
70
+
71
+ // Try environment variable
72
+ const envValue = process.env[envVar];
73
+ if (envValue !== undefined) {
74
+ // Parse numbers
75
+ if (typeof defaultValue === 'number') {
76
+ const parsed = parseInt(envValue, 10);
77
+ if (!isNaN(parsed)) return parsed;
78
+ }
79
+ return envValue;
80
+ }
81
+
82
+ // Return default
83
+ return defaultValue;
84
+ }
85
+
86
+ // Hard-coded fallback defaults (used only when nothing else is configured)
87
+ const FALLBACK_PORT = 4000;
88
+ const FALLBACK_APP_URL = 'http://localhost:3000';
89
+
90
+ /**
91
+ * Get the configured port (evaluated at runtime, not module load)
92
+ * @returns {number}
93
+ */
94
+ function getDefaultPort() {
95
+ return getConfigValue('visualEditor.port', 'LOXIA_VISUAL_EDITOR_PORT', FALLBACK_PORT);
96
+ }
97
+
98
+ /**
99
+ * Get the configured default app URL (evaluated at runtime)
100
+ * @returns {string}
101
+ */
102
+ function getDefaultAppUrl() {
103
+ return getConfigValue('visualEditor.defaultAppUrl', 'LOXIA_DEFAULT_APP_URL', FALLBACK_APP_URL);
104
+ }
105
+
106
+ /**
107
+ * Visual Editor Server class
108
+ */
109
+ class VisualEditorServer {
110
+ /**
111
+ * @param {Object} config - Configuration options
112
+ * @param {number} config.port - Server port (default: 4000)
113
+ * @param {Object} config.logger - Logger instance
114
+ */
115
+ constructor(config = {}) {
116
+ this.port = config.port || getDefaultPort();
117
+ this.logger = config.logger || console;
118
+ this.server = null;
119
+ this.wss = null;
120
+ this.app = null;
121
+ this.isRunning = false;
122
+
123
+ // Track active connections
124
+ this.wsConnections = new Map(); // agentId -> WebSocket
125
+ this.activeAppUrls = new Map(); // agentId -> appUrl
126
+ this.staticDirs = new Map(); // agentId -> directory path for static serving
127
+ }
128
+
129
+ /**
130
+ * Register a static directory to serve for an agent
131
+ * @param {string} agentId - Agent identifier
132
+ * @param {string} directory - Directory path to serve
133
+ */
134
+ registerStaticDir(agentId, directory) {
135
+ this.staticDirs.set(agentId, directory);
136
+ this.logger.info?.(`[VisualEditorServer] Registered static dir for ${agentId}: ${directory}`) ||
137
+ console.log(`[VisualEditorServer] Registered static dir for ${agentId}: ${directory}`);
138
+ }
139
+
140
+ /**
141
+ * Unregister a static directory
142
+ * @param {string} agentId - Agent identifier
143
+ */
144
+ unregisterStaticDir(agentId) {
145
+ this.staticDirs.delete(agentId);
146
+ }
147
+
148
+ /**
149
+ * Start the Visual Editor Server
150
+ * Uses findFreePort to handle port conflicts and registers with service registry
151
+ * @returns {Promise<Object>} Start result
152
+ */
153
+ async start() {
154
+ if (this.isRunning) {
155
+ return { success: true, port: this.port, message: 'Already running' };
156
+ }
157
+
158
+ // Find a free port starting from the preferred port
159
+ const preferredPort = this.port;
160
+ try {
161
+ const actualPort = await findFreePort(preferredPort);
162
+
163
+ if (actualPort !== preferredPort) {
164
+ this.logger.info?.(`[VisualEditorServer] Port ${preferredPort} taken, using ${actualPort}`) ||
165
+ console.log(`[VisualEditorServer] Port ${preferredPort} taken, using ${actualPort}`);
166
+ this.port = actualPort;
167
+ }
168
+ } catch (err) {
169
+ this.logger.error?.(`[VisualEditorServer] Could not find free port: ${err.message}`);
170
+ throw err;
171
+ }
172
+
173
+ this.app = express();
174
+ this._setupMiddleware();
175
+ this._setupRoutes();
176
+
177
+ return new Promise((resolve, reject) => {
178
+ this.server = this.app.listen(this.port, '0.0.0.0', () => {
179
+ this.isRunning = true;
180
+ this._setupWebSocketServer();
181
+
182
+ // Register with service registry
183
+ registry.register(SERVICE_NAME, {
184
+ port: this.port,
185
+ host: 'localhost',
186
+ protocol: 'http',
187
+ metadata: {
188
+ wsPath: '/ws',
189
+ startedAt: Date.now()
190
+ }
191
+ });
192
+
193
+ this.logger.info?.(`[VisualEditorServer] Running on port ${this.port}`) ||
194
+ console.log(`[VisualEditorServer] Running on port ${this.port}`);
195
+
196
+ resolve({ success: true, port: this.port });
197
+ });
198
+
199
+ this.server.on('error', (err) => {
200
+ // This shouldn't happen since we checked with findFreePort, but handle anyway
201
+ this.logger.error?.(`[VisualEditorServer] Server error: ${err.message}`);
202
+ reject(err);
203
+ });
204
+ });
205
+ }
206
+
207
+ /**
208
+ * Stop the Visual Editor Server
209
+ * @returns {Promise<void>}
210
+ */
211
+ async stop() {
212
+ if (!this.isRunning) return;
213
+
214
+ // Unregister from service registry
215
+ registry.unregister(SERVICE_NAME);
216
+
217
+ // Close all WebSocket connections
218
+ for (const [agentId, ws] of this.wsConnections.entries()) {
219
+ try {
220
+ ws.close(1000, 'Server shutting down');
221
+ } catch (err) {
222
+ // Ignore
223
+ }
224
+ }
225
+ this.wsConnections.clear();
226
+ this.activeAppUrls.clear();
227
+
228
+ // Close WebSocket server
229
+ if (this.wss) {
230
+ this.wss.close();
231
+ this.wss = null;
232
+ }
233
+
234
+ // Close HTTP server — force-close keep-alive connections
235
+ return new Promise((resolve) => {
236
+ if (this.server) {
237
+ if (typeof this.server.closeAllConnections === 'function') {
238
+ this.server.closeAllConnections();
239
+ }
240
+ this.server.close(() => {
241
+ this.isRunning = false;
242
+ this.server = null;
243
+ this.logger.info?.('[VisualEditorServer] Stopped') ||
244
+ console.log('[VisualEditorServer] Stopped');
245
+ resolve();
246
+ });
247
+ } else {
248
+ resolve();
249
+ }
250
+ });
251
+ }
252
+
253
+ /**
254
+ * Get server status
255
+ * @returns {Object} Status info
256
+ */
257
+ getStatus() {
258
+ return {
259
+ isRunning: this.isRunning,
260
+ port: this.port,
261
+ activeConnections: this.wsConnections.size,
262
+ connectedAgents: Array.from(this.wsConnections.keys())
263
+ };
264
+ }
265
+
266
+ /**
267
+ * Register an app URL for an agent
268
+ * @param {string} agentId - Agent identifier
269
+ * @param {string} appUrl - User's app URL
270
+ */
271
+ registerAppUrl(agentId, appUrl) {
272
+ this.activeAppUrls.set(agentId, appUrl);
273
+ }
274
+
275
+ /**
276
+ * Unregister an agent's app URL
277
+ * @param {string} agentId - Agent identifier
278
+ */
279
+ unregisterAppUrl(agentId) {
280
+ this.activeAppUrls.delete(agentId);
281
+ }
282
+
283
+ /**
284
+ * Send message to a specific agent's WebSocket
285
+ * @param {string} agentId - Agent identifier
286
+ * @param {Object} message - Message to send
287
+ * @returns {boolean} Success
288
+ */
289
+ sendToAgent(agentId, message) {
290
+ const ws = this.wsConnections.get(agentId);
291
+ if (ws && ws.readyState === 1) { // WebSocket.OPEN
292
+ try {
293
+ ws.send(JSON.stringify(message));
294
+ return true;
295
+ } catch (err) {
296
+ this.logger.error?.(`[VisualEditorServer] Failed to send to agent ${agentId}:`, err);
297
+ }
298
+ }
299
+ return false;
300
+ }
301
+
302
+ /**
303
+ * Set up Express middleware
304
+ * @private
305
+ */
306
+ _setupMiddleware() {
307
+ // Request logging for debugging
308
+ this.app.use((req, res, next) => {
309
+ this.logger.debug?.(`[VisualEditorServer] ${req.method} ${req.url}`);
310
+ next();
311
+ });
312
+
313
+ // CORS for cross-origin requests
314
+ this.app.use((req, res, next) => {
315
+ res.header('Access-Control-Allow-Origin', '*');
316
+ res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
317
+ res.header('Access-Control-Allow-Headers', 'Content-Type');
318
+ if (req.method === 'OPTIONS') {
319
+ return res.sendStatus(200);
320
+ }
321
+ next();
322
+ });
323
+
324
+ // Parse JSON bodies
325
+ this.app.use(express.json());
326
+ }
327
+
328
+ /**
329
+ * Set up Express routes
330
+ * @private
331
+ */
332
+ _setupRoutes() {
333
+ // Health check endpoint
334
+ this.app.get('/health', (req, res) => {
335
+ res.json({
336
+ status: 'ok',
337
+ timestamp: Date.now(),
338
+ connections: this.wsConnections.size
339
+ });
340
+ });
341
+
342
+ // Test proxy connectivity endpoint (for debugging)
343
+ this.app.get('/test-proxy', async (req, res) => {
344
+ const targetUrl = req.query.url || 'https://httpbin.org/html';
345
+ this.logger.info?.(`[VisualEditorServer] Testing connectivity to: ${targetUrl}`);
346
+
347
+ try {
348
+ const parsed = new URL(targetUrl);
349
+ const isHttps = parsed.protocol === 'https:';
350
+ const httpModule = isHttps ? https : http;
351
+
352
+ const result = await new Promise((resolve, reject) => {
353
+ const reqOptions = {
354
+ hostname: parsed.hostname,
355
+ port: parsed.port || (isHttps ? 443 : 80),
356
+ path: parsed.pathname + parsed.search,
357
+ method: 'HEAD', // Just check connectivity, don't download content
358
+ timeout: 10000,
359
+ headers: {
360
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0'
361
+ },
362
+ rejectUnauthorized: false // Allow self-signed certs
363
+ };
364
+
365
+ const request = httpModule.request(reqOptions, (response) => {
366
+ resolve({
367
+ success: true,
368
+ url: targetUrl,
369
+ status: response.statusCode,
370
+ statusText: response.statusMessage,
371
+ contentType: response.headers['content-type']
372
+ });
373
+ });
374
+
375
+ request.on('timeout', () => {
376
+ request.destroy();
377
+ reject(new Error('Connection timed out after 10s'));
378
+ });
379
+
380
+ request.on('error', reject);
381
+ request.end();
382
+ });
383
+
384
+ res.json(result);
385
+ } catch (err) {
386
+ this.logger.error?.(`[VisualEditorServer] Test connectivity failed:`, err.message);
387
+ res.json({
388
+ success: false,
389
+ url: targetUrl,
390
+ error: err.message,
391
+ code: err.code || 'UNKNOWN'
392
+ });
393
+ }
394
+ });
395
+
396
+ // Main editor page (served in iframe)
397
+ this.app.get('/', (req, res) => {
398
+ const { agentId, appUrl } = req.query;
399
+
400
+ if (!agentId) {
401
+ return res.status(400).send('Missing agentId parameter');
402
+ }
403
+
404
+ const targetUrl = appUrl || this.activeAppUrls.get(agentId) || getDefaultAppUrl();
405
+ const html = this._generateEditorHtml(agentId, targetUrl);
406
+ res.type('html').send(html);
407
+ });
408
+
409
+ // Serve overlay script
410
+ this.app.get('/overlay.js', (req, res) => {
411
+ const overlayScript = this._getOverlayScript();
412
+ res.type('application/javascript').send(overlayScript);
413
+ });
414
+
415
+ // Serve static files for agents (for static HTML projects)
416
+ this.app.use('/static/:agentId', (req, res, next) => {
417
+ const { agentId } = req.params;
418
+ const staticDir = this.staticDirs.get(agentId);
419
+
420
+ if (!staticDir) {
421
+ return res.status(404).json({
422
+ error: 'No static directory registered for this agent',
423
+ agentId
424
+ });
425
+ }
426
+
427
+ // Create static middleware for this directory
428
+ const staticMiddleware = express.static(staticDir, {
429
+ index: ['index.html', 'index.htm'],
430
+ extensions: ['html', 'htm']
431
+ });
432
+
433
+ // Inject overlay script into HTML files
434
+ const originalSend = res.send.bind(res);
435
+ res.send = (body) => {
436
+ if (typeof body === 'string' && body.includes('</body>')) {
437
+ // Inject overlay script before </body>
438
+ const overlayScript = `<script src="/overlay.js"></script>`;
439
+ body = body.replace('</body>', `${overlayScript}</body>`);
440
+ }
441
+ return originalSend(body);
442
+ };
443
+
444
+ staticMiddleware(req, res, next);
445
+ });
446
+
447
+ // Proxy to user's app with overlay injection
448
+ // Wrap in error handler to catch any proxy initialization errors
449
+ const proxyMiddleware = this._createProxyMiddleware();
450
+ this.app.use('/app', (req, res, next) => {
451
+ try {
452
+ proxyMiddleware(req, res, next);
453
+ } catch (err) {
454
+ this.logger.error?.(`[VisualEditorServer] Proxy middleware error:`, err.message);
455
+ res.status(502).type('html').send(this._generateErrorHtml(
456
+ req.query.target || 'unknown',
457
+ `Proxy error: ${err.message}`
458
+ ));
459
+ }
460
+ });
461
+
462
+ // 404 catch-all - log and return helpful message
463
+ this.app.use((req, res) => {
464
+ this.logger.warn?.(`[VisualEditorServer] 404: ${req.method} ${req.url}`);
465
+ res.status(404).json({
466
+ error: 'Not found',
467
+ path: req.url,
468
+ hint: 'Use /app?target=URL to proxy to a website'
469
+ });
470
+ });
471
+
472
+ // Express error handler - catches uncaught errors
473
+ this.app.use((err, req, res, next) => {
474
+ this.logger.error?.(`[VisualEditorServer] Express error:`, err.message);
475
+ if (!res.headersSent) {
476
+ res.status(500).type('html').send(this._generateErrorHtml(
477
+ req.query?.target || req.url,
478
+ `Server error: ${err.message}`
479
+ ));
480
+ }
481
+ });
482
+ }
483
+
484
+ /**
485
+ * Create proxy middleware for user's app
486
+ * Uses router option for dynamic target selection based on query param
487
+ * @private
488
+ */
489
+ _createProxyMiddleware() {
490
+ const self = this;
491
+
492
+ // Store current target URL for use in callbacks
493
+ let currentTargetUrl = getDefaultAppUrl();
494
+
495
+ // http-proxy-middleware v3.x uses 'on' property for event handlers
496
+ return createProxyMiddleware({
497
+ // Use router for dynamic target based on query parameter
498
+ router: (req) => {
499
+ const targetUrl = req.query.target || getDefaultAppUrl();
500
+ currentTargetUrl = targetUrl; // Store for use in callbacks
501
+
502
+ // Validate URL
503
+ try {
504
+ const parsed = new URL(targetUrl);
505
+ self.logger.info?.(`[VisualEditorServer] Proxying to: ${parsed.origin}`);
506
+ return parsed.origin; // Return just the origin (protocol + host + port)
507
+ } catch (err) {
508
+ self.logger.error?.(`[VisualEditorServer] Invalid target URL: ${targetUrl}`);
509
+ return getDefaultAppUrl(); // Fallback to default
510
+ }
511
+ },
512
+ changeOrigin: true,
513
+ selfHandleResponse: true, // We'll handle response to inject script
514
+ secure: false, // Don't validate SSL certificates (needed for dev servers)
515
+ followRedirects: true, // Follow redirects
516
+ proxyTimeout: 30000, // 30 second proxy timeout
517
+ timeout: 30000, // 30 second request timeout
518
+ pathRewrite: (path, req) => {
519
+ // Get the path from the target URL and append request path
520
+ const targetUrl = req.query.target || getDefaultAppUrl();
521
+ try {
522
+ const parsed = new URL(targetUrl);
523
+ // Start with the path from target URL
524
+ let newPath = parsed.pathname;
525
+ if (newPath === '/') newPath = '';
526
+
527
+ // Parse current request path and remove /app and query params
528
+ const reqUrl = new URL(path, 'http://localhost');
529
+ reqUrl.searchParams.delete('target');
530
+ const reqPath = reqUrl.pathname.replace(/^\/app\/?/, '/');
531
+
532
+ // Combine paths (avoid double slashes)
533
+ const finalPath = newPath + (reqPath === '/' ? '' : reqPath) + reqUrl.search;
534
+ self.logger.debug?.(`[VisualEditorServer] Path rewrite: ${path} -> ${finalPath || '/'}`);
535
+ return finalPath || '/';
536
+ } catch (e) {
537
+ return '/';
538
+ }
539
+ },
540
+ // v3.x event handlers using 'on' property
541
+ on: {
542
+ proxyReq: (proxyReq, req, res) => {
543
+ const targetUrl = req.query.target || getDefaultAppUrl();
544
+ self.logger.info?.(`[VisualEditorServer] Proxy request to: ${targetUrl}`);
545
+
546
+ // Set browser-like headers to avoid being blocked
547
+ // Wrap in try-catch because headers might already be sent on redirects
548
+ try {
549
+ if (!proxyReq.headersSent) {
550
+ proxyReq.setHeader('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
551
+ proxyReq.setHeader('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8');
552
+ proxyReq.setHeader('Accept-Language', 'en-US,en;q=0.9');
553
+ // IMPORTANT: Request uncompressed content - we need to read/modify HTML
554
+ // If we request gzip, we'd need to decompress before injecting the overlay script
555
+ proxyReq.setHeader('Accept-Encoding', 'identity');
556
+ // Remove headers that might cause issues
557
+ proxyReq.removeHeader('x-forwarded-for');
558
+ proxyReq.removeHeader('x-forwarded-host');
559
+ proxyReq.removeHeader('x-forwarded-proto');
560
+ }
561
+ } catch (e) {
562
+ // Headers already sent (e.g., during redirect) - ignore
563
+ self.logger.debug?.(`[VisualEditorServer] Could not set headers: ${e.message}`);
564
+ }
565
+ },
566
+ proxyRes: (proxyRes, req, res) => {
567
+ const targetUrl = req.query.target || currentTargetUrl;
568
+ self._handleProxyResponse(proxyRes, req, res, targetUrl);
569
+ },
570
+ error: (err, req, res) => {
571
+ try {
572
+ const targetUrl = req?.query?.target || currentTargetUrl || 'unknown';
573
+
574
+ // Log detailed error information
575
+ self.logger.error?.('[VisualEditorServer] Proxy error:', {
576
+ message: err?.message,
577
+ code: err?.code,
578
+ target: targetUrl,
579
+ url: req?.url
580
+ });
581
+
582
+ // Provide more helpful error messages based on error type
583
+ let errorMessage = err?.message || 'Unknown proxy error';
584
+ if (err?.code === 'ECONNREFUSED') {
585
+ errorMessage = `Connection refused - the server at ${targetUrl} is not running or not accepting connections`;
586
+ } else if (err?.code === 'ENOTFOUND') {
587
+ errorMessage = `DNS lookup failed - could not resolve hostname for ${targetUrl}`;
588
+ } else if (err?.code === 'ETIMEDOUT' || err?.code === 'ESOCKETTIMEDOUT') {
589
+ errorMessage = `Connection timed out - the server at ${targetUrl} took too long to respond (30s limit)`;
590
+ } else if (err?.code === 'CERT_HAS_EXPIRED' || err?.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
591
+ errorMessage = `SSL certificate error for ${targetUrl}. This may be a self-signed certificate issue.`;
592
+ } else if (err?.code === 'ECONNRESET') {
593
+ errorMessage = `Connection was reset by the server at ${targetUrl}`;
594
+ } else if (err?.code === 'HPE_INVALID_CONSTANT') {
595
+ errorMessage = `Invalid response from ${targetUrl} - the server may not be an HTTP server`;
596
+ }
597
+
598
+ // Return a user-friendly HTML error page
599
+ if (res && !res.headersSent) {
600
+ res.writeHead(502, { 'Content-Type': 'text/html' });
601
+ res.end(self._generateErrorHtml(targetUrl, errorMessage));
602
+ }
603
+ } catch (handlerErr) {
604
+ self.logger.error?.('[VisualEditorServer] Error in error handler:', handlerErr);
605
+ // Last resort - try to send a simple error
606
+ try {
607
+ if (res && !res.headersSent) {
608
+ res.writeHead(502, { 'Content-Type': 'text/plain' });
609
+ res.end('Proxy error: ' + (err?.message || 'Unknown error'));
610
+ }
611
+ } catch (e) {
612
+ // Nothing more we can do
613
+ }
614
+ }
615
+ }
616
+ }
617
+ });
618
+ }
619
+
620
+ /**
621
+ * Generate error HTML page for proxy failures
622
+ * @private
623
+ */
624
+ _generateErrorHtml(targetUrl, errorDetails) {
625
+ return `<!DOCTYPE html>
626
+ <html>
627
+ <head>
628
+ <title>Connection Error - Visual Editor</title>
629
+ <style>
630
+ * { margin: 0; padding: 0; box-sizing: border-box; }
631
+ body {
632
+ font-family: system-ui, -apple-system, sans-serif;
633
+ background: #f3f4f6;
634
+ min-height: 100vh;
635
+ display: flex;
636
+ align-items: center;
637
+ justify-content: center;
638
+ padding: 20px;
639
+ }
640
+ .error-container {
641
+ background: white;
642
+ border-radius: 12px;
643
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
644
+ padding: 32px;
645
+ max-width: 500px;
646
+ text-align: center;
647
+ }
648
+ .error-icon {
649
+ width: 64px;
650
+ height: 64px;
651
+ margin: 0 auto 16px;
652
+ background: #fef2f2;
653
+ border-radius: 50%;
654
+ display: flex;
655
+ align-items: center;
656
+ justify-content: center;
657
+ }
658
+ .error-icon svg {
659
+ width: 32px;
660
+ height: 32px;
661
+ color: #ef4444;
662
+ }
663
+ h1 {
664
+ color: #1f2937;
665
+ font-size: 20px;
666
+ margin-bottom: 8px;
667
+ }
668
+ .target-url {
669
+ color: #3b82f6;
670
+ font-family: monospace;
671
+ background: #eff6ff;
672
+ padding: 8px 12px;
673
+ border-radius: 6px;
674
+ margin: 16px 0;
675
+ word-break: break-all;
676
+ }
677
+ .instructions {
678
+ color: #6b7280;
679
+ font-size: 14px;
680
+ line-height: 1.6;
681
+ margin-top: 16px;
682
+ }
683
+ .instructions ol {
684
+ text-align: left;
685
+ padding-left: 20px;
686
+ margin-top: 12px;
687
+ }
688
+ .instructions li {
689
+ margin-bottom: 8px;
690
+ }
691
+ .retry-btn {
692
+ margin-top: 20px;
693
+ padding: 10px 24px;
694
+ background: #3b82f6;
695
+ color: white;
696
+ border: none;
697
+ border-radius: 6px;
698
+ font-size: 14px;
699
+ cursor: pointer;
700
+ transition: background 0.2s;
701
+ }
702
+ .retry-btn:hover {
703
+ background: #2563eb;
704
+ }
705
+ .error-details {
706
+ margin-top: 16px;
707
+ padding: 12px;
708
+ background: #fef2f2;
709
+ border-radius: 6px;
710
+ color: #991b1b;
711
+ font-size: 12px;
712
+ font-family: monospace;
713
+ }
714
+ </style>
715
+ </head>
716
+ <body>
717
+ <div class="error-container">
718
+ <div class="error-icon">
719
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
720
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
721
+ </svg>
722
+ </div>
723
+ <h1>Cannot Connect to Your App</h1>
724
+ <div class="target-url">${targetUrl}</div>
725
+ <div class="instructions">
726
+ <p>Make sure your app is running at this address.</p>
727
+ <ol>
728
+ <li>Start your development server (e.g., <code>npm run dev</code>)</li>
729
+ <li>Enter the correct URL in the address bar above</li>
730
+ <li>Click "Go" or retry below</li>
731
+ </ol>
732
+ </div>
733
+ <button class="retry-btn" onclick="location.reload()">Retry Connection</button>
734
+ <div class="error-details">${errorDetails}</div>
735
+ </div>
736
+ </body>
737
+ </html>`;
738
+ }
739
+
740
+ /**
741
+ * Handle proxy response - inject overlay script into HTML
742
+ * @private
743
+ */
744
+ _handleProxyResponse(proxyRes, req, res, targetUrl) {
745
+ const contentType = proxyRes.headers['content-type'] || '';
746
+ const contentEncoding = proxyRes.headers['content-encoding'] || '';
747
+
748
+ // Copy headers (skip content-length and content-encoding as we'll modify content)
749
+ Object.keys(proxyRes.headers).forEach(key => {
750
+ const lowerKey = key.toLowerCase();
751
+ if (lowerKey !== 'content-length' && lowerKey !== 'content-encoding') {
752
+ res.setHeader(key, proxyRes.headers[key]);
753
+ }
754
+ });
755
+
756
+ res.status(proxyRes.statusCode);
757
+
758
+ // Only inject into HTML responses
759
+ if (contentType.includes('text/html')) {
760
+ const chunks = [];
761
+
762
+ // Collect all data chunks
763
+ proxyRes.on('data', (chunk) => {
764
+ chunks.push(chunk);
765
+ });
766
+
767
+ proxyRes.on('end', () => {
768
+ // Combine chunks into a single buffer
769
+ const buffer = Buffer.concat(chunks);
770
+
771
+ // Decompress if needed (some servers ignore Accept-Encoding: identity)
772
+ this._decompressBuffer(buffer, contentEncoding)
773
+ .then(decompressed => {
774
+ const body = decompressed.toString('utf-8');
775
+ // Inject overlay script before </body>
776
+ const injectedHtml = this._injectOverlayScript(body, targetUrl);
777
+ res.send(injectedHtml);
778
+ })
779
+ .catch(err => {
780
+ this.logger.error?.(`[VisualEditorServer] Decompression error: ${err.message}`);
781
+ // Try to send as-is (might be uncompressed despite header)
782
+ try {
783
+ const body = buffer.toString('utf-8');
784
+ const injectedHtml = this._injectOverlayScript(body, targetUrl);
785
+ res.send(injectedHtml);
786
+ } catch (e) {
787
+ res.status(500).send('Error processing response');
788
+ }
789
+ });
790
+ });
791
+ } else {
792
+ // Pass through non-HTML responses
793
+ proxyRes.pipe(res);
794
+ }
795
+ }
796
+
797
+ /**
798
+ * Decompress buffer based on content-encoding
799
+ * @private
800
+ */
801
+ async _decompressBuffer(buffer, encoding) {
802
+ if (!encoding || encoding === 'identity') {
803
+ return buffer;
804
+ }
805
+
806
+ return new Promise((resolve, reject) => {
807
+ if (encoding === 'gzip') {
808
+ zlib.gunzip(buffer, (err, result) => {
809
+ if (err) reject(err);
810
+ else resolve(result);
811
+ });
812
+ } else if (encoding === 'deflate') {
813
+ zlib.inflate(buffer, (err, result) => {
814
+ if (err) reject(err);
815
+ else resolve(result);
816
+ });
817
+ } else if (encoding === 'br') {
818
+ zlib.brotliDecompress(buffer, (err, result) => {
819
+ if (err) reject(err);
820
+ else resolve(result);
821
+ });
822
+ } else {
823
+ // Unknown encoding, try to use as-is
824
+ resolve(buffer);
825
+ }
826
+ });
827
+ }
828
+
829
+ /**
830
+ * Inject overlay script into HTML
831
+ * @private
832
+ */
833
+ _injectOverlayScript(html, targetUrl) {
834
+ // Use ABSOLUTE URL for overlay.js since we inject a <base> tag that would redirect relative paths
835
+ const overlayUrl = `http://localhost:${this.port}/overlay.js`;
836
+ const scriptTag = `
837
+ <!-- Loxia Visual Editor Overlay -->
838
+ <script src="${overlayUrl}"></script>
839
+ `;
840
+
841
+ // Add a <base> tag to make relative URLs resolve to the original site
842
+ // This prevents assets (scripts, styles, images) from being requested through our server
843
+ let baseTag = '';
844
+ try {
845
+ const parsed = new URL(targetUrl);
846
+ baseTag = `<base href="${parsed.origin}/">`;
847
+ } catch (e) {
848
+ // Invalid URL, skip base tag
849
+ }
850
+
851
+ let modifiedHtml = html;
852
+
853
+ // Inject base tag after <head> (if not already present)
854
+ if (baseTag && !html.toLowerCase().includes('<base ')) {
855
+ if (html.includes('<head>')) {
856
+ modifiedHtml = modifiedHtml.replace('<head>', `<head>${baseTag}`);
857
+ } else if (html.includes('<HEAD>')) {
858
+ modifiedHtml = modifiedHtml.replace('<HEAD>', `<HEAD>${baseTag}`);
859
+ }
860
+ }
861
+
862
+ // Inject overlay script before </body> or at end
863
+ if (modifiedHtml.includes('</body>')) {
864
+ return modifiedHtml.replace('</body>', `${scriptTag}</body>`);
865
+ } else if (modifiedHtml.includes('</html>')) {
866
+ return modifiedHtml.replace('</html>', `${scriptTag}</html>`);
867
+ } else {
868
+ return modifiedHtml + scriptTag;
869
+ }
870
+ }
871
+
872
+ /**
873
+ * Set up WebSocket server for backend bridge communication
874
+ * @private
875
+ */
876
+ _setupWebSocketServer() {
877
+ this.wss = new WebSocketServer({
878
+ server: this.server,
879
+ path: '/ws'
880
+ });
881
+
882
+ this.wss.on('connection', (ws, req) => {
883
+ const url = new URL(req.url, `http://localhost:${this.port}`);
884
+ const agentId = url.searchParams.get('agentId');
885
+
886
+ if (!agentId) {
887
+ ws.close(1008, 'Missing agentId');
888
+ return;
889
+ }
890
+
891
+ this.logger.info?.(`[VisualEditorServer] WebSocket connected: ${agentId}`);
892
+
893
+ // Store connection
894
+ this.wsConnections.set(agentId, ws);
895
+
896
+ // Send ready message
897
+ ws.send(JSON.stringify({
898
+ type: 'editor-ready',
899
+ agentId,
900
+ timestamp: Date.now()
901
+ }));
902
+
903
+ // Handle incoming messages
904
+ ws.on('message', (data) => {
905
+ this._handleWebSocketMessage(agentId, data, ws);
906
+ });
907
+
908
+ // Handle close
909
+ ws.on('close', (code, reason) => {
910
+ this.logger.info?.(`[VisualEditorServer] WebSocket closed: ${agentId} (${code})`);
911
+ this.wsConnections.delete(agentId);
912
+ });
913
+
914
+ // Handle errors
915
+ ws.on('error', (err) => {
916
+ this.logger.error?.(`[VisualEditorServer] WebSocket error for ${agentId}:`, err);
917
+ });
918
+ });
919
+ }
920
+
921
+ /**
922
+ * Handle incoming WebSocket messages
923
+ * @private
924
+ */
925
+ _handleWebSocketMessage(agentId, data, ws) {
926
+ try {
927
+ const message = JSON.parse(data.toString());
928
+
929
+ this.logger.debug?.(`[VisualEditorServer] Message from ${agentId}:`, message.type);
930
+
931
+ switch (message.type) {
932
+ case 'element-selected':
933
+ // Forward element selection (already handled by postMessage to web-ui)
934
+ // This is for backend bridge to receive selections
935
+ this._emitElementSelected(agentId, message);
936
+ break;
937
+
938
+ case 'ping':
939
+ ws.send(JSON.stringify({ type: 'pong', timestamp: Date.now() }));
940
+ break;
941
+
942
+ case 'highlight':
943
+ case 'scroll-to':
944
+ case 'reload':
945
+ // These commands come from backend, forward to any connected editor pages
946
+ // (handled via the editor page's WebSocket connection)
947
+ break;
948
+
949
+ case 'subscribe':
950
+ // Agent subscribing to editor events
951
+ if (message.appUrl) {
952
+ this.activeAppUrls.set(agentId, message.appUrl);
953
+ }
954
+ break;
955
+
956
+ case 'unsubscribe':
957
+ this.activeAppUrls.delete(agentId);
958
+ break;
959
+
960
+ default:
961
+ this.logger.debug?.(`[VisualEditorServer] Unknown message type: ${message.type}`);
962
+ }
963
+ } catch (err) {
964
+ this.logger.error?.('[VisualEditorServer] Invalid WebSocket message:', err);
965
+ }
966
+ }
967
+
968
+ /**
969
+ * Emit element selected event (for external listeners)
970
+ * @private
971
+ */
972
+ _emitElementSelected(agentId, message) {
973
+ const elementData = message.data || message;
974
+
975
+ this.logger.info?.(`[VisualEditorServer] Element selected for ${agentId}:`, {
976
+ selector: elementData.selector,
977
+ component: elementData.sourceHint?.component
978
+ });
979
+
980
+ // Forward to visualEditorBridge so context is available for message injection
981
+ const bridge = getBridge();
982
+ if (bridge && bridge.hasInstance(agentId)) {
983
+ const success = bridge.setVisualContext(agentId, {
984
+ selector: elementData.selector,
985
+ tagName: elementData.tagName,
986
+ text: elementData.text,
987
+ attributes: elementData.attributes,
988
+ boundingRect: elementData.boundingRect,
989
+ sourceHint: elementData.sourceHint,
990
+ computedStyle: elementData.computedStyle
991
+ });
992
+
993
+ if (success) {
994
+ this.logger.info?.(`[VisualEditorServer] Visual context synced to bridge for ${agentId}`);
995
+ } else {
996
+ this.logger.warn?.(`[VisualEditorServer] Failed to sync visual context for ${agentId}`);
997
+ }
998
+ } else {
999
+ this.logger.debug?.(`[VisualEditorServer] No bridge instance for ${agentId}, context not synced`);
1000
+ }
1001
+ }
1002
+
1003
+ /**
1004
+ * Generate editor HTML page
1005
+ * @private
1006
+ */
1007
+ _generateEditorHtml(agentId, appUrl) {
1008
+ const encodedAppUrl = encodeURIComponent(appUrl);
1009
+
1010
+ return `<!DOCTYPE html>
1011
+ <html>
1012
+ <head>
1013
+ <meta charset="UTF-8">
1014
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1015
+ <title>Loxia Visual Editor</title>
1016
+ <style>
1017
+ * { margin: 0; padding: 0; box-sizing: border-box; }
1018
+ html, body { height: 100%; overflow: hidden; }
1019
+ body { font-family: system-ui, -apple-system, sans-serif; }
1020
+
1021
+ #app-frame {
1022
+ width: 100%;
1023
+ height: 100%;
1024
+ border: none;
1025
+ }
1026
+
1027
+ #loading-overlay {
1028
+ position: fixed;
1029
+ top: 0;
1030
+ left: 0;
1031
+ right: 0;
1032
+ bottom: 0;
1033
+ background: #f3f4f6;
1034
+ display: flex;
1035
+ flex-direction: column;
1036
+ align-items: center;
1037
+ justify-content: center;
1038
+ z-index: 1000;
1039
+ transition: opacity 0.3s ease;
1040
+ }
1041
+
1042
+ #loading-overlay.hidden {
1043
+ opacity: 0;
1044
+ pointer-events: none;
1045
+ }
1046
+
1047
+ .spinner {
1048
+ width: 40px;
1049
+ height: 40px;
1050
+ border: 3px solid #e5e7eb;
1051
+ border-top-color: #3b82f6;
1052
+ border-radius: 50%;
1053
+ animation: spin 1s linear infinite;
1054
+ }
1055
+
1056
+ @keyframes spin {
1057
+ to { transform: rotate(360deg); }
1058
+ }
1059
+
1060
+ .loading-text {
1061
+ margin-top: 16px;
1062
+ color: #6b7280;
1063
+ font-size: 14px;
1064
+ }
1065
+
1066
+ #error-message {
1067
+ display: none;
1068
+ position: fixed;
1069
+ top: 50%;
1070
+ left: 50%;
1071
+ transform: translate(-50%, -50%);
1072
+ background: white;
1073
+ padding: 24px;
1074
+ border-radius: 8px;
1075
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
1076
+ text-align: center;
1077
+ max-width: 400px;
1078
+ }
1079
+
1080
+ #error-message h3 {
1081
+ color: #ef4444;
1082
+ margin-bottom: 8px;
1083
+ }
1084
+
1085
+ #error-message p {
1086
+ color: #6b7280;
1087
+ font-size: 14px;
1088
+ }
1089
+ </style>
1090
+ </head>
1091
+ <body>
1092
+ <div id="loading-overlay">
1093
+ <div class="spinner"></div>
1094
+ <p class="loading-text">Loading preview...</p>
1095
+ </div>
1096
+
1097
+ <div id="error-message">
1098
+ <h3>Connection Error</h3>
1099
+ <p id="error-text">Could not load the preview.</p>
1100
+ </div>
1101
+
1102
+ <iframe
1103
+ id="app-frame"
1104
+ src="/app?target=${encodedAppUrl}"
1105
+ sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-modals"
1106
+ ></iframe>
1107
+
1108
+ <script>
1109
+ const agentId = '${agentId}';
1110
+ const appUrl = '${appUrl}';
1111
+
1112
+ // Hide loading overlay when iframe loads
1113
+ const iframe = document.getElementById('app-frame');
1114
+ const loadingOverlay = document.getElementById('loading-overlay');
1115
+ const errorMessage = document.getElementById('error-message');
1116
+ const errorText = document.getElementById('error-text');
1117
+
1118
+ iframe.onload = () => {
1119
+ loadingOverlay.classList.add('hidden');
1120
+
1121
+ // Check if the iframe loaded our error page (proxy failure)
1122
+ try {
1123
+ const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
1124
+ const title = iframeDoc.title || '';
1125
+ if (title.includes('Connection Error') || title.includes('Cannot Connect')) {
1126
+ // Proxy returned an error page - show error message
1127
+ errorText.textContent = 'Could not proxy to ' + appUrl + '. See the error details in the preview.';
1128
+ errorMessage.style.display = 'block';
1129
+ }
1130
+ } catch (e) {
1131
+ // Cross-origin - can't check, but that usually means it loaded successfully
1132
+ console.log('[Visual Editor] Cross-origin iframe loaded - assuming success');
1133
+ }
1134
+ };
1135
+
1136
+ iframe.onerror = () => {
1137
+ loadingOverlay.classList.add('hidden');
1138
+ errorText.textContent = 'Could not connect to ' + appUrl + '. Make sure your app is running.';
1139
+ errorMessage.style.display = 'block';
1140
+ };
1141
+
1142
+ // Forward messages from app iframe to parent (Loxia Web-UI)
1143
+ window.addEventListener('message', (e) => {
1144
+ // Only forward element-selected messages
1145
+ if (e.data && e.data.type === 'element-selected') {
1146
+ // Forward to parent window (Loxia Web-UI)
1147
+ window.parent.postMessage(e.data, '*');
1148
+
1149
+ // Also send via WebSocket to backend
1150
+ if (window.wsConnection && window.wsConnection.readyState === 1) {
1151
+ window.wsConnection.send(JSON.stringify({
1152
+ ...e.data,
1153
+ agentId
1154
+ }));
1155
+ }
1156
+ }
1157
+ });
1158
+
1159
+ // Listen for commands from parent (Loxia Web-UI) or WebSocket
1160
+ window.addEventListener('message', (e) => {
1161
+ if (e.data && (e.data.type === 'highlight' || e.data.type === 'scroll-to')) {
1162
+ // Forward to app iframe
1163
+ iframe.contentWindow.postMessage(e.data, '*');
1164
+ }
1165
+ });
1166
+
1167
+ // Connect to WebSocket for backend communication
1168
+ const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
1169
+ const wsUrl = wsProtocol + '//' + location.host + '/ws?agentId=' + agentId;
1170
+
1171
+ function connectWebSocket() {
1172
+ window.wsConnection = new WebSocket(wsUrl);
1173
+
1174
+ window.wsConnection.onopen = () => {
1175
+ console.log('[Visual Editor] WebSocket connected');
1176
+ // Notify parent that editor is ready
1177
+ window.parent.postMessage({ type: 'editor-ready' }, '*');
1178
+ };
1179
+
1180
+ window.wsConnection.onmessage = (e) => {
1181
+ try {
1182
+ const msg = JSON.parse(e.data);
1183
+
1184
+ // Forward commands to app iframe
1185
+ if (msg.type === 'highlight' || msg.type === 'scroll-to' || msg.type === 'reload') {
1186
+ iframe.contentWindow.postMessage(msg, '*');
1187
+ }
1188
+ } catch (err) {
1189
+ console.error('[Visual Editor] Invalid message:', err);
1190
+ }
1191
+ };
1192
+
1193
+ window.wsConnection.onerror = (e) => {
1194
+ console.error('[Visual Editor] WebSocket error');
1195
+ window.parent.postMessage({
1196
+ type: 'editor-error',
1197
+ data: { message: 'WebSocket connection error' }
1198
+ }, '*');
1199
+ };
1200
+
1201
+ window.wsConnection.onclose = () => {
1202
+ console.log('[Visual Editor] WebSocket closed, reconnecting in 3s...');
1203
+ setTimeout(connectWebSocket, 3000);
1204
+ };
1205
+ }
1206
+
1207
+ connectWebSocket();
1208
+
1209
+ // Handle iframe load timeout (35s to allow proxy's 30s timeout to report actual error)
1210
+ setTimeout(() => {
1211
+ if (!loadingOverlay.classList.contains('hidden')) {
1212
+ loadingOverlay.classList.add('hidden');
1213
+ errorText.textContent = 'Preview is taking too long to load. Check if ' + appUrl + ' is accessible and responding.';
1214
+ errorMessage.style.display = 'block';
1215
+ }
1216
+ }, 35000);
1217
+ </script>
1218
+ </body>
1219
+ </html>`;
1220
+ }
1221
+
1222
+ /**
1223
+ * Get overlay script for element selection
1224
+ * @private
1225
+ */
1226
+ _getOverlayScript() {
1227
+ return `/**
1228
+ * Loxia Visual Editor Overlay Script
1229
+ * Injected into user's app for element selection
1230
+ */
1231
+ (function() {
1232
+ 'use strict';
1233
+
1234
+ // Prevent double injection
1235
+ if (window.__LOXIA_VISUAL_EDITOR_LOADED__) return;
1236
+ window.__LOXIA_VISUAL_EDITOR_LOADED__ = true;
1237
+
1238
+ let isEnabled = true;
1239
+ let hoveredElement = null;
1240
+ let selectedElement = null;
1241
+ let highlightOverlay = null;
1242
+ let selectionOverlay = null;
1243
+ let tooltip = null;
1244
+
1245
+ // Apply cursor style based on mode
1246
+ function updateCursorStyle(enabled) {
1247
+ document.body.style.cursor = enabled ? 'crosshair' : '';
1248
+ }
1249
+ updateCursorStyle(true);
1250
+
1251
+ // Create highlight overlay element (hover)
1252
+ function createHighlightOverlay() {
1253
+ const overlay = document.createElement('div');
1254
+ overlay.id = 'loxia-highlight-overlay';
1255
+ overlay.style.cssText = \`
1256
+ position: fixed;
1257
+ pointer-events: none;
1258
+ background: rgba(59, 130, 246, 0.1);
1259
+ border: 2px solid #3b82f6;
1260
+ border-radius: 4px;
1261
+ z-index: 999998;
1262
+ transition: all 0.1s ease;
1263
+ display: none;
1264
+ \`;
1265
+ document.body.appendChild(overlay);
1266
+ return overlay;
1267
+ }
1268
+
1269
+ // Create selection overlay element (click)
1270
+ function createSelectionOverlay() {
1271
+ const overlay = document.createElement('div');
1272
+ overlay.id = 'loxia-selection-overlay';
1273
+ overlay.style.cssText = \`
1274
+ position: fixed;
1275
+ pointer-events: none;
1276
+ background: rgba(34, 197, 94, 0.15);
1277
+ border: 2px solid #22c55e;
1278
+ border-radius: 4px;
1279
+ z-index: 999999;
1280
+ display: none;
1281
+ \`;
1282
+ document.body.appendChild(overlay);
1283
+ return overlay;
1284
+ }
1285
+
1286
+ // Create tooltip for element info
1287
+ function createTooltip() {
1288
+ const tip = document.createElement('div');
1289
+ tip.id = 'loxia-tooltip';
1290
+ tip.style.cssText = \`
1291
+ position: fixed;
1292
+ background: #1f2937;
1293
+ color: white;
1294
+ padding: 4px 8px;
1295
+ border-radius: 4px;
1296
+ font-size: 12px;
1297
+ font-family: ui-monospace, monospace;
1298
+ z-index: 1000000;
1299
+ pointer-events: none;
1300
+ display: none;
1301
+ max-width: 300px;
1302
+ overflow: hidden;
1303
+ text-overflow: ellipsis;
1304
+ white-space: nowrap;
1305
+ \`;
1306
+ document.body.appendChild(tip);
1307
+ return tip;
1308
+ }
1309
+
1310
+ // Generate CSS selector for element
1311
+ function getSelector(el) {
1312
+ if (!el || el === document.body || el === document.documentElement) {
1313
+ return el ? el.tagName.toLowerCase() : '';
1314
+ }
1315
+
1316
+ // Try ID first
1317
+ if (el.id && /^[a-zA-Z][\\w-]*$/.test(el.id)) {
1318
+ return '#' + el.id;
1319
+ }
1320
+
1321
+ let path = [];
1322
+ let current = el;
1323
+
1324
+ while (current && current !== document.body && path.length < 5) {
1325
+ let selector = current.tagName.toLowerCase();
1326
+
1327
+ // Add id if available
1328
+ if (current.id && /^[a-zA-Z][\\w-]*$/.test(current.id)) {
1329
+ path.unshift('#' + current.id);
1330
+ break;
1331
+ }
1332
+
1333
+ // Add meaningful classes (skip utility classes)
1334
+ if (current.className && typeof current.className === 'string') {
1335
+ const classes = current.className
1336
+ .split(/\\s+/)
1337
+ .filter(c => c && c.length > 2 && !c.match(/^(w-|h-|p-|m-|text-|bg-|flex|grid|block|inline)/))
1338
+ .slice(0, 2);
1339
+ if (classes.length) {
1340
+ selector += '.' + classes.join('.');
1341
+ }
1342
+ }
1343
+
1344
+ // Add nth-child if needed for uniqueness
1345
+ const siblings = current.parentElement ?
1346
+ Array.from(current.parentElement.children).filter(s => s.tagName === current.tagName) : [];
1347
+ if (siblings.length > 1) {
1348
+ const index = siblings.indexOf(current) + 1;
1349
+ selector += ':nth-child(' + index + ')';
1350
+ }
1351
+
1352
+ path.unshift(selector);
1353
+ current = current.parentElement;
1354
+ }
1355
+
1356
+ return path.join(' > ');
1357
+ }
1358
+
1359
+ // Try to get React component info
1360
+ function getReactInfo(el) {
1361
+ // Look for React fiber
1362
+ const fiberKey = Object.keys(el).find(k =>
1363
+ k.startsWith('__reactFiber') || k.startsWith('__reactInternalInstance')
1364
+ );
1365
+
1366
+ if (!fiberKey) return null;
1367
+
1368
+ let fiber = el[fiberKey];
1369
+ let depth = 0;
1370
+ const maxDepth = 20;
1371
+
1372
+ while (fiber && depth < maxDepth) {
1373
+ if (fiber.type && typeof fiber.type === 'function') {
1374
+ const name = fiber.type.displayName || fiber.type.name;
1375
+ if (name && name !== 'Anonymous' && !name.startsWith('_')) {
1376
+ return {
1377
+ component: name,
1378
+ source: fiber._debugSource || null
1379
+ };
1380
+ }
1381
+ }
1382
+ fiber = fiber.return;
1383
+ depth++;
1384
+ }
1385
+
1386
+ return null;
1387
+ }
1388
+
1389
+ // Extract a useful relative path from absolute file path
1390
+ function getRelativePath(fullPath) {
1391
+ if (!fullPath) return null;
1392
+
1393
+ // Common source folder markers (prioritized)
1394
+ const sourceMarkers = ['/src/', '/app/', '/pages/', '/components/', '/lib/', '/utils/'];
1395
+
1396
+ for (const marker of sourceMarkers) {
1397
+ const index = fullPath.indexOf(marker);
1398
+ if (index !== -1) {
1399
+ // Return path starting from the marker (e.g., 'src/components/Button.tsx')
1400
+ return fullPath.substring(index + 1);
1401
+ }
1402
+ }
1403
+
1404
+ // Fallback: return last 3 path segments
1405
+ const parts = fullPath.split('/').filter(Boolean);
1406
+ if (parts.length <= 3) {
1407
+ return parts.join('/');
1408
+ }
1409
+ return parts.slice(-3).join('/');
1410
+ }
1411
+
1412
+ // Get element info for selection
1413
+ function getElementInfo(el) {
1414
+ const rect = el.getBoundingClientRect();
1415
+ const reactInfo = getReactInfo(el);
1416
+ const computedStyle = window.getComputedStyle(el);
1417
+
1418
+ return {
1419
+ selector: getSelector(el),
1420
+ tagName: el.tagName.toLowerCase(),
1421
+ text: (el.textContent || '').trim().slice(0, 100),
1422
+ attributes: {
1423
+ id: el.id || null,
1424
+ class: el.className || null,
1425
+ href: el.href || null,
1426
+ src: el.src || null,
1427
+ type: el.type || null,
1428
+ name: el.name || null
1429
+ },
1430
+ boundingRect: {
1431
+ top: Math.round(rect.top),
1432
+ left: Math.round(rect.left),
1433
+ width: Math.round(rect.width),
1434
+ height: Math.round(rect.height)
1435
+ },
1436
+ computedStyle: {
1437
+ display: computedStyle.display,
1438
+ position: computedStyle.position,
1439
+ color: computedStyle.color,
1440
+ backgroundColor: computedStyle.backgroundColor,
1441
+ fontSize: computedStyle.fontSize
1442
+ },
1443
+ sourceHint: reactInfo ? {
1444
+ component: reactInfo.component,
1445
+ file: getRelativePath(reactInfo.source?.fileName),
1446
+ fullPath: reactInfo.source?.fileName,
1447
+ line: reactInfo.source?.lineNumber,
1448
+ confidence: reactInfo.source ? 'high' : 'low'
1449
+ } : null
1450
+ };
1451
+ }
1452
+
1453
+ // Position overlay on element
1454
+ function positionOverlay(overlay, el) {
1455
+ const rect = el.getBoundingClientRect();
1456
+ overlay.style.top = rect.top + 'px';
1457
+ overlay.style.left = rect.left + 'px';
1458
+ overlay.style.width = rect.width + 'px';
1459
+ overlay.style.height = rect.height + 'px';
1460
+ overlay.style.display = 'block';
1461
+ }
1462
+
1463
+ // Handle element selection
1464
+ function selectElement(el, event) {
1465
+ event.preventDefault();
1466
+ event.stopPropagation();
1467
+
1468
+ selectedElement = el;
1469
+ const info = getElementInfo(el);
1470
+
1471
+ // Show selection overlay
1472
+ if (!selectionOverlay) selectionOverlay = createSelectionOverlay();
1473
+ positionOverlay(selectionOverlay, el);
1474
+
1475
+ // Send selection to parent
1476
+ const message = {
1477
+ type: 'element-selected',
1478
+ data: info
1479
+ };
1480
+
1481
+ window.parent.postMessage(message, '*');
1482
+
1483
+ console.log('[Loxia] Element selected:', info.selector);
1484
+ }
1485
+
1486
+ // Mouse move - highlight hovered element
1487
+ document.addEventListener('mousemove', (e) => {
1488
+ if (!isEnabled) return;
1489
+
1490
+ const el = e.target;
1491
+ if (el === hoveredElement) return;
1492
+ if (el.id?.startsWith('loxia-')) return;
1493
+
1494
+ hoveredElement = el;
1495
+
1496
+ if (!highlightOverlay) highlightOverlay = createHighlightOverlay();
1497
+ if (!tooltip) tooltip = createTooltip();
1498
+
1499
+ positionOverlay(highlightOverlay, el);
1500
+
1501
+ // Update tooltip
1502
+ const tagName = el.tagName.toLowerCase();
1503
+ const id = el.id ? '#' + el.id : '';
1504
+ const classes = el.className && typeof el.className === 'string' ?
1505
+ '.' + el.className.split(' ').slice(0, 2).join('.') : '';
1506
+
1507
+ tooltip.textContent = tagName + id + classes;
1508
+ tooltip.style.left = (e.clientX + 10) + 'px';
1509
+ tooltip.style.top = (e.clientY + 10) + 'px';
1510
+ tooltip.style.display = 'block';
1511
+ }, true);
1512
+
1513
+ // Mouse leave - hide highlight
1514
+ document.addEventListener('mouseleave', () => {
1515
+ if (highlightOverlay) highlightOverlay.style.display = 'none';
1516
+ if (tooltip) tooltip.style.display = 'none';
1517
+ }, true);
1518
+
1519
+ // Click - select element
1520
+ document.addEventListener('click', (e) => {
1521
+ if (!isEnabled) return;
1522
+ if (e.target.id?.startsWith('loxia-')) return;
1523
+
1524
+ selectElement(e.target, e);
1525
+ }, true);
1526
+
1527
+ // Keyboard shortcuts
1528
+ document.addEventListener('keydown', (e) => {
1529
+ // Escape - toggle overlay
1530
+ if (e.key === 'Escape') {
1531
+ isEnabled = !isEnabled;
1532
+ updateCursorStyle(isEnabled);
1533
+ if (!isEnabled) {
1534
+ if (highlightOverlay) highlightOverlay.style.display = 'none';
1535
+ if (selectionOverlay) selectionOverlay.style.display = 'none';
1536
+ if (tooltip) tooltip.style.display = 'none';
1537
+ hoveredElement = null;
1538
+ }
1539
+ // Notify parent of mode change
1540
+ window.parent.postMessage({ type: 'mode-toggled', enabled: isEnabled }, '*');
1541
+ console.log('[Loxia] Visual editor ' + (isEnabled ? 'enabled' : 'disabled'));
1542
+ }
1543
+ });
1544
+
1545
+ // Listen for commands from editor
1546
+ window.addEventListener('message', (e) => {
1547
+ if (!e.data || !e.data.type) return;
1548
+
1549
+ switch (e.data.type) {
1550
+ case 'highlight':
1551
+ const highlightEl = document.querySelector(e.data.selector);
1552
+ if (highlightEl) {
1553
+ if (!selectionOverlay) selectionOverlay = createSelectionOverlay();
1554
+ positionOverlay(selectionOverlay, highlightEl);
1555
+
1556
+ // Auto-hide after duration
1557
+ setTimeout(() => {
1558
+ if (selectionOverlay) selectionOverlay.style.display = 'none';
1559
+ }, e.data.duration || 2000);
1560
+ }
1561
+ break;
1562
+
1563
+ case 'scroll-to':
1564
+ const scrollEl = document.querySelector(e.data.selector);
1565
+ if (scrollEl) {
1566
+ scrollEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
1567
+ }
1568
+ break;
1569
+
1570
+ case 'reload':
1571
+ location.reload();
1572
+ break;
1573
+
1574
+ case 'toggle':
1575
+ isEnabled = e.data.enabled !== undefined ? e.data.enabled : !isEnabled;
1576
+ updateCursorStyle(isEnabled);
1577
+ // Hide/show overlays based on new state
1578
+ if (!isEnabled) {
1579
+ if (highlightOverlay) highlightOverlay.style.display = 'none';
1580
+ if (selectionOverlay) selectionOverlay.style.display = 'none';
1581
+ if (tooltip) tooltip.style.display = 'none';
1582
+ hoveredElement = null;
1583
+ }
1584
+ console.log('[Loxia] Visual editor ' + (isEnabled ? 'enabled (Select mode)' : 'disabled (Preview mode)'));
1585
+ break;
1586
+
1587
+ case 'set-error-reporting':
1588
+ errorReportingEnabled = !!e.data.enabled;
1589
+ console.log('[Loxia] Error reporting ' + (errorReportingEnabled ? 'enabled' : 'disabled'));
1590
+ break;
1591
+ }
1592
+ });
1593
+
1594
+ // === Console Error Capture ===
1595
+ let errorReportingEnabled = true;
1596
+ const capturedErrors = [];
1597
+ const MAX_CAPTURED = 20;
1598
+
1599
+ function reportError(error) {
1600
+ if (!errorReportingEnabled) return;
1601
+ if (capturedErrors.length >= MAX_CAPTURED) return;
1602
+ // Skip Loxia's own logs
1603
+ if (typeof error.message === 'string' && error.message.startsWith('[Loxia]')) return;
1604
+
1605
+ const entry = {
1606
+ type: error.type || 'error',
1607
+ message: String(error.message || error).slice(0, 500),
1608
+ source: error.source || null,
1609
+ line: error.line || null,
1610
+ col: error.col || null,
1611
+ timestamp: Date.now()
1612
+ };
1613
+ capturedErrors.push(entry);
1614
+
1615
+ window.parent.postMessage({
1616
+ type: 'console-error',
1617
+ data: entry
1618
+ }, '*');
1619
+ }
1620
+
1621
+ // Capture unhandled errors
1622
+ window.addEventListener('error', (e) => {
1623
+ reportError({
1624
+ type: 'runtime-error',
1625
+ message: e.message,
1626
+ source: e.filename,
1627
+ line: e.lineno,
1628
+ col: e.colno
1629
+ });
1630
+ });
1631
+
1632
+ // Capture unhandled promise rejections
1633
+ window.addEventListener('unhandledrejection', (e) => {
1634
+ reportError({
1635
+ type: 'unhandled-rejection',
1636
+ message: e.reason ? (e.reason.message || String(e.reason)) : 'Unknown rejection'
1637
+ });
1638
+ });
1639
+
1640
+ // Intercept console.error
1641
+ const originalConsoleError = console.error;
1642
+ console.error = function() {
1643
+ const msg = Array.from(arguments).map(a => {
1644
+ if (a instanceof Error) return a.message + (a.stack ? '\\n' + a.stack.split('\\n').slice(0, 3).join('\\n') : '');
1645
+ if (typeof a === 'object') try { return JSON.stringify(a).slice(0, 300); } catch { return String(a); }
1646
+ return String(a);
1647
+ }).join(' ');
1648
+
1649
+ reportError({ type: 'console-error', message: msg });
1650
+ originalConsoleError.apply(console, arguments);
1651
+ };
1652
+
1653
+ // Intercept console.warn for build warnings
1654
+ const originalConsoleWarn = console.warn;
1655
+ console.warn = function() {
1656
+ const msg = Array.from(arguments).map(a => typeof a === 'string' ? a : String(a)).join(' ');
1657
+ // Only capture warnings that look like build/framework issues
1658
+ if (/deprecat|warning|failed|error|cannot|invalid/i.test(msg)) {
1659
+ reportError({ type: 'console-warning', message: msg });
1660
+ }
1661
+ originalConsoleWarn.apply(console, arguments);
1662
+ };
1663
+
1664
+ console.log('[Loxia] Visual Editor overlay loaded - Click elements to select, ESC to toggle');
1665
+ })();`;
1666
+ }
1667
+ }
1668
+
1669
+ // Singleton instance
1670
+ let serverInstance = null;
1671
+
1672
+ /**
1673
+ * Get or create the Visual Editor Server singleton
1674
+ * @param {Object} config - Configuration (only used on first call)
1675
+ * @returns {VisualEditorServer}
1676
+ */
1677
+ export function getVisualEditorServer(config = {}) {
1678
+ if (!serverInstance) {
1679
+ serverInstance = new VisualEditorServer(config);
1680
+ }
1681
+ return serverInstance;
1682
+ }
1683
+
1684
+ /**
1685
+ * Reset the singleton (for testing)
1686
+ */
1687
+ export async function resetVisualEditorServer() {
1688
+ if (serverInstance) {
1689
+ await serverInstance.stop();
1690
+ serverInstance = null;
1691
+ }
1692
+ }
1693
+
1694
+ /**
1695
+ * Set the bridge getter function to enable element selection forwarding
1696
+ * This avoids circular dependencies between visualEditorServer and visualEditorBridge
1697
+ * @param {Function} getter - Function that returns the visualEditorBridge instance
1698
+ */
1699
+ export { setBridgeGetter };
1700
+
1701
+ /**
1702
+ * Get the Visual Editor port from service registry (source of truth)
1703
+ * Falls back to server instance port or default if not registered
1704
+ * @returns {number} The port number
1705
+ */
1706
+ export function getVisualEditorPort() {
1707
+ // Check service registry first (source of truth)
1708
+ const service = registry.get(SERVICE_NAME);
1709
+ if (service) {
1710
+ return service.port;
1711
+ }
1712
+
1713
+ // Fall back to server instance or default
1714
+ return serverInstance?.port || getDefaultPort();
1715
+ }
1716
+
1717
+ /**
1718
+ * Get the Visual Editor base URL from service registry
1719
+ * @returns {string} The base URL (e.g., http://localhost:4000)
1720
+ */
1721
+ export function getVisualEditorBaseUrl() {
1722
+ const port = getVisualEditorPort();
1723
+ return `http://localhost:${port}`;
1724
+ }
1725
+
1726
+ export { FALLBACK_PORT as VISUAL_EDITOR_DEFAULT_PORT };
1727
+ export default VisualEditorServer;