xonecode 0.5.1 → 0.8.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 (315) hide show
  1. package/apps/web/dist/assets/{index-BB3SamhR.css → index-Boak51UP.css} +1 -1
  2. package/apps/web/dist/assets/index-Cg28x6nG.js +477 -0
  3. package/apps/web/dist/assets/{php-D_2--9WK.js → php-Cu6F_QCJ.js} +1 -1
  4. package/apps/web/dist/assets/{ruby-DSHSDbk1.js → ruby-BiqQPoC6.js} +1 -1
  5. package/apps/web/dist/index.html +2 -2
  6. package/dist/agent/{cloudstudioClient.js → cloudstudio/cloudstudioClient.js} +10 -0
  7. package/dist/agent/cloudstudio/cloudstudioClient.js.map +1 -0
  8. package/dist/agent/{cloudstudioMcp.js → cloudstudio/cloudstudioMcp.js} +5 -5
  9. package/dist/agent/cloudstudio/cloudstudioMcp.js.map +1 -0
  10. package/dist/agent/{descarga.js → cloudstudio/descarga.js} +17 -7
  11. package/dist/agent/cloudstudio/descarga.js.map +1 -0
  12. package/dist/agent/cloudstudio/manifiesto.js.map +1 -0
  13. package/dist/agent/cloudstudio/ramaActiva.js +11 -0
  14. package/dist/agent/cloudstudio/ramaActiva.js.map +1 -0
  15. package/dist/agent/{subida.js → cloudstudio/subida.js} +16 -7
  16. package/dist/agent/cloudstudio/subida.js.map +1 -0
  17. package/dist/agent/{zip.js → cloudstudio/zip.js} +1 -1
  18. package/dist/agent/cloudstudio/zip.js.map +1 -0
  19. package/dist/agent/{authEnDisco.js → config/authEnDisco.js} +3 -3
  20. package/dist/agent/config/authEnDisco.js.map +1 -0
  21. package/dist/agent/{catalogoModelos.js → config/catalogoModelos.js} +47 -1
  22. package/dist/agent/config/catalogoModelos.js.map +1 -0
  23. package/dist/agent/{configEnDisco.js → config/configEnDisco.js} +6 -6
  24. package/dist/agent/config/configEnDisco.js.map +1 -0
  25. package/dist/agent/{crearProyecto.js → config/crearProyecto.js} +2 -2
  26. package/dist/agent/config/crearProyecto.js.map +1 -0
  27. package/dist/agent/config/entorno.js.map +1 -0
  28. package/dist/agent/config/gemini.js.map +1 -0
  29. package/dist/agent/config/modelos.js +235 -0
  30. package/dist/agent/config/modelos.js.map +1 -0
  31. package/dist/agent/{modelosDeMotor.js → config/modelosDeMotor.js} +1 -1
  32. package/dist/agent/config/modelosDeMotor.js.map +1 -0
  33. package/dist/agent/config/mudanzaEnDisco.js +121 -0
  34. package/dist/agent/config/mudanzaEnDisco.js.map +1 -0
  35. package/dist/agent/{persona.js → config/persona.js} +1 -1
  36. package/dist/agent/config/persona.js.map +1 -0
  37. package/dist/agent/config/selectorEnMaquina.js +45 -0
  38. package/dist/agent/config/selectorEnMaquina.js.map +1 -0
  39. package/dist/agent/{settingsEnDisco.js → config/settingsEnDisco.js} +37 -2
  40. package/dist/agent/config/settingsEnDisco.js.map +1 -0
  41. package/dist/agent/{versionEnDisco.js → config/versionEnDisco.js} +8 -4
  42. package/dist/agent/config/versionEnDisco.js.map +1 -0
  43. package/dist/agent/dispositivos/arranqueDeEmulador.js +146 -0
  44. package/dist/agent/dispositivos/arranqueDeEmulador.js.map +1 -0
  45. package/dist/agent/{dispositivosEnMaquina.js → dispositivos/dispositivosEnMaquina.js} +93 -6
  46. package/dist/agent/dispositivos/dispositivosEnMaquina.js.map +1 -0
  47. package/dist/agent/dispositivos/hotswap.js.map +1 -0
  48. package/dist/agent/dispositivos/instalacionEnMaquina.js.map +1 -0
  49. package/dist/agent/dispositivos/juezVisual.js +152 -0
  50. package/dist/agent/dispositivos/juezVisual.js.map +1 -0
  51. package/dist/agent/{lanzamientoEnMaquina.js → dispositivos/lanzamientoEnMaquina.js} +1 -1
  52. package/dist/agent/dispositivos/lanzamientoEnMaquina.js.map +1 -0
  53. package/dist/agent/{paqueteDelProyecto.js → dispositivos/paqueteDelProyecto.js} +5 -5
  54. package/dist/agent/dispositivos/paqueteDelProyecto.js.map +1 -0
  55. package/dist/agent/dispositivos/procesosEnMaquina.js.map +1 -0
  56. package/dist/agent/dispositivos/sensorDeArranque.js +154 -0
  57. package/dist/agent/dispositivos/sensorDeArranque.js.map +1 -0
  58. package/dist/agent/exportarPdf.js +140 -0
  59. package/dist/agent/exportarPdf.js.map +1 -0
  60. package/dist/agent/{arbolDeProyecto.js → grafo/arbolDeProyecto.js} +2 -2
  61. package/dist/agent/grafo/arbolDeProyecto.js.map +1 -0
  62. package/dist/agent/{artefactosEnDisco.js → grafo/artefactosEnDisco.js} +2 -2
  63. package/dist/agent/grafo/artefactosEnDisco.js.map +1 -0
  64. package/dist/agent/{busquedaRegex.js → grafo/busquedaRegex.js} +41 -1
  65. package/dist/agent/grafo/busquedaRegex.js.map +1 -0
  66. package/dist/agent/grafo/criticaVisual.js +115 -0
  67. package/dist/agent/grafo/criticaVisual.js.map +1 -0
  68. package/dist/agent/grafo/excluirTools.js +82 -0
  69. package/dist/agent/grafo/excluirTools.js.map +1 -0
  70. package/dist/agent/{memoriaDeProyecto.js → grafo/memoriaDeProyecto.js} +1 -1
  71. package/dist/agent/grafo/memoriaDeProyecto.js.map +1 -0
  72. package/dist/agent/grafo/navegacionXone.js +231 -0
  73. package/dist/agent/grafo/navegacionXone.js.map +1 -0
  74. package/dist/agent/{perfiles.js → grafo/perfiles.js} +96 -10
  75. package/dist/agent/grafo/perfiles.js.map +1 -0
  76. package/dist/agent/{proyecto.js → grafo/proyecto.js} +274 -24
  77. package/dist/agent/grafo/proyecto.js.map +1 -0
  78. package/dist/agent/grafo/skills.js +425 -0
  79. package/dist/agent/grafo/skills.js.map +1 -0
  80. package/dist/agent/grafo/xoneAgent.js +566 -0
  81. package/dist/agent/grafo/xoneAgent.js.map +1 -0
  82. package/dist/agent/navegacion/estilosEnDisco.js +110 -0
  83. package/dist/agent/navegacion/estilosEnDisco.js.map +1 -0
  84. package/dist/agent/navegacion/hechosEnDisco.js +26 -0
  85. package/dist/agent/navegacion/hechosEnDisco.js.map +1 -0
  86. package/dist/agent/navegacion/indiceEnDisco.js +36 -0
  87. package/dist/agent/navegacion/indiceEnDisco.js.map +1 -0
  88. package/dist/agent/navegacion/modeloDeProyecto.js +228 -0
  89. package/dist/agent/navegacion/modeloDeProyecto.js.map +1 -0
  90. package/dist/agent/raizDelPaquete.js +56 -0
  91. package/dist/agent/raizDelPaquete.js.map +1 -0
  92. package/dist/agent/{checkpointer.js → sesiones/checkpointer.js} +1 -1
  93. package/dist/agent/sesiones/checkpointer.js.map +1 -0
  94. package/dist/agent/sesiones/git.js.map +1 -0
  95. package/dist/agent/{gitSync.js → sesiones/gitSync.js} +3 -3
  96. package/dist/agent/sesiones/gitSync.js.map +1 -0
  97. package/dist/agent/{sesionGit.js → sesiones/sesionGit.js} +2 -2
  98. package/dist/agent/sesiones/sesionGit.js.map +1 -0
  99. package/dist/agent/subagentes/agentesEnDisco.js +818 -0
  100. package/dist/agent/subagentes/agentesEnDisco.js.map +1 -0
  101. package/dist/agent/subagentes/consumoExterno.js.map +1 -0
  102. package/dist/agent/subagentes/escrituraDeCodex.js.map +1 -0
  103. package/dist/agent/{escrituraDeOpencode.js → subagentes/escrituraDeOpencode.js} +1 -1
  104. package/dist/agent/subagentes/escrituraDeOpencode.js.map +1 -0
  105. package/dist/agent/{escrituraExterna.js → subagentes/escrituraExterna.js} +59 -6
  106. package/dist/agent/subagentes/escrituraExterna.js.map +1 -0
  107. package/dist/agent/subagentes/pluginDeSkills.js +99 -0
  108. package/dist/agent/subagentes/pluginDeSkills.js.map +1 -0
  109. package/dist/agent/subagentes/subagenteCodex.js.map +1 -0
  110. package/dist/agent/{subagenteExterno.js → subagentes/subagenteExterno.js} +48 -0
  111. package/dist/agent/subagentes/subagenteExterno.js.map +1 -0
  112. package/dist/agent/{subagenteOpencode.js → subagentes/subagenteOpencode.js} +53 -1
  113. package/dist/agent/subagentes/subagenteOpencode.js.map +1 -0
  114. package/dist/agent/{aumentador.js → tareas/aumentador.js} +2 -2
  115. package/dist/agent/tareas/aumentador.js.map +1 -0
  116. package/dist/agent/{juezDeTarea.js → tareas/juezDeTarea.js} +6 -6
  117. package/dist/agent/tareas/juezDeTarea.js.map +1 -0
  118. package/dist/agent/{tareasEnDisco.js → tareas/tareasEnDisco.js} +53 -4
  119. package/dist/agent/tareas/tareasEnDisco.js.map +1 -0
  120. package/dist/agent/{diagnosticoDeTools.js → turno/diagnosticoDeTools.js} +5 -1
  121. package/dist/agent/turno/diagnosticoDeTools.js.map +1 -0
  122. package/dist/agent/{guionizado.js → turno/guionizado.js} +1 -1
  123. package/dist/agent/turno/guionizado.js.map +1 -0
  124. package/dist/agent/turno/informeDeTraza.js +245 -0
  125. package/dist/agent/turno/informeDeTraza.js.map +1 -0
  126. package/dist/agent/turno/inspectorDePrompt.js +205 -0
  127. package/dist/agent/turno/inspectorDePrompt.js.map +1 -0
  128. package/dist/agent/{instantanea.js → turno/instantanea.js} +7 -2
  129. package/dist/agent/turno/instantanea.js.map +1 -0
  130. package/dist/agent/{interrupts.js → turno/interrupts.js} +2 -2
  131. package/dist/agent/turno/interrupts.js.map +1 -0
  132. package/dist/agent/turno/mensajes.js.map +1 -0
  133. package/dist/agent/turno/normalizar.js.map +1 -0
  134. package/dist/agent/{puente.js → turno/puente.js} +71 -4
  135. package/dist/agent/turno/puente.js.map +1 -0
  136. package/dist/agent/turno/resumenDeContexto.js +260 -0
  137. package/dist/agent/turno/resumenDeContexto.js.map +1 -0
  138. package/dist/agent/{resumenDeTool.js → turno/resumenDeTool.js} +33 -0
  139. package/dist/agent/turno/resumenDeTool.js.map +1 -0
  140. package/dist/agent/turno/rubrica.js +91 -0
  141. package/dist/agent/turno/rubrica.js.map +1 -0
  142. package/dist/agent/turno/textoDeTool.js.map +1 -0
  143. package/dist/agent/{turnoReal.js → turno/turnoReal.js} +210 -25
  144. package/dist/agent/turno/turnoReal.js.map +1 -0
  145. package/dist/agent/turno/verificador.js.map +1 -0
  146. package/dist/cli/config.js +1 -1
  147. package/dist/cli/config.js.map +1 -1
  148. package/dist/cli/consola.js +72 -9
  149. package/dist/cli/consola.js.map +1 -1
  150. package/dist/cli/describe.js +2 -2
  151. package/dist/cli/describe.js.map +1 -1
  152. package/dist/cli/doctor.js +1 -1
  153. package/dist/cli/doctor.js.map +1 -1
  154. package/dist/cli/fuentesDeDisco.js +29 -0
  155. package/dist/cli/fuentesDeDisco.js.map +1 -0
  156. package/dist/cli/main.js +87 -31
  157. package/dist/cli/main.js.map +1 -1
  158. package/dist/cli/run.js +40 -10
  159. package/dist/cli/run.js.map +1 -1
  160. package/dist/cli/traza.js +48 -0
  161. package/dist/cli/traza.js.map +1 -0
  162. package/dist/cli/tui/correrTui.js +4 -4
  163. package/dist/cli/tui/correrTui.js.map +1 -1
  164. package/dist/cli/verify.js +1 -1
  165. package/dist/cli/verify.js.map +1 -1
  166. package/dist/core/actos.js +1 -1
  167. package/dist/core/adjuntos.js +2 -2
  168. package/dist/core/agentes.js +247 -7
  169. package/dist/core/agentes.js.map +1 -1
  170. package/dist/core/artefactos.js +4 -4
  171. package/dist/core/config.js +1 -1
  172. package/dist/core/contextos.js +12 -1
  173. package/dist/core/contextos.js.map +1 -1
  174. package/dist/core/descargas.js +1 -1
  175. package/dist/core/dispositivos.js +53 -3
  176. package/dist/core/dispositivos.js.map +1 -1
  177. package/dist/core/esfuerzo.js +141 -0
  178. package/dist/core/esfuerzo.js.map +1 -0
  179. package/dist/core/esqueleto.js +1 -1
  180. package/dist/core/estilos.js +78 -0
  181. package/dist/core/estilos.js.map +1 -0
  182. package/dist/core/exportacion.js +140 -0
  183. package/dist/core/exportacion.js.map +1 -0
  184. package/dist/core/hechosDelProyecto.js +87 -0
  185. package/dist/core/hechosDelProyecto.js.map +1 -0
  186. package/dist/core/logDeDispositivo.js +143 -0
  187. package/dist/core/logDeDispositivo.js.map +1 -0
  188. package/dist/core/modelos.js +48 -3
  189. package/dist/core/modelos.js.map +1 -1
  190. package/dist/core/mudanzaDeWorkspace.js +93 -0
  191. package/dist/core/mudanzaDeWorkspace.js.map +1 -0
  192. package/dist/core/navegacion.js +134 -0
  193. package/dist/core/navegacion.js.map +1 -0
  194. package/dist/core/notify.js +5 -0
  195. package/dist/core/notify.js.map +1 -1
  196. package/dist/core/planDeSubida.js +1 -1
  197. package/dist/core/planes.js +70 -0
  198. package/dist/core/planes.js.map +1 -0
  199. package/dist/core/ports.js +4 -0
  200. package/dist/core/ports.js.map +1 -1
  201. package/dist/core/rubrica.js +90 -0
  202. package/dist/core/rubrica.js.map +1 -0
  203. package/dist/core/selectorDeCarpeta.js +87 -0
  204. package/dist/core/selectorDeCarpeta.js.map +1 -0
  205. package/dist/core/settings.js +56 -7
  206. package/dist/core/settings.js.map +1 -1
  207. package/dist/core/shellDeAgente.js +144 -0
  208. package/dist/core/shellDeAgente.js.map +1 -0
  209. package/dist/core/skills.js +158 -0
  210. package/dist/core/skills.js.map +1 -0
  211. package/dist/core/topeAgotado.js +69 -0
  212. package/dist/core/topeAgotado.js.map +1 -0
  213. package/dist/core/zipDeSkill.js +107 -0
  214. package/dist/core/zipDeSkill.js.map +1 -0
  215. package/dist/web/servidor/arranque.js +853 -54
  216. package/dist/web/servidor/arranque.js.map +1 -1
  217. package/dist/web/servidor/corredorDeTareas.js +3 -3
  218. package/dist/web/servidor/corredorDeTareas.js.map +1 -1
  219. package/dist/web/servidor/sesiones.js +34 -6
  220. package/dist/web/servidor/sesiones.js.map +1 -1
  221. package/dist/web/servidor/transporte.js +1 -1
  222. package/dist/web/servidor/transporte.js.map +1 -1
  223. package/dist/web/servidor/vestibulo.js +62 -13
  224. package/dist/web/servidor/vestibulo.js.map +1 -1
  225. package/package.json +7 -4
  226. package/skills/archify/SKILL.md +25 -2
  227. package/skills/artifacts-builder/SKILL.md +23 -0
  228. package/skills/xone-hotswap/SKILL.md +96 -0
  229. package/skills/xone-hotswap/scripts/xone-arrancar-android +202 -0
  230. package/skills/xone-hotswap/scripts/xone-arrancar-ios +85 -0
  231. package/skills/xone-hotswap/scripts/xone-captura-android +93 -0
  232. package/skills/xone-hotswap/scripts/xone-desplegar-android +439 -0
  233. package/skills/xone-hotswap/scripts/xone-hotswap +201 -0
  234. package/skills/xone-hotswap/scripts/xone-log-android +102 -0
  235. package/skills/xone-hotswap/scripts/xone-reiniciar-android +132 -0
  236. package/skills/xone-plan-builder/SKILL.md +22 -4
  237. package/skills/xone-spec-builder/SKILL.md +37 -7
  238. package/apps/web/dist/assets/index-CFYAoHdw.js +0 -477
  239. package/dist/agent/agentesEnDisco.js +0 -539
  240. package/dist/agent/agentesEnDisco.js.map +0 -1
  241. package/dist/agent/arbolDeProyecto.js.map +0 -1
  242. package/dist/agent/artefactosEnDisco.js.map +0 -1
  243. package/dist/agent/aumentador.js.map +0 -1
  244. package/dist/agent/authEnDisco.js.map +0 -1
  245. package/dist/agent/busquedaRegex.js.map +0 -1
  246. package/dist/agent/catalogoModelos.js.map +0 -1
  247. package/dist/agent/checkpointer.js.map +0 -1
  248. package/dist/agent/cloudstudioClient.js.map +0 -1
  249. package/dist/agent/cloudstudioMcp.js.map +0 -1
  250. package/dist/agent/configEnDisco.js.map +0 -1
  251. package/dist/agent/consumoExterno.js.map +0 -1
  252. package/dist/agent/crearProyecto.js.map +0 -1
  253. package/dist/agent/descarga.js.map +0 -1
  254. package/dist/agent/diagnosticoDeTools.js.map +0 -1
  255. package/dist/agent/dispositivosEnMaquina.js.map +0 -1
  256. package/dist/agent/entorno.js.map +0 -1
  257. package/dist/agent/escrituraDeCodex.js.map +0 -1
  258. package/dist/agent/escrituraDeOpencode.js.map +0 -1
  259. package/dist/agent/escrituraExterna.js.map +0 -1
  260. package/dist/agent/gemini.js.map +0 -1
  261. package/dist/agent/git.js.map +0 -1
  262. package/dist/agent/gitSync.js.map +0 -1
  263. package/dist/agent/guionizado.js.map +0 -1
  264. package/dist/agent/hotswap.js.map +0 -1
  265. package/dist/agent/instalacionEnMaquina.js.map +0 -1
  266. package/dist/agent/instantanea.js.map +0 -1
  267. package/dist/agent/interrupts.js.map +0 -1
  268. package/dist/agent/juezDeTarea.js.map +0 -1
  269. package/dist/agent/lanzamientoEnMaquina.js.map +0 -1
  270. package/dist/agent/manifiesto.js.map +0 -1
  271. package/dist/agent/memoriaDeProyecto.js.map +0 -1
  272. package/dist/agent/mensajes.js.map +0 -1
  273. package/dist/agent/modelos.js +0 -130
  274. package/dist/agent/modelos.js.map +0 -1
  275. package/dist/agent/modelosDeMotor.js.map +0 -1
  276. package/dist/agent/normalizar.js.map +0 -1
  277. package/dist/agent/paqueteDelProyecto.js.map +0 -1
  278. package/dist/agent/perfiles.js.map +0 -1
  279. package/dist/agent/persona.js.map +0 -1
  280. package/dist/agent/procesosEnMaquina.js.map +0 -1
  281. package/dist/agent/proyecto.js.map +0 -1
  282. package/dist/agent/puente.js.map +0 -1
  283. package/dist/agent/resumenDeContexto.js +0 -30
  284. package/dist/agent/resumenDeContexto.js.map +0 -1
  285. package/dist/agent/resumenDeTool.js.map +0 -1
  286. package/dist/agent/sesionGit.js.map +0 -1
  287. package/dist/agent/settingsEnDisco.js.map +0 -1
  288. package/dist/agent/skills.js +0 -65
  289. package/dist/agent/skills.js.map +0 -1
  290. package/dist/agent/subagenteCodex.js.map +0 -1
  291. package/dist/agent/subagenteExterno.js.map +0 -1
  292. package/dist/agent/subagenteOpencode.js.map +0 -1
  293. package/dist/agent/subida.js.map +0 -1
  294. package/dist/agent/tareasEnDisco.js.map +0 -1
  295. package/dist/agent/textoDeTool.js.map +0 -1
  296. package/dist/agent/turnoReal.js.map +0 -1
  297. package/dist/agent/verificador.js.map +0 -1
  298. package/dist/agent/versionEnDisco.js.map +0 -1
  299. package/dist/agent/xoneAgent.js +0 -347
  300. package/dist/agent/xoneAgent.js.map +0 -1
  301. package/dist/agent/zip.js.map +0 -1
  302. /package/dist/agent/{manifiesto.js → cloudstudio/manifiesto.js} +0 -0
  303. /package/dist/agent/{entorno.js → config/entorno.js} +0 -0
  304. /package/dist/agent/{gemini.js → config/gemini.js} +0 -0
  305. /package/dist/agent/{hotswap.js → dispositivos/hotswap.js} +0 -0
  306. /package/dist/agent/{instalacionEnMaquina.js → dispositivos/instalacionEnMaquina.js} +0 -0
  307. /package/dist/agent/{procesosEnMaquina.js → dispositivos/procesosEnMaquina.js} +0 -0
  308. /package/dist/agent/{git.js → sesiones/git.js} +0 -0
  309. /package/dist/agent/{consumoExterno.js → subagentes/consumoExterno.js} +0 -0
  310. /package/dist/agent/{escrituraDeCodex.js → subagentes/escrituraDeCodex.js} +0 -0
  311. /package/dist/agent/{subagenteCodex.js → subagentes/subagenteCodex.js} +0 -0
  312. /package/dist/agent/{mensajes.js → turno/mensajes.js} +0 -0
  313. /package/dist/agent/{normalizar.js → turno/normalizar.js} +0 -0
  314. /package/dist/agent/{textoDeTool.js → turno/textoDeTool.js} +0 -0
  315. /package/dist/agent/{verificador.js → turno/verificador.js} +0 -0
@@ -10,6 +10,28 @@ metadata:
10
10
 
11
11
  # Archify
12
12
 
13
+ ## Antes de escribir nada: dónde va y qué no funciona ahí
14
+
15
+ **Esto se lee aquí porque es donde se lee de verdad.** Vivía en el prompt de cuatro
16
+ especialistas —unos 1.400 caracteres que viajaban en CADA llamada de cada uno, hablaran o no de
17
+ diagramas— y antes de eso vivía en `estilo.md`, que un turno real no llegó a abrir: escribió un
18
+ artefacto con `localStorage`, el script se mató entero y el botón del tema quedó muerto. La
19
+ lección no era «repítelo en todos los prompts», era **ponerlo en el fichero que sí se abre**.
20
+
21
+ - **DÓNDE se guarda**: `/artefactos/<nombre>.html`, nunca en la raíz del proyecto ni dentro de
22
+ `/skills`. `/artefactos/` es la carpeta de ESTA sesión: no es del proyecto, no pasa por
23
+ aprobación, no entra en git y no sube a CloudStudio. Un HTML escrito fuera de ella acaba
24
+ dentro de la app XOne del usuario.
25
+ - **DÓNDE se VE, y qué NO funciona ahí**: la consola lo pinta en un iframe sin
26
+ `allow-same-origin`. `localStorage`, `sessionStorage` e `indexedDB` **LANZAN**, y se llevan por
27
+ delante el resto de tu `<script>`: nada de interruptor de tema ni de recordar nada — el tema se
28
+ resuelve con `matchMedia`, y ya. Y no se llega al origen de la consola, así que **hornea los
29
+ datos dentro del HTML**.
30
+ - **Apóyate en el código real** antes de dibujar: no inventes nombres, componentes ni flujos.
31
+ - **Para un diagrama, esquema, arquitectura, flujo, secuencia, datos o estados, esta skill basta.**
32
+ Si ADEMÁS piden un contenedor interactivo alrededor, eso es otra skill: úsala solo si la
33
+ tienes y después de decidir el diagrama aquí. **No nombres jamás una skill que no tengas.**
34
+
13
35
  Create a diagram: architecture, workflow, sequence, dataflow, or lifecycle.
14
36
 
15
37
  ## Aquí no hay terminal
@@ -93,8 +115,9 @@ pide explícitamente una de esas capacidades.
93
115
 
94
116
  Construye el diagrama tú mismo, como SVG o mermaid inline dentro de tu propio HTML
95
117
  autocontenido — no hay JSON de Archify que escribir ni motor que lo consuma, así que no lleva
96
- theme switching, pan/zoom, búsqueda ni export automático salvo que tú los programes. Sigue la
97
- skill `artifacts-builder`, en concreto `reference/diagramas.md`. La entrega depende de la tool
118
+ theme switching, pan/zoom, búsqueda ni export automático salvo que tú los programes. **Si tienes** la skill
119
+ `artifacts-builder`, su `reference/diagramas.md` te sirve de guía; si no la tienes, sigue aquí y
120
+ no la nombres. La entrega depende de la tool
98
121
  que tengas:
99
122
 
100
123
  - **Con `publish_artifact`**: `publish_artifact(path=..., title=...)`. **Cuando confirme, HAS
@@ -5,6 +5,29 @@ description: "Build a self-contained interactive HTML artifact the user can view
5
5
 
6
6
  # Artifacts Builder
7
7
 
8
+ ## Antes de escribir nada: dónde va y qué no funciona ahí
9
+
10
+ **Esto se lee aquí porque es donde se lee de verdad.** Vivía en el prompt de cuatro
11
+ especialistas —unos 1.400 caracteres que viajaban en CADA llamada de cada uno, hablaran o no de
12
+ diagramas— y antes de eso vivía en `estilo.md`, que un turno real no llegó a abrir: escribió un
13
+ artefacto con `localStorage`, el script se mató entero y el botón del tema quedó muerto. La
14
+ lección no era «repítelo en todos los prompts», era **ponerlo en el fichero que sí se abre**.
15
+
16
+ - **DÓNDE se guarda**: `/artefactos/<nombre>.html`, nunca en la raíz del proyecto ni dentro de
17
+ `/skills`. `/artefactos/` es la carpeta de ESTA sesión: no es del proyecto, no pasa por
18
+ aprobación, no entra en git y no sube a CloudStudio. Un HTML escrito fuera de ella acaba
19
+ dentro de la app XOne del usuario.
20
+ - **DÓNDE se VE, y qué NO funciona ahí**: la consola lo pinta en un iframe sin
21
+ `allow-same-origin`. `localStorage`, `sessionStorage` e `indexedDB` **LANZAN**, y se llevan por
22
+ delante el resto de tu `<script>`: nada de interruptor de tema ni de recordar nada — el tema se
23
+ resuelve con `matchMedia`, y ya. Y no se llega al origen de la consola, así que **hornea los
24
+ datos dentro del HTML**.
25
+ - **Apóyate en el código real** antes de dibujar: no inventes nombres, componentes ni flujos.
26
+ - **Esta skill es para dashboards, informes, tablas filtrables y piezas interactivas.** Para un
27
+ diagrama de arquitectura, flujo o secuencia hay otra: úsala si la tienes, y si no la tienes,
28
+ dilo en vez de dibujarlo aquí — un diagrama hecho a mano sale peor y cuesta más. **No nombres
29
+ jamás una skill que no tengas.**
30
+
8
31
  You write ONE self-contained HTML file. No build step, no npm, no `execute` anywhere this
9
32
  skill is mounted — nothing here runs a script.
10
33
 
@@ -34,6 +34,102 @@ responde. Habla dos protocolos por el mismo puerto: **WebSocket** para los coman
34
34
  comando exista. Cuál es cuál, comando a comando: la columna **Canal (iOS)** de la
35
35
  [matriz](references/comandos.md#matriz-de-disponibilidad).
36
36
 
37
+ ## Los scripts que trae esta skill: se llaman por su nombre
38
+
39
+ Esta carpeta trae siete programas, y **están en el PATH**: se llaman como cualquier otro comando,
40
+ sin ruta. Existen por una razón concreta — los comandos del canal viajan por **WebSocket** y
41
+ `curl` no habla WebSocket, así que sin ellos no hay forma de mandar un `getAllElements` desde una
42
+ shell.
43
+
44
+ ```bash
45
+ # ANTES QUE NADA si `adb devices` no lista ninguno: levanta el emulador y ESPERA a que esté
46
+ # listo de verdad. Ya arrancado, lo dice y no lanza nada, así que se puede llamar sin miedo.
47
+ xone-arrancar-android
48
+ xone-arrancar-android --avd pixel8 # con varios AVDs
49
+ xone-arrancar-android --lista # cuáles hay, sin arrancar ninguno
50
+
51
+ # LO PRIMERO al empezar con un aparato, y lo primero que hay que probar si el canal no contesta:
52
+ # el host reabre la última app ejecutada, y si ésa se quedó en un error, nada responde.
53
+ xone-reiniciar-android
54
+ xone-reiniciar-android --app MiApp
55
+
56
+ # «lanza la app», «despliégala»: la cadena ENTERA (túnel, ZIP, subida, reinicio, lanzamiento)
57
+ # y termina diciendo si la app está VIVA, con su árbol de controles.
58
+ xone-desplegar-android
59
+ xone-desplegar-android --captura # …y deja la captura, en la MISMA orden
60
+ xone-desplegar-android --app MiApp --serie emulator-5554
61
+
62
+ # Cualquier comando del catálogo de más abajo (varios en orden, si le pasas varios):
63
+ xone-hotswap '{"command":"getAllElements","format":"xone"}'
64
+ xone-hotswap '{"command":"getScreenshot"}'
65
+ xone-hotswap '{"command":"click","name":"MAP_BT_ACEPTAR"}'
66
+
67
+ # Por qué algo no se pinta o la app se muere: las excepciones del aparato.
68
+ xone-log-android
69
+ xone-log-android --limpiar # aísla lo que pase a partir de ahora
70
+
71
+ # RESPALDO de la captura, para cuando el canal no contesta: `adb exec-out screencap` pero
72
+ # guardándola donde toca. Nunca redirijas ese `adb` tú: el `cwd` es la raíz del proyecto.
73
+ xone-captura-android
74
+ xone-captura-android --nombre login.png
75
+
76
+ # iOS: levantar el host y dejar el canal listo (desplegar en iOS NO está medido).
77
+ xone-arrancar-ios
78
+ ```
79
+
80
+ **El log es donde está el porqué.** Una pantalla en blanco no dice nada; el log dice
81
+ `LayoutException … Cannot find font file Inter-Regular`, con la colección y el control. Medido
82
+ sobre un proyecto real: la app arrancaba, el árbol de controles contestaba, y lo que faltaba era
83
+ una fuente. Ni la captura ni `getAllElements` lo cuentan.
84
+
85
+ **Encadenar órdenes cuesta más que la orden.** Lo que devuelve cada una es pequeño —1,6 KB el
86
+ árbol de controles, 108 bytes la captura—, pero cada ida y vuelta reenvía la conversación
87
+ entera. Por eso «lanza la app y sácame una captura» es `xone-desplegar-android --captura`, un
88
+ viaje, y no tres órdenes encadenadas. Y `xone-hotswap` acepta varios comandos de golpe, que es
89
+ la misma idea.
90
+
91
+ Tres cosas de `xone-hotswap` que conviene saber antes de leer su salida:
92
+
93
+ - **Una captura no se imprime**: se guarda en `$XONECODE_ARTEFACTOS` y lo que sale es su nombre y
94
+ su tamaño. Un `getScreenshot` son ~57.000 caracteres de base64, y volcarlos en la salida de un
95
+ comando es meterlos en el contexto sin que nadie pueda mirarlos. Para ENSEÑARLA, di su nombre:
96
+ la interfaz la enseña desde ahí. **No intentes abrirla ni leerla**: es binaria.
97
+ - **La respuesta viene en `status`** —no en un campo `image`— y una captura de Android es
98
+ **JPEG**, no PNG.
99
+ - Si muere con «no se pudo hablar con el aparato», casi siempre falta el túnel
100
+ (`adb forward tcp:8443 tcp:8443`) o la app host no está viva.
101
+
102
+ Y `xone-desplegar-android` **no necesita un `zip` del sistema**: construye el ZIP él mismo con
103
+ `zlib`, así que tampoco depende de que haya uno en Windows.
104
+
105
+ **No uses las herramientas nativas para lo que contesta el canal**: `uiautomator dump` da la
106
+ jerarquía de vistas de ANDROID y `adb exec-out screencap` una foto cruda; ninguna de las dos
107
+ conoce los controles XOne, que es por lo que suelen preguntarte.
108
+
109
+ **Pero si el canal no contesta y aun así hace falta una captura, es `xone-captura-android`, no
110
+ un `adb` compuesto a mano.** Medido: redirigir `adb exec-out screencap -p > captura.png` deja el
111
+ fichero en el `cwd`, que es LA RAÍZ DEL PROYECTO del usuario — de ahí se va a la aprobación, a
112
+ git y a CloudStudio. El script hace lo mismo y lo deja en `$XONECODE_ARTEFACTOS`, e imprime solo
113
+ el nombre. Lo que enseña es lo que pinta el SISTEMA, no lo que la app host dice de sí misma: por
114
+ eso sigue siendo el respaldo y no la vía buena.
115
+
116
+ **Sin aparato no hay nada que probar, y arrancarlo es `xone-arrancar-android`.** Espera a que
117
+ el sistema diga `sys.boot_completed=1` —no solo a que salga en `adb devices`—, porque sobre un
118
+ Android a medio arrancar el despliegue falla de formas que no se leen. Y si ya está arrancado
119
+ lo dice y no lanza nada, así que se puede llamar a la defensiva antes de desplegar.
120
+
121
+ ```bash
122
+ adb devices # ¿hay algo?
123
+ xone-arrancar-android # si no; --avd <nombre> con varios AVDs
124
+ xone-desplegar-android
125
+ ```
126
+
127
+ **No lo compongas a mano.** Un comando que no termina cuelga el turno, y además el binario no
128
+ suele estar donde crees: tus comandos corren con `/bin/sh`, sin el `~/.zshrc` de nadie, y en un
129
+ Mac con Homebrew `emulator` vive en `share/android-commandlinetools/emulator/` aunque `adb` sí
130
+ esté en el PATH. El script recibe la ruta buena del harness (`$XONECODE_EMULATOR`), que la
131
+ resuelve con el mismo localizador que la pestaña Ejecutar.
132
+
37
133
  ## Cómo llegar, según la plataforma
38
134
 
39
135
  **Android** — por `adb forward`, y entonces la IP es `127.0.0.1` (el localhost del PC, que adb
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Levantar un emulador de Android y dejarlo listo para desplegar.
4
+ *
5
+ * EXISTE PORQUE FALTABA, y el síntoma fue literal: con el emulador cerrado, el agente no lo
6
+ * levantaba. Lo que había era prosa en la skill —«mándalo al fondo y espera a una
7
+ * condición»— y cuatro sitios donde equivocarse: encontrar un binario que NO está en el PATH
8
+ * (en un Mac con Homebrew `emulator` vive en `share/android-commandlinetools/emulator/`
9
+ * mientras `adb` sí está, y `ANDROID_HOME` suele estar vacía), elegir el AVD, mandarlo al
10
+ * fondo sin colgar el turno, y saber cuándo está listo de verdad.
11
+ *
12
+ * LAS RUTAS NO SE ADIVINAN, LLEGAN: `$XONECODE_EMULATOR` y `$XONECODE_ADB` las pone el
13
+ * harness con el MISMO localizador que usa la pestaña Ejecutar
14
+ * (`core/shellDeAgente.ts#variablesDeAndroid` ← `localizadorDeAndroid`). Dos reglas sobre
15
+ * dónde vive el SDK son dos reglas que divergen. Si no están, se cae al PATH, que es lo
16
+ * único honesto que se puede hacer sin ellas.
17
+ *
18
+ * DOS COSAS QUE LO SEPARAN DEL BOTÓN DE LA WEB (`agent/dispositivos/arranqueDeEmulador.ts`),
19
+ * y son deliberadas:
20
+ *
21
+ * - **Espera a `sys.boot_completed`, no solo a que el aparato APAREZCA.** Al botón le basta
22
+ * con que `adb devices` lo liste con su nombre de AVD: quien mira la pantalla ya ve el
23
+ * resto. Aquí el comando siguiente es `xone-desplegar-android`, y sobre un Android a
24
+ * medio arrancar falla de formas que no se leen —un `pm grant` que no responde, un
25
+ * `am start` que se traga—. La condición de «listo» es la del sistema, no la nuestra.
26
+ * - **Es IDEMPOTENTE.** El modelo lo va a llamar a la defensiva antes de desplegar. Si ya
27
+ * hay un emulador vivo con ese AVD se dice y se sale con 0, sin lanzar nada: un segundo
28
+ * `emulator -avd pixel8` falla con ruido y manda al modelo a diagnosticar un problema que
29
+ * no existe.
30
+ *
31
+ * USO:
32
+ * xone-arrancar-android el único AVD que haya
33
+ * xone-arrancar-android --avd pixel8 con varios
34
+ * xone-arrancar-android --lista qué AVDs hay, sin arrancar nada
35
+ */
36
+ import { execFileSync, spawn } from "node:child_process";
37
+
38
+ /** Lo que tarda un arranque en frío de un AVD con `google_apis`, con margen. Por debajo del
39
+ * tope de reloj de UN comando del harness (600 s), que es quien mataría al script entero. */
40
+ const TOPE_MS = 180_000;
41
+ const ESPERA_S = 2;
42
+
43
+ const args = process.argv.slice(2);
44
+ const opcion = (nombre) => {
45
+ const i = args.indexOf(`--${nombre}`);
46
+ return i >= 0 ? args[i + 1] : undefined;
47
+ };
48
+
49
+ if (args.includes("--help") || args.includes("-h")) {
50
+ console.error(`xone-arrancar-android — levanta un emulador y espera a que esté ARRANCADO.
51
+
52
+ xone-arrancar-android el único AVD que haya
53
+ xone-arrancar-android --avd pixel8 con varios
54
+ xone-arrancar-android --lista qué AVDs hay, sin arrancar nada
55
+
56
+ Si ya hay un emulador vivo con ese AVD, lo dice y no lanza nada. Espera a que el sistema
57
+ diga \`sys.boot_completed=1\`, no solo a que el aparato aparezca en \`adb devices\`: sobre un
58
+ Android a medio arrancar, \`xone-desplegar-android\` falla de formas que no se leen.
59
+ Después: xone-desplegar-android`);
60
+ process.exit(0);
61
+ }
62
+
63
+ // El harness los pone; el PATH es el respaldo. Un valor vacío no es «no consta», así que no
64
+ // se acepta como ruta: se cae al nombre pelado igual que si no estuviera.
65
+ const EMULATOR = process.env.XONECODE_EMULATOR || "emulator";
66
+ const ADB = process.env.XONECODE_ADB || "adb";
67
+
68
+ const adb = (...partes) =>
69
+ execFileSync(ADB, partes, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
70
+
71
+ /** Los emuladores conectados, con el AVD que dice ser cada uno. */
72
+ function emuladoresVivos() {
73
+ let salida;
74
+ try {
75
+ salida = adb("devices");
76
+ } catch {
77
+ // `adb` puede estar levantando su demonio. No es un fallo del arranque, es un sondeo más.
78
+ return [];
79
+ }
80
+ const vivos = [];
81
+ for (const linea of salida.split(/\r?\n/)) {
82
+ const l = linea.trim();
83
+ if (l === "" || /^List of devices/i.test(l) || l.startsWith("*")) continue;
84
+ const [serie, estado] = l.split(/\s+/);
85
+ if (!serie || !serie.startsWith("emulator-") || estado !== "device") continue;
86
+ let avd;
87
+ try {
88
+ avd = adb("-s", serie, "emu", "avd", "name")
89
+ .split(/\r?\n/)
90
+ .map((x) => x.trim())
91
+ .find((x) => x !== "" && x !== "OK");
92
+ } catch {
93
+ // Ese emulador todavía no contesta a la consola. Se sigue con los demás.
94
+ }
95
+ vivos.push({ serie, avd });
96
+ }
97
+ return vivos;
98
+ }
99
+
100
+ /** «Listo» lo dice el SISTEMA, no nosotros. */
101
+ function arrancadoDelTodo(serie) {
102
+ try {
103
+ return adb("-s", serie, "shell", "getprop", "sys.boot_completed").trim() === "1";
104
+ } catch {
105
+ return false;
106
+ }
107
+ }
108
+
109
+ function avdsDisponibles() {
110
+ try {
111
+ return execFileSync(EMULATOR, ["-list-avds"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] })
112
+ .split(/\r?\n/)
113
+ .map((l) => l.trim())
114
+ .filter((l) => l !== "");
115
+ } catch (error) {
116
+ console.error(`no pude listar los AVDs con «${EMULATOR}»: ${error.message.split("\n")[0]}`);
117
+ console.error("Si el SDK está instalado pero no lo encuentro, es que falta ANDROID_HOME o la carpeta emulator/.");
118
+ process.exit(1);
119
+ }
120
+ }
121
+
122
+ if (args.includes("--lista")) {
123
+ const avds = avdsDisponibles();
124
+ if (avds.length === 0) console.error("no hay ningún AVD creado. Créalo con `avdmanager` o desde Android Studio.");
125
+ else console.log(avds.join("\n"));
126
+ process.exit(0);
127
+ }
128
+
129
+ /** El AVD: el que se diga, o el único que haya. Mismo contrato que `xone-arrancar-ios`. */
130
+ function elegirAvd() {
131
+ const dicho = opcion("avd");
132
+ if (dicho !== undefined) return dicho;
133
+ const avds = avdsDisponibles();
134
+ if (avds.length === 0) {
135
+ console.error("no hay ningún AVD creado. Créalo con `avdmanager` o desde Android Studio.");
136
+ process.exit(1);
137
+ }
138
+ if (avds.length > 1) {
139
+ console.error(`hay ${avds.length} AVDs; dime cuál con --avd:`);
140
+ for (const a of avds) console.error(` ${a}`);
141
+ process.exit(1);
142
+ }
143
+ return avds[0];
144
+ }
145
+
146
+ const avd = elegirAvd();
147
+
148
+ // El nombre se convierte en un ARGUMENTO de proceso, así que se cierra por FORMA. Ojo al guion
149
+ // INICIAL: `emulator` tomaría `-foo` por una bandera suya. Es la misma clase que acepta el
150
+ // harness (`core/dispositivos.ts#nombreDeAvdDeConsola`), a propósito.
151
+ if (!/^[A-Za-z0-9._][A-Za-z0-9._-]*$/.test(avd)) {
152
+ console.error(`«${avd}» no es un nombre de AVD`);
153
+ process.exit(1);
154
+ }
155
+
156
+ // IDEMPOTENTE: si ya está, no se lanza nada.
157
+ const yaEstaba = emuladoresVivos().find((e) => e.avd === avd);
158
+ if (yaEstaba !== undefined) {
159
+ if (arrancadoDelTodo(yaEstaba.serie)) {
160
+ console.log(`${avd} ya está arrancado (${yaEstaba.serie}). No he lanzado nada.`);
161
+ console.log("Siguiente: xone-desplegar-android");
162
+ process.exit(0);
163
+ }
164
+ console.log(`${avd} (${yaEstaba.serie}) está arrancando; espero a que termine.`);
165
+ }
166
+
167
+ if (yaEstaba === undefined) {
168
+ console.log(`1/2 lanzando ${avd}`);
169
+ /**
170
+ * DESPEGADO Y SIN TUBERÍAS. Un emulador no termina nunca: si el script se queda con sus
171
+ * pipes, no sale, y entonces el que muere por el tope de reloj es el SCRIPT —diez minutos
172
+ * de turno tirados—. `unref` es lo que deja que este proceso acabe con el nieto vivo.
173
+ */
174
+ const hijo = spawn(EMULATOR, ["-avd", avd], { detached: true, stdio: "ignore" });
175
+ let fallo;
176
+ hijo.on("error", (e) => {
177
+ fallo = e.message;
178
+ });
179
+ hijo.unref();
180
+ execFileSync("sleep", [String(ESPERA_S)]);
181
+ if (fallo !== undefined) {
182
+ console.error(`no pude lanzar el emulador: ${fallo}`);
183
+ process.exit(1);
184
+ }
185
+ }
186
+
187
+ console.log(`2/2 esperando a que ${avd} termine de arrancar`);
188
+ const t0 = Date.now();
189
+ while (Date.now() - t0 < TOPE_MS) {
190
+ const mio = emuladoresVivos().find((e) => e.avd === avd);
191
+ if (mio !== undefined && arrancadoDelTodo(mio.serie)) {
192
+ console.log(`${avd} arrancado y listo (${mio.serie}), ${Math.round((Date.now() - t0) / 1000)} s`);
193
+ console.log("Siguiente: xone-desplegar-android");
194
+ process.exit(0);
195
+ }
196
+ execFileSync("sleep", [String(ESPERA_S)]);
197
+ }
198
+
199
+ // El proceso sigue vivo: se dice lo que se SABE, que es que todavía no está listo.
200
+ console.error(`${avd} no ha terminado de arrancar en ${TOPE_MS / 1000} s; sigue en marcha.`);
201
+ console.error("Vuelve a llamarme dentro de un rato: si ya está, lo digo y no lanzo nada.");
202
+ process.exit(1);
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Levantar el host de iOS y dejar el canal listo. La plantilla equivalente a la de Android.
4
+ *
5
+ * LO QUE CAMBIA RESPECTO A ANDROID, y no es un detalle:
6
+ * - **No hay túnel.** El simulador comparte la pila de red del Mac, así que el servidor se
7
+ * alcanza en `localhost` directamente. En un iPhone físico se llega por la IP de la LAN que
8
+ * enseña la propia app.
9
+ * - **No hay `/file_upload`, ni `push_files`, ni `runScript`, ni `runSql`, ni logs.** El host
10
+ * de iOS no los tiene. O sea que **esto no despliega el proyecto**: arranca el host y abre
11
+ * el canal. Desplegar en iOS no está medido, y fingir un paso que no se ha probado es peor
12
+ * que no tenerlo.
13
+ * - El host es **`es.xone.studioapp.swift`**, y solo existe en el runtime en modo desarrollo.
14
+ *
15
+ * USO:
16
+ * node "$XONECODE_SKILL_XONE_HOTSWAP/scripts/ios-arrancar.mjs"
17
+ * node "$XONECODE_SKILL_XONE_HOTSWAP/scripts/ios-arrancar.mjs" --udid <UDID> --app MiApp
18
+ *
19
+ * Después, los comandos van por el mismo cliente que en Android:
20
+ * HOTSWAP_URL=wss://localhost:8443/hotswap node "$XONECODE_SKILL_XONE_HOTSWAP/scripts/hotswap.mjs" '{"command":"getAllElements","format":"xone"}'
21
+ *
22
+ * DOS AVISOS SOBRE EL HOST DE iOS que te van a costar una sesión si no los sabes: `MyAllXOne`
23
+ * crashea al arrancar (GoogleMaps) —para probar filas, el fixture usable es `FontIconsApp`—, y
24
+ * el host se cae al abrir una lista de menú porque le faltan los `xone_img_*.png`. Si el puerto
25
+ * deja de responder justo después de abrir un menú, es eso y es un defecto AJENO al hotswap.
26
+ */
27
+ import { execFileSync } from "node:child_process";
28
+
29
+ const HOST = "es.xone.studioapp.swift";
30
+
31
+ if (process.argv.includes("--help") || process.argv.includes("-h")) {
32
+ console.error(`xone-arrancar-ios — levanta el host de iOS y deja el canal listo.
33
+
34
+ xone-arrancar-ios usa el único simulador arrancado
35
+ xone-arrancar-ios --udid <UDID> con varios arrancados
36
+ xone-arrancar-ios --app MiApp además lanza esa app
37
+
38
+ NO despliega el proyecto: en iOS no hay /file_upload y eso no está medido. Después, los comandos
39
+ van por: HOTSWAP_URL=wss://localhost:8443/hotswap xone-hotswap '{"command":"getAllElements"}'`);
40
+ process.exit(0);
41
+ }
42
+
43
+ const args = process.argv.slice(2);
44
+ const opcion = (nombre) => {
45
+ const i = args.indexOf(`--${nombre}`);
46
+ return i >= 0 ? args[i + 1] : undefined;
47
+ };
48
+
49
+ /** El simulador arrancado, o el que se diga. Sin ninguno, se dice: no se arranca uno a ciegas. */
50
+ function udid() {
51
+ const dicho = opcion("udid");
52
+ if (dicho !== undefined) return dicho;
53
+ const salida = execFileSync("xcrun", ["simctl", "list", "devices", "booted", "-j"], { encoding: "utf8" });
54
+ const porRuntime = JSON.parse(salida).devices ?? {};
55
+ const arrancados = Object.values(porRuntime).flat();
56
+ if (arrancados.length === 0) {
57
+ console.error("no hay ningún simulador arrancado. Arranca uno (`xcrun simctl boot <UDID>`) o pásame --udid.");
58
+ process.exit(1);
59
+ }
60
+ if (arrancados.length > 1) {
61
+ console.error(`hay ${arrancados.length} simuladores arrancados; dime cuál con --udid:`);
62
+ for (const d of arrancados) console.error(` ${d.udid} ${d.name}`);
63
+ process.exit(1);
64
+ }
65
+ return arrancados[0].udid;
66
+ }
67
+
68
+ const destino = udid();
69
+ console.log(`1/2 arrancando el host en ${destino}`);
70
+ // Arrancar el host es lo que levanta el servidor: sin la app viva, el puerto no responde.
71
+ console.log(execFileSync("xcrun", ["simctl", "launch", destino, HOST], { encoding: "utf8" }).trim());
72
+
73
+ const app = opcion("app");
74
+ if (app === undefined) {
75
+ console.log("2/2 host arrancado. El canal es wss://localhost:8443/hotswap");
76
+ } else {
77
+ console.log(`2/2 lanzando ${app}`);
78
+ const cliente = new URL("hotswap.mjs", import.meta.url).pathname;
79
+ console.log(
80
+ execFileSync("node", [cliente, JSON.stringify({ command: "launchApplication", appName: app })], {
81
+ encoding: "utf8",
82
+ env: { ...process.env, HOTSWAP_URL: "wss://localhost:8443/hotswap" },
83
+ }).trim(),
84
+ );
85
+ }
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Una captura de la pantalla del aparato, POR LA VÍA NATIVA, a la carpeta de artefactos.
4
+ *
5
+ * POR QUÉ EXISTE, si el canal ya sabe capturar. `xone-hotswap '{"command":"getScreenshot"}'`
6
+ * es la vía buena —la pide la app host, así que sale lo que la app pinta— pero necesita el
7
+ * túnel y la app viva. Cuando eso no está, lo único que queda es `adb exec-out screencap`, y
8
+ * medido: el modelo la compone él y redirige la salida a un fichero, que en el `cwd` de la
9
+ * shell es LA RAÍZ DEL PROYECTO. Así apareció un `screenshot_login.png` de 773 KB entre los
10
+ * `.xne` del cliente, camino de la aprobación, de git y de CloudStudio.
11
+ *
12
+ * Un script no es una barrera —una shell alcanza el disco entero y eso no se arregla con
13
+ * prompts—, pero le quita el motivo: pedir la captura por su nombre cuesta menos que
14
+ * componer el `adb` y elegir dónde dejarla, y ya cae donde tiene que caer.
15
+ *
16
+ * LO QUE NO HACE: no imprime la imagen. Un PNG de pantalla completa son cientos de miles de
17
+ * caracteres en base64, y meterlos en el contexto es pagarlos sin que nadie pueda mirarlos.
18
+ * Sale el NOMBRE, que es con lo que la interfaz la enseña.
19
+ *
20
+ * USO:
21
+ * xone-captura-android captura a $XONECODE_ARTEFACTOS
22
+ * xone-captura-android --nombre login.png con nombre propio
23
+ * xone-captura-android --serie emulator-5554 con varios aparatos conectados
24
+ */
25
+ import { execFileSync } from "node:child_process";
26
+ import { writeFileSync } from "node:fs";
27
+ import { join, basename } from "node:path";
28
+
29
+ const args = process.argv.slice(2);
30
+ if (args.includes("--help") || args.includes("-h")) {
31
+ console.error(`xone-captura-android — captura la pantalla del aparato con \`adb\` y la deja en artefactos.
32
+
33
+ xone-captura-android captura a $XONECODE_ARTEFACTOS
34
+ xone-captura-android --nombre login.png con nombre propio
35
+ xone-captura-android --serie <id> con varios aparatos conectados
36
+
37
+ Es la vía de RESPALDO: si el túnel y la app host están vivos, la buena es
38
+ \`xone-hotswap '{"command":"getScreenshot"}'\`, que captura lo que la app pinta.
39
+ Nunca redirijas \`adb exec-out screencap\` a un fichero: el \`cwd\` es la raíz del
40
+ proyecto del usuario y ahí no se escribe.`);
41
+ process.exit(0);
42
+ }
43
+
44
+ const ARTEFACTOS = process.env.XONECODE_ARTEFACTOS;
45
+ if (ARTEFACTOS === undefined) {
46
+ // Sin sitio donde dejarla no se inventa uno: escribir en el `cwd` es exactamente el fallo
47
+ // que este script existe para no cometer.
48
+ console.error("no hay XONECODE_ARTEFACTOS: esta sesión no tiene carpeta de artefactos y la captura no se puede guardar.");
49
+ process.exit(1);
50
+ }
51
+
52
+ const opcion = (nombre) => {
53
+ const i = args.indexOf(`--${nombre}`);
54
+ return i >= 0 ? args[i + 1] : undefined;
55
+ };
56
+ const serie = opcion("serie");
57
+
58
+ // `basename` y no la cadena tal cual: el nombre lo elige el modelo, y un `../` aquí escribiría
59
+ // fuera de la carpeta de la sesión. Misma regla que `esRutaDeArtefacto` en el host.
60
+ const pedido = opcion("nombre");
61
+ const nombre = pedido === undefined ? `captura-${Date.now()}.png` : basename(pedido);
62
+ if (nombre === "" || nombre === "." || nombre === "..") {
63
+ console.error("ese nombre no vale para un fichero.");
64
+ process.exit(1);
65
+ }
66
+
67
+ let bytes;
68
+ try {
69
+ // `exec-out` y no `shell`: `shell` traduce los finales de línea y corrompe el PNG.
70
+ bytes = execFileSync("adb", [...(serie ? ["-s", serie] : []), "exec-out", "screencap", "-p"], {
71
+ maxBuffer: 256 * 1024 * 1024,
72
+ });
73
+ } catch (error) {
74
+ console.error(`no se pudo capturar con adb: ${error.message}`);
75
+ process.exit(1);
76
+ }
77
+
78
+ // Una captura vacía no es una captura: `screencap` puede salir con 0 y no devolver nada si el
79
+ // aparato está bloqueado. Decirlo es mejor que dejar un fichero de 0 bytes con buena pinta.
80
+ if (bytes.length === 0) {
81
+ console.error("adb devolvió una captura VACÍA: ¿está el aparato despierto y desbloqueado?");
82
+ process.exit(1);
83
+ }
84
+
85
+ const destino = join(ARTEFACTOS, nombre);
86
+ try {
87
+ writeFileSync(destino, bytes);
88
+ } catch (error) {
89
+ console.error(`no se pudo guardar la captura: ${error.message}`);
90
+ process.exit(1);
91
+ }
92
+
93
+ console.log(`captura guardada como ${nombre} (${bytes.length} bytes). Es de la vía nativa: enseña lo que el SISTEMA pinta, no lo que la app host dice.`);