xonecode 0.3.0 → 0.5.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 (406) hide show
  1. package/README.md +49 -7
  2. package/THIRD_PARTY_NOTICES.md +128 -0
  3. package/apps/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  4. package/apps/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  5. package/apps/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  6. package/apps/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  7. package/apps/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  8. package/apps/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  9. package/apps/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  10. package/apps/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  11. package/apps/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  12. package/apps/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  13. package/apps/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  14. package/apps/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  15. package/apps/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  16. package/apps/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  17. package/apps/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  18. package/apps/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  19. package/apps/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  20. package/apps/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  21. package/apps/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  22. package/apps/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  23. package/apps/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  24. package/apps/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  25. package/apps/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  26. package/apps/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  27. package/apps/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  28. package/apps/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  29. package/apps/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  30. package/apps/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  31. package/apps/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  32. package/apps/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  33. package/apps/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  34. package/apps/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  35. package/apps/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  36. package/apps/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  37. package/apps/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  38. package/apps/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  39. package/apps/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  40. package/apps/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  41. package/apps/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  42. package/apps/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  43. package/apps/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  44. package/apps/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  45. package/apps/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  46. package/apps/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  47. package/apps/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  48. package/apps/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  49. package/apps/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  50. package/apps/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  51. package/apps/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  52. package/apps/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  53. package/apps/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  54. package/apps/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  55. package/apps/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  56. package/apps/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  57. package/apps/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  58. package/apps/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  59. package/apps/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  60. package/apps/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  61. package/apps/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  62. package/apps/web/dist/assets/c-BIGW1oBm.js +1 -0
  63. package/apps/web/dist/assets/cpp-DddhVWhK.js +1 -0
  64. package/apps/web/dist/assets/csharp-DSvCPggb.js +1 -0
  65. package/apps/web/dist/assets/css-CLj8gQPS.js +1 -0
  66. package/apps/web/dist/assets/go-C27-OAKa.js +1 -0
  67. package/apps/web/dist/assets/html-CfGypltT.js +1 -0
  68. package/apps/web/dist/assets/index-BB3SamhR.css +1 -0
  69. package/apps/web/dist/assets/index-CFYAoHdw.js +477 -0
  70. package/apps/web/dist/assets/ini-BEwlwnbL.js +1 -0
  71. package/apps/web/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  72. package/apps/web/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  73. package/apps/web/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  74. package/apps/web/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  75. package/apps/web/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  76. package/apps/web/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  77. package/apps/web/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  78. package/apps/web/dist/assets/java-CylS5w8V.js +1 -0
  79. package/apps/web/dist/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  80. package/apps/web/dist/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  81. package/apps/web/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  82. package/apps/web/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  83. package/apps/web/dist/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  84. package/apps/web/dist/assets/kotlin-BdnUsdx6.js +1 -0
  85. package/apps/web/dist/assets/less-B1dDrJ26.js +1 -0
  86. package/apps/web/dist/assets/lua-BaeVxFsk.js +1 -0
  87. package/apps/web/dist/assets/markdown-Cvjx9yec.js +1 -0
  88. package/apps/web/dist/assets/mdx-Cmh6b_Ma.js +1 -0
  89. package/apps/web/dist/assets/php-D_2--9WK.js +1 -0
  90. package/apps/web/dist/assets/python-B6aJPvgy.js +1 -0
  91. package/apps/web/dist/assets/ruby-DSHSDbk1.js +1 -0
  92. package/apps/web/dist/assets/rust-B1yitclQ.js +1 -0
  93. package/apps/web/dist/assets/scss-D5BDwBP9.js +1 -0
  94. package/apps/web/dist/assets/sql-CRqJ_cUM.js +1 -0
  95. package/apps/web/dist/assets/swift-C2oV4EkX.js +1 -0
  96. package/apps/web/dist/assets/toml-vGWfd6FD.js +1 -0
  97. package/apps/web/dist/assets/xml-sdJ4AIDG.js +1 -0
  98. package/apps/web/dist/assets/yaml-Buea-lGh.js +1 -0
  99. package/apps/web/dist/iconos/xonecode.png +0 -0
  100. package/apps/web/dist/index.html +20 -0
  101. package/dist/agent/agentesEnDisco.js +539 -0
  102. package/dist/agent/agentesEnDisco.js.map +1 -0
  103. package/dist/agent/arbolDeProyecto.js +245 -0
  104. package/dist/agent/arbolDeProyecto.js.map +1 -0
  105. package/dist/agent/artefactosEnDisco.js +139 -0
  106. package/dist/agent/artefactosEnDisco.js.map +1 -0
  107. package/dist/agent/aumentador.js +175 -0
  108. package/dist/agent/aumentador.js.map +1 -0
  109. package/dist/agent/authEnDisco.js +76 -1
  110. package/dist/agent/authEnDisco.js.map +1 -1
  111. package/dist/agent/busquedaRegex.js +92 -0
  112. package/dist/agent/busquedaRegex.js.map +1 -0
  113. package/dist/agent/catalogoModelos.js +345 -0
  114. package/dist/agent/catalogoModelos.js.map +1 -0
  115. package/dist/agent/checkpointer.js +127 -0
  116. package/dist/agent/checkpointer.js.map +1 -0
  117. package/dist/agent/cloudstudioClient.js +194 -0
  118. package/dist/agent/cloudstudioClient.js.map +1 -0
  119. package/dist/agent/cloudstudioMcp.js +722 -0
  120. package/dist/agent/cloudstudioMcp.js.map +1 -0
  121. package/dist/agent/configEnDisco.js +309 -11
  122. package/dist/agent/configEnDisco.js.map +1 -1
  123. package/dist/agent/consumoExterno.js +99 -0
  124. package/dist/agent/consumoExterno.js.map +1 -0
  125. package/dist/agent/descarga.js +149 -0
  126. package/dist/agent/descarga.js.map +1 -0
  127. package/dist/agent/diagnosticoDeTools.js +51 -0
  128. package/dist/agent/diagnosticoDeTools.js.map +1 -0
  129. package/dist/agent/dispositivosEnMaquina.js +596 -0
  130. package/dist/agent/dispositivosEnMaquina.js.map +1 -0
  131. package/dist/agent/escrituraDeCodex.js +134 -0
  132. package/dist/agent/escrituraDeCodex.js.map +1 -0
  133. package/dist/agent/escrituraDeOpencode.js +80 -0
  134. package/dist/agent/escrituraDeOpencode.js.map +1 -0
  135. package/dist/agent/escrituraExterna.js +742 -0
  136. package/dist/agent/escrituraExterna.js.map +1 -0
  137. package/dist/agent/gemini.js +81 -0
  138. package/dist/agent/gemini.js.map +1 -0
  139. package/dist/agent/git.js +83 -0
  140. package/dist/agent/git.js.map +1 -0
  141. package/dist/agent/gitSync.js +441 -0
  142. package/dist/agent/gitSync.js.map +1 -0
  143. package/dist/agent/hotswap.js +448 -0
  144. package/dist/agent/hotswap.js.map +1 -0
  145. package/dist/agent/instalacionEnMaquina.js +252 -0
  146. package/dist/agent/instalacionEnMaquina.js.map +1 -0
  147. package/dist/agent/instantanea.js +7 -10
  148. package/dist/agent/instantanea.js.map +1 -1
  149. package/dist/agent/interrupts.js +11 -2
  150. package/dist/agent/interrupts.js.map +1 -1
  151. package/dist/agent/juezDeTarea.js +345 -0
  152. package/dist/agent/juezDeTarea.js.map +1 -0
  153. package/dist/agent/lanzamientoEnMaquina.js +377 -0
  154. package/dist/agent/lanzamientoEnMaquina.js.map +1 -0
  155. package/dist/agent/manifiesto.js +59 -0
  156. package/dist/agent/manifiesto.js.map +1 -0
  157. package/dist/agent/memoriaDeProyecto.js +60 -0
  158. package/dist/agent/memoriaDeProyecto.js.map +1 -0
  159. package/dist/agent/modelos.js +105 -17
  160. package/dist/agent/modelos.js.map +1 -1
  161. package/dist/agent/modelosDeMotor.js +204 -0
  162. package/dist/agent/modelosDeMotor.js.map +1 -0
  163. package/dist/agent/paqueteDelProyecto.js +181 -0
  164. package/dist/agent/paqueteDelProyecto.js.map +1 -0
  165. package/dist/agent/perfiles.js +92 -31
  166. package/dist/agent/perfiles.js.map +1 -1
  167. package/dist/agent/persona.js +40 -0
  168. package/dist/agent/persona.js.map +1 -0
  169. package/dist/agent/procesosEnMaquina.js +204 -0
  170. package/dist/agent/procesosEnMaquina.js.map +1 -0
  171. package/dist/agent/proyecto.js +311 -13
  172. package/dist/agent/proyecto.js.map +1 -1
  173. package/dist/agent/puente.js +63 -6
  174. package/dist/agent/puente.js.map +1 -1
  175. package/dist/agent/resumenDeContexto.js +30 -0
  176. package/dist/agent/resumenDeContexto.js.map +1 -0
  177. package/dist/agent/resumenDeTool.js +64 -18
  178. package/dist/agent/resumenDeTool.js.map +1 -1
  179. package/dist/agent/sesionGit.js +481 -0
  180. package/dist/agent/sesionGit.js.map +1 -0
  181. package/dist/agent/settingsEnDisco.js +189 -0
  182. package/dist/agent/settingsEnDisco.js.map +1 -0
  183. package/dist/agent/subagenteCodex.js +368 -0
  184. package/dist/agent/subagenteCodex.js.map +1 -0
  185. package/dist/agent/subagenteExterno.js +423 -0
  186. package/dist/agent/subagenteExterno.js.map +1 -0
  187. package/dist/agent/subagenteOpencode.js +389 -0
  188. package/dist/agent/subagenteOpencode.js.map +1 -0
  189. package/dist/agent/subida.js +202 -0
  190. package/dist/agent/subida.js.map +1 -0
  191. package/dist/agent/tareasEnDisco.js +466 -0
  192. package/dist/agent/tareasEnDisco.js.map +1 -0
  193. package/dist/agent/turnoReal.js +663 -50
  194. package/dist/agent/turnoReal.js.map +1 -1
  195. package/dist/agent/versionEnDisco.js +48 -0
  196. package/dist/agent/versionEnDisco.js.map +1 -0
  197. package/dist/agent/xoneAgent.js +290 -49
  198. package/dist/agent/xoneAgent.js.map +1 -1
  199. package/dist/agent/zip.js +52 -0
  200. package/dist/agent/zip.js.map +1 -0
  201. package/dist/cli/acuseDeModelo.js +30 -0
  202. package/dist/cli/acuseDeModelo.js.map +1 -0
  203. package/dist/cli/aprobar.js +55 -2
  204. package/dist/cli/aprobar.js.map +1 -1
  205. package/dist/cli/config.js +9 -13
  206. package/dist/cli/config.js.map +1 -1
  207. package/dist/cli/consola.js +750 -19
  208. package/dist/cli/consola.js.map +1 -1
  209. package/dist/cli/main.js +740 -81
  210. package/dist/cli/main.js.map +1 -1
  211. package/dist/cli/markdown.js +188 -2
  212. package/dist/cli/markdown.js.map +1 -1
  213. package/dist/cli/run.js +32 -4
  214. package/dist/cli/run.js.map +1 -1
  215. package/dist/cli/stdio.js +46 -19
  216. package/dist/cli/stdio.js.map +1 -1
  217. package/dist/cli/tema.js +96 -8
  218. package/dist/cli/tema.js.map +1 -1
  219. package/dist/cli/tokens.js +29 -0
  220. package/dist/cli/tokens.js.map +1 -0
  221. package/dist/cli/tui/app.js +171 -0
  222. package/dist/cli/tui/app.js.map +1 -0
  223. package/dist/cli/tui/aprobarTui.js +80 -0
  224. package/dist/cli/tui/aprobarTui.js.map +1 -0
  225. package/dist/cli/tui/barra.js +37 -0
  226. package/dist/cli/tui/barra.js.map +1 -0
  227. package/dist/cli/tui/correrTui.js +478 -0
  228. package/dist/cli/tui/correrTui.js.map +1 -0
  229. package/dist/cli/tui/entrada.js +166 -0
  230. package/dist/cli/tui/entrada.js.map +1 -0
  231. package/dist/cli/tui/filas.js +24 -0
  232. package/dist/cli/tui/filas.js.map +1 -0
  233. package/dist/cli/tui/logo.js +49 -0
  234. package/dist/cli/tui/logo.js.map +1 -0
  235. package/dist/cli/tui/pielTui.js +14 -0
  236. package/dist/cli/tui/pielTui.js.map +1 -0
  237. package/dist/cli/tui/raton.js +126 -0
  238. package/dist/cli/tui/raton.js.map +1 -0
  239. package/dist/cli/tui/sidebar.js +86 -0
  240. package/dist/cli/tui/sidebar.js.map +1 -0
  241. package/dist/cli/tui/store.js +143 -0
  242. package/dist/cli/tui/store.js.map +1 -0
  243. package/dist/cli/tui/tarjeta.js +19 -0
  244. package/dist/cli/tui/tarjeta.js.map +1 -0
  245. package/dist/cli/tui/temaInk.js +114 -0
  246. package/dist/cli/tui/temaInk.js.map +1 -0
  247. package/dist/cli/tui/transcript.js +254 -0
  248. package/dist/cli/tui/transcript.js.map +1 -0
  249. package/dist/cli/wizardInicial.js +240 -0
  250. package/dist/cli/wizardInicial.js.map +1 -0
  251. package/dist/core/actos.js +113 -0
  252. package/dist/core/actos.js.map +1 -0
  253. package/dist/core/adjuntos.js +97 -0
  254. package/dist/core/adjuntos.js.map +1 -0
  255. package/dist/core/agentes.js +227 -0
  256. package/dist/core/agentes.js.map +1 -0
  257. package/dist/core/artefactos.js +149 -0
  258. package/dist/core/artefactos.js.map +1 -0
  259. package/dist/core/cloudstudio.js +7 -0
  260. package/dist/core/cloudstudio.js.map +1 -0
  261. package/dist/core/config.js +212 -5
  262. package/dist/core/config.js.map +1 -1
  263. package/dist/core/contextos.js +53 -1
  264. package/dist/core/contextos.js.map +1 -1
  265. package/dist/core/descargas.js +106 -0
  266. package/dist/core/descargas.js.map +1 -0
  267. package/dist/core/descriptoresDeApp.js +279 -0
  268. package/dist/core/descriptoresDeApp.js.map +1 -0
  269. package/dist/core/dispositivos.js +466 -0
  270. package/dist/core/dispositivos.js.map +1 -0
  271. package/dist/core/entrega.js +141 -0
  272. package/dist/core/entrega.js.map +1 -0
  273. package/dist/core/entrelazar.js +90 -0
  274. package/dist/core/entrelazar.js.map +1 -0
  275. package/dist/core/esqueleto.js +10 -0
  276. package/dist/core/esqueleto.js.map +1 -1
  277. package/dist/core/modelos.js +235 -3
  278. package/dist/core/modelos.js.map +1 -1
  279. package/dist/core/notify.js +27 -9
  280. package/dist/core/notify.js.map +1 -1
  281. package/dist/core/planDeSubida.js +127 -0
  282. package/dist/core/planDeSubida.js.map +1 -0
  283. package/dist/core/ports.js +189 -1
  284. package/dist/core/ports.js.map +1 -1
  285. package/dist/core/puedeLanzarse.js +239 -0
  286. package/dist/core/puedeLanzarse.js.map +1 -0
  287. package/dist/core/rutaVirtual.js +42 -0
  288. package/dist/core/rutaVirtual.js.map +1 -0
  289. package/dist/core/settings.js +246 -0
  290. package/dist/core/settings.js.map +1 -0
  291. package/dist/core/tareas.js +230 -0
  292. package/dist/core/tareas.js.map +1 -0
  293. package/dist/core/textos.js +36 -0
  294. package/dist/core/textos.js.map +1 -0
  295. package/dist/core/turno.js +56 -8
  296. package/dist/core/turno.js.map +1 -1
  297. package/dist/core/version.js +31 -0
  298. package/dist/core/version.js.map +1 -0
  299. package/dist/vendor/tokenTracking.js +22 -1
  300. package/dist/vendor/tokenTracking.js.map +1 -1
  301. package/dist/web/servidor/arranque.js +3964 -0
  302. package/dist/web/servidor/arranque.js.map +1 -0
  303. package/dist/web/servidor/consolaDeTarea.js +161 -0
  304. package/dist/web/servidor/consolaDeTarea.js.map +1 -0
  305. package/dist/web/servidor/consolaWeb.js +389 -0
  306. package/dist/web/servidor/consolaWeb.js.map +1 -0
  307. package/dist/web/servidor/corredorDeTareas.js +1119 -0
  308. package/dist/web/servidor/corredorDeTareas.js.map +1 -0
  309. package/dist/web/servidor/pielWeb.js +269 -0
  310. package/dist/web/servidor/pielWeb.js.map +1 -0
  311. package/dist/web/servidor/servidor.js +257 -0
  312. package/dist/web/servidor/servidor.js.map +1 -0
  313. package/dist/web/servidor/sesiones.js +440 -0
  314. package/dist/web/servidor/sesiones.js.map +1 -0
  315. package/dist/web/servidor/transporte.js +169 -0
  316. package/dist/web/servidor/transporte.js.map +1 -0
  317. package/dist/web/servidor/vestibulo.js +1276 -0
  318. package/dist/web/servidor/vestibulo.js.map +1 -0
  319. package/package.json +30 -8
  320. package/skills/archify/LICENSE +22 -0
  321. package/skills/archify/SKILL.md +109 -0
  322. package/skills/archify/THIRD_PARTY_NOTICES.md +56 -0
  323. package/skills/archify/assets/template.html +14787 -0
  324. package/skills/archify/bin/archify.mjs +1990 -0
  325. package/skills/archify/bin/open-artifact.mjs +86 -0
  326. package/skills/archify/bin/preview.mjs +648 -0
  327. package/skills/archify/bin/visual-check.mjs +829 -0
  328. package/skills/archify/brand-marks/README.md +31 -0
  329. package/skills/archify/brand-marks/catalog.json +131 -0
  330. package/skills/archify/delta/architecture-delta.mjs +1214 -0
  331. package/skills/archify/examples/agent-run.lifecycle.json +71 -0
  332. package/skills/archify/examples/agent-tool-call.workflow.json +94 -0
  333. package/skills/archify/examples/async-job-roundtrip.sequence.json +61 -0
  334. package/skills/archify/examples/brand-aware-delivery.architecture.json +47 -0
  335. package/skills/archify/examples/cache-miss-request.sequence.json +81 -0
  336. package/skills/archify/examples/checkout-platform.base.architecture.json +31 -0
  337. package/skills/archify/examples/checkout-platform.head.architecture.json +31 -0
  338. package/skills/archify/examples/dataflow-product-analytics.html +14898 -0
  339. package/skills/archify/examples/deployment-release.lifecycle.json +49 -0
  340. package/skills/archify/examples/event-stream.dataflow.json +57 -0
  341. package/skills/archify/examples/incident-response.workflow.json +64 -0
  342. package/skills/archify/examples/lifecycle-agent-run.html +14847 -0
  343. package/skills/archify/examples/product-analytics.dataflow.json +76 -0
  344. package/skills/archify/examples/production-deployment.architecture.json +71 -0
  345. package/skills/archify/examples/release-delivery.workflow.json +62 -0
  346. package/skills/archify/examples/sequence-cache-miss-request.html +14913 -0
  347. package/skills/archify/examples/web-app-rendered.html +14862 -0
  348. package/skills/archify/examples/web-app.architecture.json +46 -0
  349. package/skills/archify/examples/workflow-agent-tool-call-rendered.html +14904 -0
  350. package/skills/archify/migrations/workflow-v2.mjs +279 -0
  351. package/skills/archify/package.json +17 -0
  352. package/skills/archify/recipes/scenarios.mjs +391 -0
  353. package/skills/archify/references/authoring-contract.md +198 -0
  354. package/skills/archify/references/brand-marks.md +69 -0
  355. package/skills/archify/references/delivery-contract.md +116 -0
  356. package/skills/archify/references/viewer-runtime.md +45 -0
  357. package/skills/archify/renderers/architecture/grid.mjs +62 -0
  358. package/skills/archify/renderers/architecture/render-architecture.mjs +1078 -0
  359. package/skills/archify/renderers/dataflow/README.md +104 -0
  360. package/skills/archify/renderers/dataflow/render-dataflow.mjs +483 -0
  361. package/skills/archify/renderers/lifecycle/README.md +115 -0
  362. package/skills/archify/renderers/lifecycle/render-lifecycle.mjs +561 -0
  363. package/skills/archify/renderers/sequence/README.md +114 -0
  364. package/skills/archify/renderers/sequence/render-sequence.mjs +453 -0
  365. package/skills/archify/renderers/shared/brand-marks.mjs +563 -0
  366. package/skills/archify/renderers/shared/cli.mjs +218 -0
  367. package/skills/archify/renderers/shared/desktop-readability.mjs +26 -0
  368. package/skills/archify/renderers/shared/diagnostics.mjs +127 -0
  369. package/skills/archify/renderers/shared/engineering-profiles.mjs +157 -0
  370. package/skills/archify/renderers/shared/generated-brand-marks.mjs +2003 -0
  371. package/skills/archify/renderers/shared/generated-validators.mjs +13 -0
  372. package/skills/archify/renderers/shared/geometry.mjs +1423 -0
  373. package/skills/archify/renderers/shared/i18n.mjs +594 -0
  374. package/skills/archify/renderers/shared/layout-report.mjs +40 -0
  375. package/skills/archify/renderers/shared/legend.mjs +217 -0
  376. package/skills/archify/renderers/shared/output-path.mjs +321 -0
  377. package/skills/archify/renderers/shared/repository-evidence.mjs +235 -0
  378. package/skills/archify/renderers/shared/text-fit.mjs +49 -0
  379. package/skills/archify/renderers/shared/utils.mjs +232 -0
  380. package/skills/archify/renderers/shared/validator.mjs +86 -0
  381. package/skills/archify/renderers/workflow/README.md +223 -0
  382. package/skills/archify/renderers/workflow/render-workflow.mjs +35 -0
  383. package/skills/archify/renderers/workflow/workflow-compiler.mjs +4400 -0
  384. package/skills/archify/renderers/workflow/workflow-migration-geometry.mjs +144 -0
  385. package/skills/archify/schemas/README.md +211 -0
  386. package/skills/archify/schemas/architecture.schema.json +176 -0
  387. package/skills/archify/schemas/common.schema.json +115 -0
  388. package/skills/archify/schemas/dataflow.schema.json +243 -0
  389. package/skills/archify/schemas/lifecycle.schema.json +266 -0
  390. package/skills/archify/schemas/sequence.schema.json +223 -0
  391. package/skills/archify/schemas/workflow.schema.json +428 -0
  392. package/skills/archify/scripts/check-render-output.mjs +835 -0
  393. package/skills/archify/scripts/check-update.mjs +1667 -0
  394. package/skills/archify/scripts/render-examples.mjs +26 -0
  395. package/skills/archify/scripts/update-contract.mjs +182 -0
  396. package/skills/archify/skill-release.json +10 -0
  397. package/skills/artifacts-builder/SKILL.md +120 -0
  398. package/skills/artifacts-builder/reference/diagramas.md +123 -0
  399. package/skills/artifacts-builder/reference/estilo.md +123 -0
  400. package/skills/artifacts-builder/reference/exportar-pdf.md +56 -0
  401. package/skills/artifacts-builder/reference/graficas.md +22 -0
  402. package/skills/artifacts-builder/scripts/bundle_artifact.py +109 -0
  403. package/skills/artifacts-builder/scripts/validate_artifact.py +345 -0
  404. package/skills/xone-hotswap/SKILL.md +185 -0
  405. package/skills/xone-hotswap/references/comandos.md +706 -0
  406. package/skills/xone-hotswap/references/conexion-y-despliegue.md +226 -0
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env python3
2
+ """Colapsa un HTML con ficheros locales a UN solo fichero autocontenido.
3
+
4
+ Mismo trabajo que `html-inline` de la skill original de Anthropic, pero sin
5
+ npm: el contenedor tiene python3 y node, no npm.
6
+
7
+ Los recursos absolutos (http/https, //) se dejan como estan — el CSP decide que
8
+ CDNs se permiten (ver `validate_artifact.py`, que es quien enforza esa lista
9
+ blanca de hosts); este script no repite esa lista, solo distingue local de
10
+ absoluto por el esquema de la URL.
11
+
12
+ Solo libreria estandar.
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import base64
17
+ import mimetypes
18
+ import pathlib
19
+ import re
20
+ import sys
21
+ from urllib.parse import urlparse
22
+
23
+ # Un unico regex con alternancia: `re.sub` solo escanea el string ORIGINAL, no
24
+ # vuelve a mirar lo que el propio callback acaba de insertar. Con tres pases
25
+ # separados (link, luego script, luego img) el pase de imagenes podia colarse
26
+ # DENTRO de un <script> ya inlineado si el JS traia algo con pinta de
27
+ # `<img src="...">` en un string literal — corrupcion silenciosa del JS.
28
+ _RECURSO = re.compile(
29
+ r"""<link\b[^>]*\bhref\s*=\s*["'](?P<css>[^"']+)["'][^>]*>"""
30
+ r"""|<script\b[^>]*\bsrc\s*=\s*["'](?P<js>[^"']+)["'][^>]*>\s*</script>"""
31
+ r"""|(?P<imgpre><img\b[^>]*\bsrc\s*=\s*["'])(?P<img>[^"']+)(?P<imgpost>["'])""",
32
+ re.I,
33
+ )
34
+
35
+
36
+ def _es_local(url: str) -> bool:
37
+ u = (url or "").strip()
38
+ if not u or u.startswith(("data:", "blob:", "#")):
39
+ return False
40
+ p = urlparse(u)
41
+ if p.scheme in ("http", "https") or u.startswith("//"):
42
+ return False
43
+ return True
44
+
45
+
46
+ def _leer(base: pathlib.Path, url: str) -> bytes | None:
47
+ ruta = (base / url.lstrip("/")).resolve()
48
+ try:
49
+ # No salir del directorio del artifact.
50
+ ruta.relative_to(base.resolve())
51
+ except ValueError:
52
+ print(f"[bundle] fuera del directorio, se ignora: {url}", file=sys.stderr)
53
+ return None
54
+ if not ruta.is_file():
55
+ print(f"[bundle] no encontrado, se deja el tag: {url}", file=sys.stderr)
56
+ return None
57
+ return ruta.read_bytes()
58
+
59
+
60
+ def empaquetar(html: str, base: pathlib.Path) -> str:
61
+ """Devuelve el HTML con todo recurso LOCAL embebido."""
62
+ base = pathlib.Path(base)
63
+
64
+ def _sub(m: re.Match[str]) -> str:
65
+ if m.group("css") is not None:
66
+ url = m.group("css")
67
+ if not _es_local(url) or "stylesheet" not in m.group(0).lower():
68
+ return m.group(0)
69
+ datos = _leer(base, url)
70
+ if datos is None:
71
+ return m.group(0)
72
+ return "<style>\n" + datos.decode("utf-8", "replace") + "\n</style>"
73
+
74
+ if m.group("js") is not None:
75
+ url = m.group("js")
76
+ if not _es_local(url):
77
+ return m.group(0)
78
+ datos = _leer(base, url)
79
+ if datos is None:
80
+ return m.group(0)
81
+ return "<script>\n" + datos.decode("utf-8", "replace") + "\n</script>"
82
+
83
+ # Imagen.
84
+ pre, url, post = m.group("imgpre"), m.group("img"), m.group("imgpost")
85
+ if not _es_local(url):
86
+ return m.group(0)
87
+ datos = _leer(base, url)
88
+ if datos is None:
89
+ return m.group(0)
90
+ mime = mimetypes.guess_type(url)[0] or "application/octet-stream"
91
+ return pre + f"data:{mime};base64," + base64.b64encode(datos).decode() + post
92
+
93
+ return _RECURSO.sub(_sub, html)
94
+
95
+
96
+ def main(argv: list[str]) -> int:
97
+ if len(argv) not in (2, 3):
98
+ print("uso: bundle_artifact.py <entrada.html> [salida.html]", file=sys.stderr)
99
+ return 2
100
+ entrada = pathlib.Path(argv[1])
101
+ salida = pathlib.Path(argv[2]) if len(argv) == 3 else entrada
102
+ resultado = empaquetar(entrada.read_text(encoding="utf-8"), entrada.parent)
103
+ salida.write_text(resultado, encoding="utf-8")
104
+ print(f"OK: {salida} ({len(resultado.encode('utf-8')) / 1024:.0f} KB)")
105
+ return 0
106
+
107
+
108
+ if __name__ == "__main__":
109
+ raise SystemExit(main(sys.argv))
@@ -0,0 +1,345 @@
1
+ #!/usr/bin/env python3
2
+ """Comprueba que un HTML cumple el contrato de artifact de NappAI.
3
+
4
+ Contrato (nivel 0): el artifact es autocontenido y NO habla con nadie. Cada
5
+ regla de aqui espeja una directiva del CSP con que se sirve; si se cambia una,
6
+ se cambia la otra.
7
+
8
+ OJO: esto es analisis estatico y se puede enganar (JS ofuscado). La garantia
9
+ real es el CSP de la respuesta, que lo impone el navegador. Esto da
10
+ realimentacion rapida al modelo, no seguridad.
11
+
12
+ Solo libreria estandar: corre en un contenedor pelado sin pip.
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import re
17
+ import sys
18
+ from html.parser import HTMLParser
19
+ from urllib.parse import urlparse
20
+
21
+ TOPE_BYTES = 5 * 1024 * 1024
22
+
23
+ CDN_SCRIPT = {"cdnjs.cloudflare.com", "cdn.jsdelivr.net"}
24
+ CDN_ESTILO = CDN_SCRIPT | {"fonts.googleapis.com"}
25
+ CDN_FUENTE = {"fonts.gstatic.com"}
26
+
27
+ _RED = [
28
+ (re.compile(r"\bfetch\s*\("), "fetch()"),
29
+ (re.compile(r"\bXMLHttpRequest\b"), "XMLHttpRequest"),
30
+ (re.compile(r"\bWebSocket\s*\("), "WebSocket"),
31
+ (re.compile(r"\bEventSource\s*\("), "EventSource"),
32
+ (re.compile(r"\bsendBeacon\s*\("), "navigator.sendBeacon()"),
33
+ (re.compile(r"\bimport\s*\("), "import() dinamico"),
34
+ ]
35
+
36
+
37
+ # Almacenamiento del navegador. **Estas tres LANZAN en el sandbox del widget**, que pinta el
38
+ # artifact en un iframe con `sandbox="allow-scripts allow-downloads allow-modals"` y SIN
39
+ # `allow-same-origin`: el documento tiene origen opaco y merely LEER una de ellas da
40
+ #
41
+ # Failed to read the 'localStorage' property from 'Window':
42
+ # The document is sandboxed and lacks the 'allow-same-origin' flag.
43
+ #
44
+ # Es la MISMA clase que `fetch`: no es un gusto, es que el navegador lo rompe. Y es PEOR que
45
+ # `fetch`, porque el throw se lleva el bloque `<script>` entero y el fallo es MUDO --- medido en
46
+ # produccion el 2026-08-31: la pagina se veia perfecta y el diagrama era un rectangulo vacio.
47
+ _ALMACEN = ("localStorage", "sessionStorage", "indexedDB")
48
+
49
+ #: Ventanas alrededor de un acceso donde se busca su `try`/`catch`. Es analisis de texto y se
50
+ #: puede enganar --- como todo este fichero, que lo dice en su docstring--- pero caza el caso
51
+ #: comun: el acceso suelto, sin nada que lo envuelva.
52
+ _ANTES = 240
53
+ _DESPUES = 400
54
+
55
+
56
+ def _accesos_sin_guarda(html: str) -> list[str]:
57
+ """Los accesos a almacenamiento que NO parecen envueltos en `try`/`catch`.
58
+
59
+ Para cada aparicion se mira hacia atras si hay un `try` cerca y hacia delante si hay un
60
+ `catch`. No es un parser de JS --- no lo hay en la libreria estandar y este fichero corre en
61
+ un contenedor pelado--- asi que se elige el error del lado seguro: se avisa del acceso
62
+ suelto, y un acceso envuelto de forma rara puede colarse. La alternativa (prohibirlo del
63
+ todo) obligaria a renunciar a recordar una pestana o un filtro por un fallo que tiene
64
+ arreglo de una linea.
65
+ """
66
+ fuera: list[str] = []
67
+ for api in _ALMACEN:
68
+ for m in re.finditer(rf"\b{api}\b", html):
69
+ antes = html[max(0, m.start() - _ANTES) : m.start()]
70
+ despues = html[m.end() : m.end() + _DESPUES]
71
+ if "try" in antes and ("catch" in despues or "catch" in antes):
72
+ continue
73
+ fuera.append(api)
74
+ break # uno por API basta: el mensaje es el mismo
75
+ return fuera
76
+
77
+
78
+ def _host(url: str) -> str | None:
79
+ """Host de una URL absoluta; None si es relativa, data: o blob:."""
80
+ url = (url or "").strip()
81
+ if not url or url.startswith(("data:", "blob:", "#")):
82
+ return None
83
+ p = urlparse(url)
84
+ if p.scheme in ("http", "https") or url.startswith("//"):
85
+ return (p.netloc or "").lower() or None
86
+ return None
87
+
88
+
89
+ class _Escaner(HTMLParser):
90
+ def __init__(self) -> None:
91
+ super().__init__(convert_charrefs=True)
92
+ self.problemas: list[str] = []
93
+
94
+ def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
95
+ a = {k.lower(): (v or "") for k, v in attrs}
96
+
97
+ if tag == "base":
98
+ self.problemas.append("<base> no esta permitido: reescribe el destino de los enlaces.")
99
+ return
100
+
101
+ if tag == "script":
102
+ h = _host(a.get("src", ""))
103
+ if h and h not in CDN_SCRIPT:
104
+ self.problemas.append(
105
+ f"<script src> de '{h}': solo se permiten {sorted(CDN_SCRIPT)}."
106
+ )
107
+ elif tag == "link":
108
+ h = _host(a.get("href", ""))
109
+ if h and h not in CDN_ESTILO | CDN_FUENTE:
110
+ self.problemas.append(
111
+ f"<link href> de '{h}': solo se permiten {sorted(CDN_ESTILO | CDN_FUENTE)}."
112
+ )
113
+ elif tag in ("img", "video", "audio", "source", "embed"):
114
+ h = _host(a.get("src", ""))
115
+ if h:
116
+ self.problemas.append(
117
+ f"<{tag} src> de '{h}': usa data: o blob:. Un src externo saca datos "
118
+ "por la query string."
119
+ )
120
+ elif tag == "iframe":
121
+ if _host(a.get("src", "")):
122
+ self.problemas.append("<iframe src> externo no esta permitido.")
123
+ elif tag == "form":
124
+ if _host(a.get("action", "")):
125
+ self.problemas.append("<form action> externo no esta permitido.")
126
+
127
+
128
+ #: Etiquetas de mermaid con PARENTESIS sin comillas. Es un error de SINTAXIS, no de estilo, y
129
+ #: rompe el diagrama entero: la pagina carga perfecta y en su sitio sale «Syntax error in text».
130
+ #:
131
+ #: **Medido contra mermaid 10.9.0 en un navegador, con `mermaid.parse` y su control**
132
+ #: (2026-09-01, sobre un panel real que fallo asi):
133
+ #:
134
+ #: | construccion | |
135
+ #: |---|---|
136
+ #: | `A[Uno] --> B[Dos]` | OK |
137
+ #: | `A[Mapeos (mapcol)]` | **ROMPE** |
138
+ #: | `A["Mapeos (mapcol)"]` | OK |
139
+ #: | `subgraph Vistas (contents)` | **ROMPE** |
140
+ #: | `subgraph S1 [Vistas (contents)]` | **ROMPE** |
141
+ #: | `subgraph "Vistas (contents)"` | OK |
142
+ #: | `A[Salida ##EXIT##]`, `A[LoginColl.xne]`, acentos, `:`, `@` | OK |
143
+ #:
144
+ #: **Las almohadillas estan INOCENTES** y es la segunda vez que se acusan en falso: `##EXIT##`
145
+ #: parsea sin problema. Lo que rompe son los parentesis, y en un proyecto XOne salen solos
146
+ #: (`(mapcol)`, `(contents)`), asi que esto no es un caso raro: es el caso normal.
147
+ #: Con que empieza un diagrama de mermaid, para reconocerlo dentro de una plantilla de JS.
148
+ _EMPIEZA_DIAGRAMA = re.compile(
149
+ r"\s*(?:%%\{.*?\}%%\s*)?"
150
+ r"(?:graph|flowchart|sequenceDiagram|stateDiagram(?:-v2)?|erDiagram|classDiagram|"
151
+ r"journey|gantt|pie|mindmap|timeline)\b",
152
+ re.S,
153
+ )
154
+
155
+ _MERMAID_NODO = re.compile(r"\[([^\]\n]*)\]")
156
+ _MERMAID_SUBGRAPH = re.compile(r"^[ \t]*subgraph[ \t]+(.*)$", re.M)
157
+ #: Los tramos ENTRE COMILLAS, que es lo que hay que quitar antes de buscar un parentesis.
158
+ #:
159
+ #: **Y esto es el arreglo de un falso positivo, cazado a los diez minutos de escribir el
160
+ #: check**: la primera version exigia la comilla PEGADA al corchete (`\[(?!["\'])`), asi que
161
+ #: `Header ["Cabecera (frmTop)"]` --- con un espacio en medio, que es como lo escribe el modelo
162
+ #: --- se leia como sin comillas y el validador RECHAZABA un panel correcto. Un aviso que
163
+ #: desmiente trabajo bueno hace mas daño que uno que falta: el constructor habria reescrito
164
+ #: tres veces algo que ya estaba bien y habria acabado sin publicar.
165
+ _ENTRE_COMILLAS = re.compile(r"\"[^\"]*\"|'[^']*'")
166
+
167
+ #: Los delimitadores de FORMA en los bordes de una etiqueta. `A[("x")]` es un cilindro (base de
168
+ #: datos), `A([\"x\"])` un estadio, `A[[\"x\"]]` un subproceso: los parentesis y corchetes de los
169
+ #: extremos son ESTRUCTURA, no texto.
170
+ #:
171
+ #: **Segundo falso positivo de este check, y otra vez habria bloqueado un panel correcto**: sin
172
+ #: quitarlos, `A[("Coleccion Usuarios")]` se leia como una etiqueta con parentesis sin comillas
173
+ #: --- las comillas estaban DENTRO de la forma --- y el validador lo rechazaba. Los dos falsos
174
+ #: positivos han venido de lo mismo: mirar la cadena cruda en vez de la ETIQUETA.
175
+ _BORDES_DE_FORMA = re.compile(r"^[\[\(\{>/\\]+|[\]\)\}/\\]+$")
176
+
177
+
178
+ def _solo_la_etiqueta(cruda: str) -> str:
179
+ """El texto de la etiqueta: sin los delimitadores de forma y sin los tramos citados."""
180
+ return _ENTRE_COMILLAS.sub("", _BORDES_DE_FORMA.sub("", cruda.strip()))
181
+
182
+
183
+ def _sin_lo_citado(texto: str) -> str:
184
+ """El texto con los tramos entre comillas fuera. Lo que quede es lo NO citado."""
185
+ return _ENTRE_COMILLAS.sub("", texto)
186
+
187
+
188
+ def _bloques_mermaid(html: str) -> list[str]:
189
+ """El texto de cada diagrama mermaid del HTML.
190
+
191
+ Se buscan las DOS formas en que llega: el `<pre class="mermaid">`/`<div class="mermaid">`
192
+ del render automatico y la cadena que se le pasa a `mermaid.render`/`mermaid.parse` desde
193
+ JS. Con solo la primera, un panel que construye el diagrama en una constante de JavaScript
194
+ --- que es lo que hacen la mitad de los que se han visto --- pasaria sin mirar.
195
+ """
196
+ fuera: list[str] = []
197
+ for m in re.finditer(
198
+ r"<(?:pre|div)[^>]*class=[\"'][^\"']*mermaid[^\"']*[\"'][^>]*>(.*?)</(?:pre|div)>",
199
+ html,
200
+ re.S | re.I,
201
+ ):
202
+ fuera.append(m.group(1))
203
+ # **La segunda forma: el diagrama dentro de una PLANTILLA de JS** (`const src = \`graph TD
204
+ # …\`;`), que es como lo escribe la mitad de los paneles. Se acota a la plantilla completa
205
+ # —de comilla invertida a comilla invertida— y **eso es el arreglo del TERCER falso positivo
206
+ # de este check**: la version anterior buscaba `graph TD` y se llevaba hasta 20.000 chars
207
+ # «hasta la comilla o el `</script>`», asi que cuando el diagrama no estaba en una plantilla
208
+ # se tragaba el CSS de Tailwind que venia detras y marcaba `rounded-[var(--radius)]` como una
209
+ # etiqueta con parentesis sin comillas. Rechazaba un panel correcto.
210
+ #
211
+ # Los tres falsos positivos de este check han salido de lo mismo: **mirar texto que no es una
212
+ # etiqueta de mermaid.** Delimitar bien lo que se mira vale mas que afinar el patron.
213
+ for m in re.finditer(r"`([^`]*)`", html, re.S):
214
+ cuerpo = m.group(1)
215
+ if _EMPIEZA_DIAGRAMA.match(cuerpo):
216
+ fuera.append(cuerpo)
217
+ return fuera
218
+
219
+
220
+ def _mermaid_roto(html: str) -> list[str]:
221
+ """Etiquetas de mermaid con un parentesis FUERA de comillas, con la culpable delante."""
222
+ problemas: list[str] = []
223
+ vistas: set[str] = set()
224
+ for bloque in _bloques_mermaid(html):
225
+ candidatas = [m.group(1) for m in _MERMAID_NODO.finditer(bloque)]
226
+ candidatas += [m.group(1) for m in _MERMAID_SUBGRAPH.finditer(bloque)]
227
+ for cruda in candidatas:
228
+ if "(" not in _solo_la_etiqueta(cruda):
229
+ continue
230
+ etiqueta = cruda.strip()[:80]
231
+ if etiqueta in vistas:
232
+ continue
233
+ vistas.add(etiqueta)
234
+ problemas.append(
235
+ f"Etiqueta de mermaid con parentesis SIN comillas: `{etiqueta}`. mermaid 10.9 "
236
+ f"no la parsea y el diagrama entero sale como «Syntax error in text» (medido "
237
+ f"con mermaid.parse). Pon el TEXTO entre comillas dobles: `[\"...\"]` en un "
238
+ f"nodo, `subgraph \"...\"` en un subgrafo."
239
+ )
240
+ return problemas
241
+
242
+
243
+ def validar(html: str) -> tuple[bool, list[str]]:
244
+ """Devuelve (ok, problemas). Cada problema dice que hacer, no solo que pasa."""
245
+ problemas: list[str] = []
246
+
247
+ tam = len(html.encode("utf-8"))
248
+ if tam > TOPE_BYTES:
249
+ problemas.append(
250
+ f"El artifact ocupa {tam / 1024 / 1024:.1f} MB y el tope son 5 MB. "
251
+ "Reduce los datos horneados o baja la resolucion de las imagenes."
252
+ )
253
+
254
+ for patron, nombre in _RED:
255
+ if patron.search(html):
256
+ problemas.append(
257
+ f"Usa {nombre}: el artifact es autocontenido y no puede hablar con el "
258
+ "servidor. Hornea los datos dentro del HTML."
259
+ )
260
+
261
+ problemas.extend(_mermaid_roto(html))
262
+
263
+ for api in _accesos_sin_guarda(html):
264
+ problemas.append(
265
+ f"Usa {api} sin try/catch: en el sandbox del visor LANZA "
266
+ f"(«The document is sandboxed and lacks the 'allow-same-origin' flag») y se lleva "
267
+ f"el bloque <script> entero, asi que la pagina se rompe EN SILENCIO. Para el tema "
268
+ f"basta matchMedia('(prefers-color-scheme: dark)'). Si necesitas guardar algo, "
269
+ f"envuelve cada acceso: try {{ {api}.getItem(k) }} catch {{}}."
270
+ )
271
+
272
+ escaner = _Escaner()
273
+ try:
274
+ escaner.feed(html)
275
+ except AssertionError as e: # lo unico que HTMLParser levanta con markup patologico
276
+ problemas.append(f"El HTML no se puede parsear: {e}")
277
+ problemas.extend(escaner.problemas)
278
+
279
+ return (not problemas), problemas
280
+
281
+
282
+ def avisos(html: str) -> list[str]:
283
+ """Calidad, no rotura: se publica igual y se le dice.
284
+
285
+ **La distincion es la que hace que el contrato signifique algo.** Si un `alt` que falta
286
+ bloqueara la publicacion igual que una pagina que no arranca, el modelo aprenderia a leer la
287
+ lista entera como ruido. Aqui van las cosas que hacen el panel peor sin hacerlo imposible.
288
+ """
289
+ fuera: list[str] = []
290
+ bajo = html.lower()
291
+
292
+ if not re.search(r"<!doctype\s+html", html, flags=re.IGNORECASE):
293
+ fuera.append("falta el doctype de HTML5: el navegador entra en modo quirks.")
294
+ if not re.search(r"<html[^>]*\slang=", html, flags=re.IGNORECASE):
295
+ fuera.append('falta `lang` en <html>: los lectores de pantalla no saben en que idioma leer.')
296
+ if "name=\"viewport\"" not in bajo and "name='viewport'" not in bajo:
297
+ fuera.append("falta el meta viewport: en movil se vera reducido al 25%.")
298
+ if not re.search(r"<title>\s*\S", html, flags=re.IGNORECASE):
299
+ fuera.append("falta un <title> con contenido: es el nombre del panel en la pestana.")
300
+
301
+ sin_alt = [
302
+ m.group(0)
303
+ for m in re.finditer(r"<img\b[^>]*>", html, flags=re.IGNORECASE)
304
+ if not re.search(r"\salt=", m.group(0), flags=re.IGNORECASE)
305
+ ]
306
+ if sin_alt:
307
+ fuera.append(f"{len(sin_alt)} imagen(es) sin `alt`.")
308
+
309
+ botones_mudos = len(
310
+ [m for m in re.finditer(r"<button\b[^>]*>\s*</button>", html, flags=re.IGNORECASE)]
311
+ )
312
+ if botones_mudos:
313
+ fuera.append(
314
+ f"{botones_mudos} boton(es) sin texto ni etiqueta accesible: usa texto o aria-label."
315
+ )
316
+
317
+ ids = re.findall(r"\sid=[\"']([^\"']+)[\"']", html)
318
+ repetidos = sorted({i for i in ids if ids.count(i) > 1})
319
+ if repetidos:
320
+ fuera.append(f"ids repetidos: {', '.join(repetidos[:5])}.")
321
+
322
+ interactivo = "<button" in bajo or "<a " in bajo
323
+ if interactivo and not re.search(r":focus-visible|:focus\b", html, flags=re.IGNORECASE):
324
+ fuera.append("ningun estilo de foco visible: con teclado no se ve donde estas.")
325
+
326
+ return fuera
327
+
328
+
329
+ def main(argv: list[str]) -> int:
330
+ if len(argv) != 2:
331
+ print("uso: validate_artifact.py <fichero.html>", file=sys.stderr)
332
+ return 2
333
+ with open(argv[1], encoding="utf-8") as fh:
334
+ ok, problemas = validar(fh.read())
335
+ if ok:
336
+ print("OK: el artifact cumple el contrato.")
337
+ return 0
338
+ print("El artifact NO cumple el contrato:")
339
+ for p in problemas:
340
+ print(f" - {p}")
341
+ return 1
342
+
343
+
344
+ if __name__ == "__main__":
345
+ raise SystemExit(main(sys.argv))
@@ -0,0 +1,185 @@
1
+ ---
2
+ name: xone-hotswap
3
+ description: Probar una app XOne en un dispositivo o emulador LOCAL —Android o iOS— a través del servidor hotswap que la propia app host levanta en el dispositivo. Usar al desplegar una app en un móvil o simulador, al reiniciarla para que aplique cambios, al capturar la pantalla o el árbol de controles, al pulsar o rellenar controles desde fuera, al leer el log o consultar la base de datos del dispositivo, y al diagnosticar por qué el dispositivo no responde en el puerto 8443. Cubre las dos plataformas y dice, comando por comando, cuál de las dos lo tiene.
4
+ ---
5
+
6
+ # Probar una app XOne en un dispositivo local (hotswap)
7
+
8
+ El interlocutor es la **app host ya instalada** en el dispositivo. No se compila ni se instala
9
+ un paquete por iteración: se le suben los ficheros del proyecto, se reinicia y se mira.
10
+
11
+ | | Android | iOS |
12
+ |---|---|---|
13
+ | App host | **XoneStudio** (`com.xone.android.framework`, o `com.xone.android.developer.framework` en el flavor de Play Store) | **XOneStudioSwift** (`es.xone.studioapp.swift`) |
14
+ | Solo existe en | builds *debuggable* | el runtime en **modo desarrollo** (`XoneAppDeveloment`); en una app de producción no existe |
15
+
16
+ El servidor vive **dentro del proceso de la app**: si la app no está viva, el puerto no
17
+ responde. Habla dos protocolos por el mismo puerto: **WebSocket** para los comandos
18
+ (`{"command": "..."}` → `{"result": ..., "status": ...}`) y **HTTP(S)** para lo demás.
19
+
20
+ ## Lo que hay que saber antes de tocar nada
21
+
22
+ - **HTTPS con certificado autofirmado** en las dos plataformas. Todas las URLs son `https://`;
23
+ con `curl` hace falta `-k`. Solo escucha en `http://` si alguien desactivó la conexión segura.
24
+ - **El puerto 8443 es el POR DEFECTO, no una garantía.** En Android, si está ocupado —otra APK
25
+ del framework instalada— el servidor coge el siguiente libre, y el real se ve en la pantalla
26
+ del servidor hotswap (pestaña Información).
27
+ - **Subir NO aplica.** El proceso tiene cargado en memoria lo de antes: hay que reiniciar la app.
28
+ - **La base de datos va cifrada con SQLCipher.** Subir un `.db` en claro termina en
29
+ `database disk image is malformed (code 11)`.
30
+ - **En iOS, `POST /command` sirve solo los comandos de automatización** — los de los bloques
31
+ **Ver la pantalla** y **Tocar**. Los comandos legados (`launchApplication`, `setAttribute`,
32
+ `listAppFiles`…) son **solo WebSocket**: sus manejadores responden escribiendo en el socket, que
33
+ en una petición HTTP no existe, así que por HTTP contestan `Unknown command '<x>'` aunque el
34
+ comando exista. Cuál es cuál, comando a comando: la columna **Canal (iOS)** de la
35
+ [matriz](references/comandos.md#matriz-de-disponibilidad).
36
+
37
+ ## Cómo llegar, según la plataforma
38
+
39
+ **Android** — por `adb forward`, y entonces la IP es `127.0.0.1` (el localhost del PC, que adb
40
+ tuneliza al dispositivo). Con varios dispositivos, `adb -s <serial>`:
41
+
42
+ ```bash
43
+ adb devices -l
44
+ adb shell am start -n com.xone.android.framework/com.xone.android.hotswap.activities.SetupActivity
45
+ adb forward tcp:8443 tcp:8443 # reaplicar tras cada reconexión del cable
46
+ ```
47
+
48
+ **iOS** — **no hay `adb` ni túnel**. El simulador comparte la pila de red del Mac, así que el
49
+ servidor es alcanzable directamente; en dispositivo físico se llega por la IP de la LAN que
50
+ muestra la propia app:
51
+
52
+ ```bash
53
+ xcrun simctl list devices booted
54
+ xcrun simctl launch <UDID> es.xone.studioapp.swift # arrancar el host levanta el servidor
55
+ curl -sk -X POST https://localhost:8443/command -d '{"command":"getAllElements"}'
56
+ ```
57
+
58
+ Detalle de despliegue, relanzado y endpoints de fichero: [conexión y despliegue](references/conexion-y-despliegue.md).
59
+
60
+ ## Qué hay en cada plataforma, de un vistazo
61
+
62
+ | Bloque | Android | iOS |
63
+ |---|---|---|
64
+ | Ver la pantalla (`getAllElements`, `getCurrentScreen`, `getScreenshot`, `getText`, `getUiAttribute`, `getRows`, `isVisible`, `isEnabled`, `waitForElement`) | sí | **sí**, con desviaciones (abajo) |
65
+ | Tocar (`click`, `tap`, `doubleTap`, `longPress`, `openSelector`, `fill`, `clear`, `scroll`, `pressKey`) | sí | **sí**, con desviaciones (abajo) |
66
+ | Ciclo de app (`launchApplication`, `exitApplication`, `directdownload`) | sí | `launchApplication` y `directdownload` sí; `exitApplication` no |
67
+ | Atributos y colecciones (`setAttribute`, `deleteAttribute`, `loadCollection`) | sí | sí |
68
+ | CSS, `replaceProperty`, `refresh`, `relayout`, `loadIncludeFile` | sí | **no** |
69
+ | Ficheros (`/file_upload`, `/file_download`, `uploadFile`, `getFileChecksum`) | sí | **no** (`listappfiles` sí) |
70
+ | Scripts y SQL (`runScript`, `runSql`, `getDatabaseTableNames`, `queryDatabaseTable`) | sí | **no** |
71
+ | Logs (`getLog`, `*DeviceLogging`, `setLog*`) | sí | **no** |
72
+ | Mock de hardware (`setMockData`, `clearMockData`) | sí | **no** |
73
+ | Edición en vivo (`enable/disableLiveEditionMode`) | sí | **no** |
74
+ | Depurador JS (`debug`) | sí | **no** (JavaScriptCore no tiene API pública de depuración) |
75
+
76
+ **Lo que en 5.0.2.2dev NO existe, medido el 16-sep-2026** (flavor standalone, Android, **con una app
77
+ ya cargada y viva**): `getCurrentScreen` y `status`/`state` contestan `Unknown command`, aunque esta
78
+ tabla los dé por disponibles. Para ver la pantalla, lo que responde es `getAllElements` —el árbol de
79
+ controles— y `getScreenshot` —el PNG en base64—. Y **el nombre de un comando es EXACTO**: no hay
80
+ alias insensible a mayúsculas. `launchApplication`, `setAttribute` y `listAppFiles` existen; sus
81
+ variantes en minúscula contestan `Unknown command`, que es como se descubrió.
82
+
83
+ Comando por comando, con su contrato y sus desviaciones: [comandos](references/comandos.md).
84
+
85
+ Los doce comandos que apuntan por nombre (`getText`, `isVisible`, `isEnabled`, `waitForElement`,
86
+ `click`, `tap`, `doubleTap`, `longPress`, `openSelector`, `fill`, `clear`, `scroll`) aceptan
87
+ además **`row` / `content` / `where`** para apuntar a una **fila**: por índice, por content, o por
88
+ **lo que contiene** (`{"ID":"2938733"}`). Solo existen las filas **pintadas**; el índice es el de
89
+ la colección y **no se renumera con el scroll**; y `waitForElement` con `row` espera a que la
90
+ fila **esté pintada**, no a que la colección tenga datos. Para leer las filas que NO están a la
91
+ vista, `getRows` — que lee el modelo, no las vistas.
92
+
93
+ ## Lo que iOS no puede hacer IGUAL, y por qué
94
+
95
+ No son carencias de implementación: son límites y consecuencias de la plataforma, y el canal las
96
+ responde con un motivo en vez de fingir. **Declarar lo que iOS no puede hacer igual vale más que
97
+ fingir paridad**: con la diferencia delante se puede trabajar; con una promesa falsa, no.
98
+
99
+ - **No hay inyección de toques ni de teclas.** `click` invoca el manejador del control (como el
100
+ `performClick()` de Android), así que **`click` y `tap` colapsan** en el mismo efecto, y
101
+ `doubleTap`/`longPress` **siempre se niegan**: el runtime iOS no tiene eventos XONE de doble
102
+ toque ni de pulsación larga en props/frames. `pressKey` solo traduce `4`/`back` y `66`/`enter`
103
+ (con `key` simbólico: `back`, `enter` y `done`).
104
+ Por eso `has no click handler` va **sin** la coletilla `use 'tap' to send a real touch` de
105
+ Android: mandaría a repetir la llamada que acaba de fallar.
106
+ - **No hay codificación WebP.** `getScreenshot` devuelve JPEG al 50% (o PNG si se pide) y lo
107
+ declara en `imageFormat`.
108
+ - **La captura no lo captura todo**: quedan fuera la cámara en vivo, las capas Metal/GL, las
109
+ teselas de mapa y la barra de estado del sistema (otro proceso).
110
+ - **Los memos multilínea y los campos de fecha/hora no los escribe ESTE CANAL**, y lo dicen así:
111
+ `Element 'X' is editable, but this channel cannot write a multiline|date/time field`. El usuario
112
+ sí puede editarlos — no es un control equivocado, es un límite del canal.
113
+ - **Los dos volcados NO son compatibles atributo a atributo.** iOS emite **8** atributos y Android
114
+ **19**, con solo **5 comunes** (`index`, `class`, `text`, `enabled`, `bounds`). iOS no tiene
115
+ `resource-id` —con el que Android nombra las piezas de un diálogo— ni `content-desc` ni los
116
+ booleanos (`checkable`, `clickable`, `scrollable`…), y tiene tres propios (`accessibility-id`,
117
+ `label`, `visible`). En iOS el canónico es **`uikit`**; `uiautomator` se acepta como **alias**
118
+ para que una llamada multiplataforma no falle, pero **devuelve datos con forma de iOS**.
119
+ - **La búsqueda mira primero lo que está ENCIMA** (diálogos, toasts) y la pantalla al final.
120
+ Alcanzar un control tapado **nombrándolo** sigue permitido; lo que cambió es el orden. Los
121
+ niveles **anteriores** de navegación **no se buscan**: un `not found` sobre un control de la
122
+ pantalla que dejaste atrás significa que todavía no has navegado.
123
+
124
+ Las **once divergencias con Android**, con su porqué:
125
+ [comandos → Divergencias](references/comandos.md#divergencias-con-android).
126
+
127
+ ## Dos avisos sobre el host iOS que te vas a encontrar
128
+
129
+ Ninguno es del hotswap; los dos te van a costar una sesión si no los sabes.
130
+
131
+ - **`MyAllXOne` crashea al arrancar** en el host (GoogleMaps). Para probar **filas**, el fixture
132
+ usable es **`FontIconsApp`**.
133
+ - **El host se cae al abrir una lista de menú**: le faltan los `xone_img_*.png`, y
134
+ `CollectionViewController`/`SortView` meten `UIImage` nil en arrays. Es un defecto **ajeno** al
135
+ hotswap. Si el puerto deja de responder justo después de abrir un menú, es esto.
136
+
137
+ ## Cómo se prueba, y qué cuenta como evidencia
138
+
139
+ 1. **Reproduce el estado**: lanza la app y espera al control que marca la pantalla lista
140
+ (`waitForElement`), nunca a un temporizador fijo.
141
+ 2. **Pregunta dónde estás** (`getCurrentScreen`, **iOS**) tras cada navegación: dice la pantalla,
142
+ su colección, qué diálogos hay encima y cuál se lleva los toques. Es una respuesta corta, y
143
+ evita interpretar un volcado para averiguar lo mismo.
144
+ 3. **Mira el árbol antes de tocar** (`getAllElements`). El formato `xone` da los controles con su
145
+ nombre XOne, que es por el que se pulsa; el otro formato da el volcado de la jerarquía de
146
+ vistas, útil para distinguir "ese control no existe" de "existe pero el motor no lo indexa".
147
+ Para el contenido de una **lista**, `getRows`: el volcado solo trae las filas pintadas.
148
+ 4. **Actúa por NOMBRE de control** (`click`, `tap`, `fill`, `scroll`), no por coordenadas: las
149
+ coordenadas cambian con el tamaño de pantalla y no dicen qué se intentaba pulsar.
150
+ 5. **Comprueba con un dato, no con una impresión**: `getText`, `isVisible`, `isEnabled`, y el
151
+ propio `getAllElements` antes y después. Una captura sirve para **enseñar** el fallo, no para
152
+ afirmar que algo funciona — nadie puede releerla.
153
+ 6. **Si algo falla, trae el log** (Android) o la respuesta literal del comando (las dos
154
+ plataformas) y la línea concreta. «No funciona» no es un hallazgo.
155
+
156
+ ## Reglas que evitan diagnósticos falsos
157
+
158
+ - **No inventes controles.** Si `getAllElements` no lo lista, no existe en esa pantalla: dilo en
159
+ vez de probar nombres a ver si suena alguno.
160
+ - **Un `result: false` es el resultado**, no un error del que recuperarse en silencio. Su `status`
161
+ dice exactamente qué pasó. En iOS, un control **ausente** ya no se confunde con uno
162
+ **intocable**, y cada mensaje manda a un sitio distinto: `not found` (corrige el nombre) ·
163
+ `is not enabled` (rellena antes la pantalla) · `has editing disabled` (desbloquéalo en la app).
164
+ El catálogo entero, con qué arregla cada uno:
165
+ [comandos → El catálogo de errores](references/comandos.md#el-catálogo-de-errores-desdoblado-ios).
166
+ - **Para saber si un campo se puede escribir, mira `editDisabled`, no `enabled`.** `enabled`
167
+ devuelve lo mismo en un campo bloqueado y en uno que no se escribe nunca; `editDisabled:true`
168
+ (que solo se emite cuando es cierto) es el único que los separa. Y su **ausencia no promete**
169
+ que el `fill` funcione: un memo o un campo de fecha salen sin él y este canal tampoco los
170
+ escribe.
171
+ - **Distingue «la app no está viva» de «la prueba falla».** Conexión rechazada en el puerto es lo
172
+ primero. En iOS hay además una distinción propia: `"App is not running"` significa que no hay
173
+ ninguna app XONE lanzada, mientras que `"Screen '<clase>' is not readable by the 'xone' format"`
174
+ significa que sí la hay, pero su pantalla es de un camino de render que ese formato no lee.
175
+ - **Después de subir, reinicia.** Un resultado medido sin reiniciar está midiendo la versión
176
+ anterior, y es el error más caro de todos porque no da ningún síntoma.
177
+ - **No dejes el dispositivo peor de como estaba**: `clearMockData` tras usar datos simulados
178
+ (Android), y `disableDeviceLogging` / `setLogDisabled` tras acabar.
179
+
180
+ ## Referencias
181
+
182
+ | Qué | Dónde |
183
+ |---|---|
184
+ | Los comandos, uno a uno: entrada, salida, errores y qué plataforma lo tiene | [references/comandos.md](references/comandos.md) |
185
+ | Llegar al dispositivo, desplegar una app, relanzarla, y los endpoints de fichero | [references/conexion-y-despliegue.md](references/conexion-y-despliegue.md) |