xonecode 0.2.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 +70 -10
  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
package/dist/cli/main.js CHANGED
@@ -14,17 +14,34 @@ import { cmdDoctor } from "./doctor.js";
14
14
  import { cmdVerify } from "./verify.js";
15
15
  import { ModeloMalEscrito, parsear, resolver } from "../core/modelos.js";
16
16
  import { topeResuelto } from "../core/contextos.js";
17
- import { cargar } from "../agent/configEnDisco.js";
18
- import { COMANDOS, correrConsola, crearCompleter, } from "./consola.js";
19
- import { crearLeerSecreto, crearPreguntar, crearPielStdio, escribirEnStdout } from "./stdio.js";
20
- import { crearTema } from "./tema.js";
17
+ import { aplicarAuth, cargar, cloudstudioDelProyecto, aplicarCredencialAlProceso, guardarCloudStudioDeProyecto, guardarEntornoDeProyecto, guardarModeloGlobal, guardarModelosDeProyecto, proveedoresPersonalizados, guardarModoDeProyecto, guardarProyectoCloudStudioDeProyecto, guardarRamaDeProyecto, guardarTemaDeProyecto, } from "../agent/configEnDisco.js";
18
+ import { crearCheckpointerDeProyecto } from "../agent/checkpointer.js";
19
+ import { crearTareasEnDisco } from "../agent/tareasEnDisco.js";
20
+ import { carpetaDeArtefactosDeSesion } from "../core/artefactos.js";
21
+ import { seAplicaSinAprobacion } from "../core/settings.js";
22
+ import { conectarCloudStudio, sesionCloudStudio, PUERTO_CALLBACK } from "../agent/cloudstudioMcp.js";
23
+ import { clienteCloudStudio } from "../agent/cloudstudioClient.js";
24
+ import { cargarSettings } from "../agent/settingsEnDisco.js";
25
+ import { descargarProyecto } from "../agent/descarga.js";
26
+ import { arbolLimpio, cambiosPendientes, prepararRepo, sinCommitear } from "../agent/gitSync.js";
27
+ import { subir } from "../agent/subida.js";
28
+ import { guardarCredencial } from "../agent/authEnDisco.js";
29
+ import { asistenteDeModelo } from "./wizardInicial.js";
30
+ import { COMANDOS, configurarModoInicial, correrConsola, crearCompleter, hayCredencial, hayEstadoDeProyecto, MENSAJE_BIENVENIDA, MENSAJE_REANUDANDO, PETICION_REANUDAR_PROYECTO, } from "./consola.js";
31
+ import { crearDetectorDeEof, crearLeerSecreto, crearPreguntar, crearPielStdio, escribirEnStdout } from "./stdio.js";
32
+ import { crearTema, esTema, seleccionarTema } from "./tema.js";
21
33
  import { pedirDecisiones } from "./aprobar.js";
34
+ import { modeloDeAcuse } from "./acuseDeModelo.js";
22
35
  import { inspeccionar } from "../agent/entorno.js";
36
+ import { versionEnMarcha } from "../agent/versionEnDisco.js";
23
37
  import { SkillsEnDisco } from "../agent/skills.js";
24
38
  import { Modelos } from "../agent/modelos.js";
39
+ import { CatalogoModelos } from "../agent/catalogoModelos.js";
25
40
  import { abrirSesionReal, ficherosDelProyecto } from "../agent/turnoReal.js";
41
+ import { SimuladorVerifier } from "../agent/verificador.js";
26
42
  import { crearProyecto } from "../agent/crearProyecto.js";
27
43
  import { createTokenTracker } from "../vendor/tokenTracking.js";
44
+ import { compacto, formatearTokens, formatearTope } from "./tokens.js";
28
45
  /**
29
46
  * Estilo, y solo con TTY detrás.
30
47
  *
@@ -40,6 +57,10 @@ const BOLD = tema.negrita;
40
57
  const RESET = tema.reset;
41
58
  /** El prompt. Se ve dónde acaba la respuesta y empieza lo que uno escribe. */
42
59
  const PROMPT = CON_COLOR ? `${tema.prompt}❯${tema.reset} ` : "> ";
60
+ /** El arranque automático falla como un turno normal: informa y deja la consola viva. */
61
+ function describirError(e) {
62
+ return e instanceof Error ? `${e.constructor.name}: ${e.message}` : String(e);
63
+ }
43
64
  /**
44
65
  * Envuelve las líneas de `readline` para pintar la barra y el prompt ANTES de cada una.
45
66
  *
@@ -68,6 +89,12 @@ async function* conPrompt(rl, escribir, barra) {
68
89
  }
69
90
  const AYUDA = `xonecode — harness de XOne
70
91
 
92
+ xonecode abre la consola WEB en el navegador (por defecto)
93
+ xonecode --no-abrir la web, pero sin abrir el navegador (imprime la URL)
94
+ xonecode --puerto <n> la web en otro puerto (por omisión 4173)
95
+ xonecode --anfitrion <host> acepta ese Host además del loopback, para un túnel
96
+ xonecode --cli la consola de terminal (TUI)
97
+ xonecode --cli --no-tui consola clásica (stdio)
71
98
  xonecode run "<peticion>" un turno, de un disparo (pipeable)
72
99
  xonecode run --real "<peticion>" el agente real sobre el proyecto del cwd
73
100
  xonecode describe qué hay montado y qué es de pega (sin red)
@@ -76,6 +103,11 @@ const AYUDA = `xonecode — harness de XOne
76
103
  xonecode doctor ¿hay un proyecto aquí? ¿responde el simulador?
77
104
  xonecode verify [ruta] valida el proyecto con xone-simulator (por omisión, aquí)
78
105
  xonecode --guion la consola con el agente de pega, sin gastar
106
+ xonecode --guion --web lo mismo, y si el cwd es un proyecto offline lo abre
107
+ solo: la maqueta completa sin alta ni CloudStudio
108
+ xonecode --web fuerza la web aunque no haya terminal interactivo
109
+ xonecode --cli --tui fuerza la interfaz de terminal (TUI)
110
+ xonecode --cli --sin-raton TUI sin capturar el ratón (la rueda vuelve al terminal)
79
111
  xonecode --help esto
80
112
 
81
113
  Modelo:
@@ -131,20 +163,6 @@ export function extraerBanderasDeModelo(argv) {
131
163
  resto,
132
164
  };
133
165
  }
134
- /**
135
- * Token compacto para la línea de estado: `0 tokens` hasta 999, `12.8K tokens` a partir
136
- * de 1000. Un decimal basta para un indicador de cabecera; la cifra exacta la da quien
137
- * la necesita (printTokenUsage al final de los experimentos).
138
- */
139
- function formatearTokens(total) {
140
- return total < 1000 ? `${total} tokens` : `${(total / 1000).toFixed(1)}K tokens`;
141
- }
142
- /** El tope de contexto, compacto: `131K`, `200K`, `1M`. Sin decimales de cortesía. */
143
- function formatearTope(tope) {
144
- if (tope >= 1_000_000)
145
- return `${tope / 1_000_000}M`;
146
- return `${Math.round(tope / 1000)}K`;
147
- }
148
166
  /**
149
167
  * La barra de estado como función PURA: la compone quien tiene los valores y
150
168
  * la pinta quien tiene el color. Así la barra se puede probar sin TTY — y es
@@ -156,13 +174,151 @@ function formatearTope(tope) {
156
174
  */
157
175
  export function formatearBarra(p) {
158
176
  const ctx = p.contexto > 0
159
- ? ` · ctx ${formatearTokens(p.contexto).replace(" tokens", "")}` +
177
+ ? ` · ctx ${compacto(p.contexto)}` +
160
178
  (p.tope !== undefined
161
179
  ? `/${formatearTope(p.tope)} (${Math.round((p.contexto / p.tope) * 100)}%)`
162
180
  : "")
163
181
  : "";
164
182
  return `─ ${p.proyecto} (${p.colecciones} colls) · ${p.modelo} · ${formatearTokens(p.tokens)}${ctx} · /ayuda`;
165
183
  }
184
+ /**
185
+ * El tope de la ventana del modelo ACTUAL: tabla de `core/contextos.ts` + lo que el
186
+ * usuario haya fijado en `config.json` (lo de proyecto gana a lo global, como los
187
+ * modelos). Fábrica PORQUE la barra lo lee en cada llamada y no en el arranque:
188
+ * /modelo cambia el modelo en caliente, y un tope del modelo equivocado es un
189
+ * porcentaje que miente. Si el id no parsea —no debería, viene de `resolver`—, no hay
190
+ * tope y no pasa nada.
191
+ *
192
+ * Exportada para que la TUI calcule su `ctx` con LA MISMA función que usa la barra de
193
+ * stdio y la que /config declara: dos pieles no pueden divergir en el tope.
194
+ */
195
+ export function crearTopeDelModelo(raiz) {
196
+ return (id) => {
197
+ try {
198
+ const { proveedor, modelo } = parsear(id);
199
+ const { config } = cargar(raiz);
200
+ // Misma resolución que /config (`topeResuelto`): proyecto gana a global, y la
201
+ // tabla es el último recurso. El origen aquí no se mira, pero así no puede
202
+ // divergir entre lo que la barra calcula y lo que /config declara.
203
+ return topeResuelto(proveedor, modelo, {
204
+ proyecto: config.proyecto?.contextos,
205
+ global: config.global?.contextos,
206
+ })?.tope;
207
+ }
208
+ catch {
209
+ return undefined;
210
+ }
211
+ };
212
+ }
213
+ /**
214
+ * ¿TUI o stdio? `--no-tui` gana siempre (el usuario pidió explícitamente la consola
215
+ * clásica); después `--tui` fuerza la TUI aunque no haya TTY (quien la pide, la tiene:
216
+ * sin TTY el raw mode no funciona y el resultado es suyo); por omisión, AMBOS lados
217
+ * TTY → TUI (solo mirar stdout monta la TUI con stdin de tubería — «echo /salir |
218
+ * xonecode» en un terminal real — y los bytes del pipe se consumen como teclas mientras
219
+ * el EOF del pipe no termina el lazo) y cualquier tubería → stdio, que es lo que
220
+ * mantiene el e2e de pipe byte-idéntico.
221
+ */
222
+ /**
223
+ * ¿Captura la TUI el ratón? Con él, la rueda mueve el transcript y no el scrollback del
224
+ * terminal, a cambio de que seleccionar texto sea Alt/Shift + arrastre. `--sin-raton` lo
225
+ * apaga. Pura, para probarla.
226
+ */
227
+ export function quiereRaton(argv = []) {
228
+ return !argv.includes("--sin-raton");
229
+ }
230
+ export function decidirTui(argv = []) {
231
+ if (argv.includes("--no-tui"))
232
+ return false;
233
+ if (argv.includes("--tui"))
234
+ return true;
235
+ return process.stdout.isTTY === true && process.stdin.isTTY === true;
236
+ }
237
+ /**
238
+ * Error de USO: una bandera imposible de obedecer, nunca un fallo del harness. Mismo
239
+ * tratamiento que `ModeloMalEscrito` en el `catch` de `main` — mensaje a stderr, sin
240
+ * traza, código 64 — pero con su propia clase: reusar `ModeloMalEscrito` aquí mentiría
241
+ * sobre la causa, y el contrato de códigos de salida lo lee CI.
242
+ */
243
+ export class ErrorDeUso extends Error {
244
+ }
245
+ const PUERTO_WEB_POR_OMISION = 4173;
246
+ /**
247
+ * Qué piel arranca. Hermana pura de `decidirTui`, y por la misma razón: la decisión se
248
+ * prueba sin TTY, y la rama que la usa solo la obedece.
249
+ *
250
+ * La omisión ya es la web. `--cli` gana siempre —es la puerta a la consola de terminal
251
+ * entera, TUI incluida— y `--web` fuerza la web incluso sin terminal, que es lo que hace
252
+ * falta en una máquina remota sin TTY.
253
+ *
254
+ * El TTY entra por parámetro y no se lee aquí dentro para poder probar los dos lados sin
255
+ * terminal; la omisión sigue siendo el `process.stdin.isTTY` de verdad.
256
+ */
257
+ /**
258
+ * Las banderas que solo tienen sentido en la consola de TERMINAL. Pedir cualquiera de ellas
259
+ * es pedir la terminal, así que implican `--cli`.
260
+ *
261
+ * Medido con un pty: sin esto, `xonecode --tui` imprimía «consola web en …» y abría el
262
+ * navegador — o sea, la bandera documentada como «fuerza la interfaz de terminal» hacía
263
+ * exactamente lo contrario y sin decir nada; `--no-tui`, que ERA la vía a la stdio, se
264
+ * ignoraba igual. Una bandera que se obedece al revés en silencio es el bug mudo que este
265
+ * repo persigue en todas partes.
266
+ */
267
+ const BANDERAS_DE_TERMINAL = ["--tui", "--no-tui", "--sin-raton"];
268
+ export function decidirPiel(argv = [], entorno = {}) {
269
+ // `--cli` gana SIEMPRE, también frente a la combinación imposible de abajo: es la orden
270
+ // más explícita que hay y no puede quedar en un error.
271
+ if (argv.includes("--cli"))
272
+ return "consola";
273
+ const deTerminal = BANDERAS_DE_TERMINAL.filter((b) => argv.includes(b));
274
+ if (argv.includes("--web")) {
275
+ // Obedecer una y callar la otra es lo que no se puede hacer: la bandera perdedora se
276
+ // habría ignorado en silencio. Error de USO (64), como `--tui` sin terminal.
277
+ if (deTerminal.length > 0) {
278
+ throw new ErrorDeUso(`«${deTerminal.join("», «")}» son de la consola de terminal y --web pide la web: elige una`);
279
+ }
280
+ return "web";
281
+ }
282
+ if (deTerminal.length > 0)
283
+ return "consola";
284
+ // Sin stdin TTY la omisión NO es la web: `echo "…" | xonecode` intentaría abrir un
285
+ // navegador y se llevaría por delante el e2e de tubería byte-idéntica, que es lo que
286
+ // sostiene que `npm test` no necesite terminal.
287
+ const tty = entorno.stdinTTY ?? process.stdin.isTTY === true;
288
+ return tty ? "web" : "consola";
289
+ }
290
+ /**
291
+ * El anfitrión de un túnel: un nombre de máquina, y nada más.
292
+ *
293
+ * Se valida aquí y no en el servidor porque es un error de USO (64) y no de ejecución. Sin
294
+ * comodines, sin esquema, sin ruta y sin puerto: lo que llega en la cabecera `Host` de un
295
+ * túnel HTTPS es el nombre pelado, y aceptar `https://x/` o `*.ngrok-free.app` sería aceptar
296
+ * algo que nunca va a casar —o, peor, casar de más—.
297
+ */
298
+ function anfitrionDeTunel(bruto) {
299
+ const valor = (bruto ?? "").trim();
300
+ if (!/^[a-zA-Z0-9.-]+$/.test(valor) || valor.includes("*")) {
301
+ throw new ErrorDeUso(`--anfitrion espera un nombre de máquina (por ejemplo «algo.ngrok-free.app»), y recibió «${bruto ?? ""}»`);
302
+ }
303
+ return valor;
304
+ }
305
+ export function parsearOpcionesWeb(argv) {
306
+ const abrir = !argv.includes("--no-abrir");
307
+ const j = argv.indexOf("--anfitrion");
308
+ const anfitrion = j === -1 ? {} : { anfitrion: anfitrionDeTunel(argv[j + 1]) };
309
+ const i = argv.indexOf("--puerto");
310
+ if (i === -1)
311
+ return { puerto: PUERTO_WEB_POR_OMISION, abrir, ...anfitrion };
312
+ const bruto = argv[i + 1];
313
+ const puerto = Number(bruto);
314
+ if (!Number.isInteger(puerto) || puerto < 1 || puerto > 65535) {
315
+ throw new ErrorDeUso(`--puerto espera un número entre 1 y 65535, y recibió «${bruto ?? ""}»`);
316
+ }
317
+ if (puerto === PUERTO_CALLBACK) {
318
+ throw new ErrorDeUso(`el puerto ${PUERTO_CALLBACK} está reservado al callback de OAuth de CloudStudio: elige otro`);
319
+ }
320
+ return { puerto, abrir, ...anfitrion };
321
+ }
166
322
  /**
167
323
  * El ejecutor de turno REAL de la consola: cada línea de prosa corre sobre una `SesionReal`
168
324
  * que sobrevive entre turnos (mismo agente, mismo hilo).
@@ -179,8 +335,31 @@ export function formatearBarra(p) {
179
335
  * `alAbrirSesion` se llama UNA vez, justo tras `abrirSesionReal`, y es la costura por la
180
336
  * que `entrarEnConsola` apunta la barra de estado al tracker de la sesión (que alimenta
181
337
  * el agente; el de `createTokenTracker()` de cabecera se descarta al reasignarse).
338
+ *
339
+ * Exportada porque la consola web la monta tal cual: es exactamente lo que espera la
340
+ * opción `crearEjecutor` del vestíbulo (`web/servidor/vestibulo.ts`), que usa esa misma
341
+ * costura para poder CERRAR la sesión al cambiar de proyecto. Una segunda fábrica para la
342
+ * web habría sido un segundo sitio donde arreglar cada bug del ejecutor real.
343
+ */
344
+ /**
345
+ * `checkpointerDeProyecto` entra por parámetro y no se importa aquí, y no es purismo: la
346
+ * consola de TERMINAL no lo pasa. Su hilo es un uuid nuevo en cada arranque y no hay índice
347
+ * de sesiones donde reanudarlo, así que persistirlo solo dejaría hilos que nadie puede
348
+ * volver a abrir engordando el fichero del proyecto. Persistir es de quien tiene identidad
349
+ * que reanudar — hoy, la consola web.
182
350
  */
183
- function crearEjecutorReal(alAbrirSesion) {
351
+ export function crearEjecutorReal(alAbrirSesion, checkpointerDeProyecto, carpetaDeArtefactos,
352
+ /**
353
+ * La carpeta de los ADJUNTOS de una tarea — `/adjuntos/` para el agente, de solo lectura.
354
+ *
355
+ * Es un valor y no una función de `(raiz, hilo)` como las dos de arriba, y esa diferencia
356
+ * es el dato: los artefactos se derivan de la sesión, pero los adjuntos son de la TAREA, y
357
+ * la tarea solo la conoce quien abrió esta consola (el corredor). Por eso llega a la
358
+ * FÁBRICA: es una propiedad de la consola, igual que su checkpointer.
359
+ *
360
+ * Ausente en toda consola de persona y en toda tarea sin adjuntos.
361
+ */
362
+ carpetaDeAdjuntos) {
184
363
  let sesion;
185
364
  let fuentesVistas;
186
365
  let hiloVisto;
@@ -194,18 +373,81 @@ function crearEjecutorReal(alAbrirSesion) {
194
373
  consolaReal.escribir("✗ falta algo imprescindible: xonecode no puede trabajar aquí.\n");
195
374
  return;
196
375
  }
376
+ const persistente = checkpointerDeProyecto?.(estado.raiz);
377
+ // Y si había fábrica pero no dio nada, el fichero no se pudo abrir: se DICE. Sin esto
378
+ // la sesión funcionaba igual y en silencio dejaba de recordar entre arranques, que es
379
+ // justo la clase de degradación muda que este repo evita. El motivo no viaja —puede
380
+ // llevar la ruta absoluta del home—, así que se dice QUÉ pasa, no dónde.
381
+ if (checkpointerDeProyecto !== undefined && persistente === undefined) {
382
+ consolaReal.escribir("aviso: no se pudo abrir la memoria del proyecto (.xonecode/checkpoint.sqlite); " +
383
+ "esta conversación no se recordará al reabrirla.\n");
384
+ }
197
385
  sesion = await abrirSesionReal({
198
386
  raiz: estado.raiz,
199
- modelos: new Modelos(estado.fuentes),
387
+ modelos: new Modelos(estado.fuentes, proveedoresPersonalizados),
200
388
  skills: new SkillsEnDisco(),
201
389
  entorno,
390
+ // El hilo de la CONSOLA, no uno propio. Había dos ids para lo mismo —el que
391
+ // `/hilo` enseña y el que se generaba dentro— y solo coincidían tras un `/nuevo`.
392
+ // Y en la web ese hilo es el id de la sesión, que es lo que la hace reanudable.
393
+ hilo: estado.hilo,
394
+ // La memoria del agente entre arranques. Ausente —porque nadie pasó fábrica, o
395
+ // porque abrir el fichero falló— y `abrirSesionReal` cae en el `MemorySaver` de
396
+ // siempre: se pierde la memoria entre arranques, que es lo que ya pasaba.
397
+ ...(persistente === undefined ? {} : { checkpointer: persistente }),
398
+ // Dónde caen los diagramas y demás salidas del agente. Con sesión (la web) van a la
399
+ // carpeta de ESA sesión; sin ella, `abrirSesionReal` cae a `.xonecode/artefactos`
400
+ // del proyecto — el hilo del terminal es un uuid nuevo por arranque, y una carpeta
401
+ // por hilo dejaría basura que nadie puede volver a abrir.
402
+ ...(carpetaDeArtefactos === undefined
403
+ ? {}
404
+ : { artefactos: carpetaDeArtefactos(estado.raiz, estado.hilo) }),
405
+ // Los adjuntos de la tarea, si esta consola es de una tarea con alguno. Ausente es
406
+ // «no hay», y el campo NO se pone: una cadena vacía montaría el cwd del proceso.
407
+ ...(carpetaDeAdjuntos === undefined ? {} : { adjuntos: carpetaDeAdjuntos }),
408
+ // Si las escrituras de ESTE proyecto se aplican sin preguntar. Se pregunta en cada
409
+ // ronda —de ahí la función— porque `/aprobacion` lo cambia sin cerrar la sesión, y
410
+ // porque `interactivo` es de la consola que esté delante. Los settings se releen
411
+ // cada vez por lo mismo: la alternativa es que el cambio no surta efecto hasta
412
+ // reabrir, y la mitad de las veces eso sería en la dirección peligrosa.
413
+ // Cuántas rondas de aprobación admite un turno de ESTA consola. La pone quien la
414
+ // monta porque es quien sabe quién está detrás (ver `Consola.topeDeAprobaciones`);
415
+ // ausente y `abrirSesionReal` usa el `MAX_APPROVAL_ROUNDS` de siempre.
416
+ ...(consolaReal.topeDeAprobaciones === undefined
417
+ ? {}
418
+ : { topeDeRondas: consolaReal.topeDeAprobaciones }),
419
+ sinAprobacion: () => seAplicaSinAprobacion({
420
+ raiz: estado.raiz,
421
+ sinAprobacion: cargarSettings().settings.sinAprobacion,
422
+ // Del DISCO y por la raíz, no de `estado.fuentes.proyecto`: en la web ese
423
+ // campo no se rellena nunca, y la guarda habría dado «offline» para todos.
424
+ cloudstudio: cloudstudioDelProyecto(estado.raiz),
425
+ // `interactivo` SOLO no basta, y esto no es celo: `consolaWeb.ts` lo declara
426
+ // `true` a fuego, así que una pestaña que se cierra a mitad de turno seguiría
427
+ // auto-aprobando. «Hay alguien delante» ya está definido en este repo —
428
+ // `pedirDecisiones` lo calcula como `interactive && !eof()` (`cli/aprobar.ts`)—
429
+ // y esa es la misma cuenta. Con ella, al caerse el cable la escritura vuelve al
430
+ // camino de aprobación, donde el eof la rechaza.
431
+ interactivo: consolaReal.interactivo && !(consolaReal.eof?.() ?? false),
432
+ }),
433
+ // El simulador de verdad. Su ausencia en la máquina no se descubre aquí sino al
434
+ // verificar, y entonces se dice en el turno — sin tumbar nada.
435
+ verifier: new SimuladorVerifier(),
202
436
  // Las aprobaciones entran por el `preguntar` de la propia consola: el turno para
203
437
  // y pregunta DENTRO de la sesión, sin salir de ella ni montar otro lector de stdin.
204
- pedirAprobacion: (lista, ficheros, diffs) => pedirDecisiones(lista, consolaReal.preguntar, consolaReal.escribir, {
205
- interactive: consolaReal.interactivo,
206
- fichero: (id) => ficheros.get(id),
207
- diff: (id) => diffs.get(id),
208
- }),
438
+ // Si la consola aporta su propio puerto (`aprobacionesTui`, el modal de la TUI),
439
+ // manda: mismo contrato que este `pedirDecisiones` por readline.
440
+ pedirAprobacion: async (lista, ficheros, diffs) => consolaReal.aprobacionesTui !== undefined
441
+ ? consolaReal.aprobacionesTui(lista, ficheros, diffs)
442
+ : pedirDecisiones(lista, consolaReal.preguntar, consolaReal.escribir, {
443
+ interactive: consolaReal.interactivo,
444
+ // Sin esto, el Ctrl-D de un TTY llega como cadena vacía y `interactive:
445
+ // true` la aprueba: un EOF haciéndose pasar por el Enter que aprueba
446
+ // escrituras. `eof` lo distingue (`cli/aprobar.ts` lo explica entero).
447
+ eof: consolaReal.eof,
448
+ fichero: (id) => ficheros.get(id),
449
+ diff: (id) => diffs.get(id),
450
+ }),
209
451
  });
210
452
  alAbrirSesion(sesion);
211
453
  // Recién construida: no hay cambio que detectar en esta primera vuelta.
@@ -216,7 +458,7 @@ function crearEjecutorReal(alAbrirSesion) {
216
458
  // `/modelo` solo actualizó `estado.fuentes`; aquí es donde deja de ser un cambio
217
459
  // cosmético: el agente se reconstruye con el modelo nuevo y el hilo SE CONSERVA.
218
460
  if (JSON.stringify(estado.fuentes) !== JSON.stringify(fuentesVistas)) {
219
- await sesion.cambiarModelos(new Modelos(estado.fuentes));
461
+ await sesion.cambiarModelos(new Modelos(estado.fuentes, proveedoresPersonalizados));
220
462
  consolaReal.escribir(" (agente reconstruido con el modelo nuevo — el hilo se conserva)\n");
221
463
  fuentesVistas = estado.fuentes;
222
464
  }
@@ -231,7 +473,59 @@ function crearEjecutorReal(alAbrirSesion) {
231
473
  hiloVisto = estado.hilo;
232
474
  }
233
475
  }
234
- await sesion.turno(peticion, crearPielStdio(consolaReal.escribir));
476
+ // La piel del turno: la de la consola si la aporta (la TUI), y el render stdio de
477
+ // siempre si no — mismo reparto que `ejecutarTurnoGuionizado`.
478
+ const resultado = await sesion.turno(peticion, consolaReal.piel?.() ?? crearPielStdio(consolaReal.escribir));
479
+ /**
480
+ * **Y se DEVUELVE lo que el turno sabe de sí mismo**, que antes se tiraba aquí.
481
+ *
482
+ * Era una deuda medida: `EjecutorDeTurno` devolvía `void`, así que el `cortadoPorTope`
483
+ * —y con él el hecho de que quedaron escrituras sin aplicar y el veredicto del
484
+ * verificador— morían en esta línea. Con las tareas de fondo aplicando escrituras
485
+ * solas, eso se veía en el kanban: cuatro ficheros escritos, una escritura abandonada,
486
+ * el verificador sin correr ni una vez, y la tarea diciendo «terminada». Quien decide
487
+ * si eso es entregable es `core/entrega.ts`; aquí solo se abre el canal.
488
+ *
489
+ * Se reenvían los campos de `ResultadoDeTurno` y NO el objeto entero: la bitácora y
490
+ * los `Cambio` son piezas de `agent/` que no tienen por qué cruzar hasta el corredor
491
+ * de tareas, y `cambios` lleva rutas del proyecto que nadie de ahí necesita.
492
+ */
493
+ return {
494
+ verificador: resultado.verificador,
495
+ pendientes: resultado.pendientes,
496
+ ...(resultado.hallazgos === undefined ? {} : { hallazgos: resultado.hallazgos }),
497
+ ...(resultado.motivoSinVerificar === undefined
498
+ ? {}
499
+ : { motivoSinVerificar: resultado.motivoSinVerificar }),
500
+ };
501
+ };
502
+ }
503
+ /**
504
+ * Los adaptadores de disco y de red que dependen SOLO de la raíz del proyecto.
505
+ *
506
+ * Existe porque estaban escritos dos veces —una en la rama TUI y otra en la stdio— y eso
507
+ * ya divergió, medido: el arreglo del `entorno` caducado de `/connect-studio` entró en la
508
+ * rama TUI y no en la de stdio, así que por la consola de terminal seguía guardándose una
509
+ * `url` nueva encima de un `entorno` que nombraba el juego de credenciales de OTRO
510
+ * servidor. Es el mismo principio que ya rige aquí para los comandos («la consola y la
511
+ * shell no divergen porque comparten función»), aplicado a los adaptadores: con una sola
512
+ * definición, «las dos ramas hacen lo mismo» deja de ser algo que haya que recordar.
513
+ */
514
+ export function adaptadoresDeProyecto(raiz,
515
+ /** Misma costura que `guardarEndpointYEntorno`: los tests no pueden leer el
516
+ * `~/.xonecode/settings.json` de quien corre la suite. */
517
+ leerSettings = cargarSettings) {
518
+ return {
519
+ guardarTemaDeProyecto: (tema) => guardarTemaDeProyecto(raiz, tema),
520
+ // `guardarEndpointYEntorno` y NUNCA `guardarCloudStudioDeProyecto` a pelo: el endpoint
521
+ // y el entorno tienen que escribirse juntos o la referencia se queda caducada.
522
+ guardarCloudStudioDeProyecto: (url, scopes) => guardarEndpointYEntorno(raiz, url, scopes, leerSettings),
523
+ guardarModoDeProyecto: (modo) => guardarModoDeProyecto(raiz, modo),
524
+ guardarProyectoCloudStudioDeProyecto: (proyecto) => guardarProyectoCloudStudioDeProyecto(raiz, proyecto),
525
+ ramasDeCloudStudio: crearListaDeRamas(raiz),
526
+ guardarRamaDeProyecto: (rama) => guardarRamaDeProyecto(raiz, rama),
527
+ guardarModelosDeProyecto: (papel, id) => guardarModelosDeProyecto(raiz, papel, id),
528
+ sincronizar: crearSincronizador(),
235
529
  };
236
530
  }
237
531
  /**
@@ -309,6 +603,188 @@ async function preguntarDatos(consola) {
309
603
  export function crearCompleterDelProyecto(raiz, escribir = escribirEnStdout) {
310
604
  return crearCompleter(escribir, () => ficherosDelProyecto(raiz));
311
605
  }
606
+ const PIEZAS_DE_SINCRONIZACION_REALES = {
607
+ leerConfig: cargar,
608
+ leerSettings: cargarSettings,
609
+ sesion: sesionCloudStudio,
610
+ cliente: clienteCloudStudio,
611
+ descargar: descargarProyecto,
612
+ preparar: prepararRepo,
613
+ pendientes: cambiosPendientes,
614
+ limpio: arbolLimpio,
615
+ sinCommitear,
616
+ subirProyecto: subir,
617
+ };
618
+ /**
619
+ * Qué entorno de `settings.json` sirve esta URL.
620
+ *
621
+ * Existe porque el `entorno` del `config.json` puede FALTAR o quedarse viejo, y las dos
622
+ * cosas mandaban el token al hueco equivocado:
623
+ *
624
+ * - Falta en todo proyecto dado de alta desde la terminal, que no elige entorno. Esos
625
+ * proyectos viven en `legado` hasta que alguien registra el oficial en el vestíbulo, y
626
+ * entonces `adoptarLegadoSiProcede` MUEVE ese juego a `webstudio` y los deja sin tokens:
627
+ * reautenticaban en silencio. Casando la URL leen el hueco adoptado y no se enteran.
628
+ * - Se queda viejo cuando `/connect-studio` reescribe la `url` del proyecto: sin volver a
629
+ * resolver, el `entorno` anterior seguiría nombrando el juego de OTRO servidor.
630
+ *
631
+ * La comparación normaliza con `URL` y cae a la comparación literal si alguna cadena no
632
+ * parsea. Qué absorbe esa normalización, MEDIDO y no supuesto: el case del host y el puerto
633
+ * por omisión explícito (`:443`) sí casan; una barra final de más, un `?` vacío, otro puerto,
634
+ * `http` frente a `https` y otro case en la RUTA no casan. Que no casen es benigno y no
635
+ * accidental: el fallo devuelve `undefined` —y cae en `legado`, donde ese proyecto ya
636
+ * estaba— en vez de resolver al id de otro entorno. Ninguna variante cruza a un id ajeno, y
637
+ * ESA es la propiedad de la que depende no mandarle a un servidor el token de otro.
638
+ */
639
+ export function entornoDeUrl(url, entornos) {
640
+ const canonica = (valor) => {
641
+ try {
642
+ return new URL(valor).toString();
643
+ }
644
+ catch {
645
+ return valor;
646
+ }
647
+ };
648
+ const buscada = canonica(url);
649
+ return entornos.find((entorno) => canonica(entorno.url) === buscada)?.id;
650
+ }
651
+ /**
652
+ * El adaptador de `/connect-studio`: guarda el endpoint Y vuelve a resolver el `entorno`.
653
+ *
654
+ * Las dos escrituras van juntas a propósito. Separarlas es lo que dejaba el `entorno`
655
+ * caducado apuntando al juego de credenciales de otro servidor — el fallo peor de todo
656
+ * este arreglo, porque manda un token válido a un endpoint que no lo emitió.
657
+ */
658
+ export function guardarEndpointYEntorno(raiz, url, scopes, leerSettings = cargarSettings) {
659
+ const guardado = guardarCloudStudioDeProyecto(raiz, url, scopes);
660
+ // Sin entorno registrado para esa URL se BORRA la clave: el OAuth de esa URL cae en
661
+ // `legado`, y decir «webstudio» sería nombrar un juego que no es el suyo.
662
+ guardarEntornoDeProyecto(raiz, entornoDeUrl(guardado.url, leerSettings().settings.entornos));
663
+ return guardado;
664
+ }
665
+ /**
666
+ * El endpoint de CloudStudio del proyecto YA guardado, o `undefined` si `/sync` no puede
667
+ * seguir: ni «no hay `cloudstudio`» ni «falta el proyecto o la rama» (un `/connect-studio`
668
+ * sin completar el alta) se distinguen hoy — las dos caen en el mismo texto genérico de
669
+ * `crearSincronizador`. Si algún día hace falta un mensaje más fino, es aquí donde separar
670
+ * los dos casos, sin tocar a quien llama.
671
+ */
672
+ function cloudStudioDeProyecto(leerConfig, leerSettings, raiz) {
673
+ const proyectoConfig = leerConfig(raiz).config.proyecto;
674
+ const cloudstudio = proyectoConfig?.cloudstudio;
675
+ if (cloudstudio === undefined)
676
+ return undefined;
677
+ if (cloudstudio.proyecto === undefined || cloudstudio.rama === undefined)
678
+ return undefined;
679
+ // `entorno` viaja junto a la URL porque decide de QUÉ juego de credenciales se lee
680
+ // (`porEntorno[id]`, `agent/cloudstudioMcp.ts`). Si al `config.json` le falta —todo
681
+ // proyecto dado de alta desde la terminal— se resuelve casando la URL contra los
682
+ // entornos registrados: ver `entornoDeUrl`. Si tampoco así aparece, `undefined` cae en
683
+ // `legado`, que es donde ese proyecto tiene sus tokens.
684
+ return {
685
+ url: cloudstudio.url,
686
+ scopes: cloudstudio.scopes,
687
+ proyecto: cloudstudio.proyecto,
688
+ rama: cloudstudio.rama,
689
+ entorno: proyectoConfig?.entorno ?? entornoDeUrl(cloudstudio.url, leerSettings().settings.entornos),
690
+ };
691
+ }
692
+ /**
693
+ * El adaptador real de `consola.sincronizar`: abre una sesión MCP VIVA con
694
+ * `sesionCloudStudio` —`conectarCloudStudio` cierra su transporte al terminar y sirve
695
+ * para el alta, pero no para operar— y la cierra siempre en un `finally`, tanto si la
696
+ * operación termina bien como si revienta.
697
+ *
698
+ * El orden dentro de «bajar» es crítico: `descargarProyecto` (que ya retira las vistas
699
+ * aplanadas) y DESPUÉS `prepararRepo`. Al revés, git vería esos `.xml` como borrados y la
700
+ * primera subida los borraría en Studio.
701
+ *
702
+ * Las dos direcciones se niegan con el árbol sucio ANTES de abrir sesión ni tocar
703
+ * CloudStudio: «subir» porque se sube el estado de un commit y no un borrador (para que
704
+ * mover la ref signifique algo), y «bajar» porque la descarga SOBRESCRIBE el disco y el
705
+ * baseline se construye después — sin un commit debajo, el trabajo local no se recupera.
706
+ */
707
+ export function crearSincronizador(piezas = PIEZAS_DE_SINCRONIZACION_REALES) {
708
+ return async (accion, raiz, politicaDeAprobacion, informar = () => { }) => {
709
+ const config = cloudStudioDeProyecto(piezas.leerConfig, piezas.leerSettings, raiz);
710
+ if (config === undefined) {
711
+ return { tipo: "texto", texto: "este proyecto no es cloud (o le falta el proyecto/rama del alta)\n" };
712
+ }
713
+ // Las DOS direcciones exigen árbol limpio, y «bajar» no menos que «subir»: la
714
+ // descarga SOBRESCRIBE el disco (`extraerZipBase64` escribe encima, no fusiona) y el
715
+ // baseline se construye DESPUÉS, así que sin un commit debajo el trabajo local no se
716
+ // recupera de ninguna forma. Es alcanzable sin escribir `/sync`: el alta llama a
717
+ // `sincronizar("bajar", …)`, así que quien arranca xonecode en una carpeta con
718
+ // trabajo y elige modo cloud lo perdería. La guarda va ANTES de abrir sesión MCP.
719
+ if (accion !== "estado" && !(await piezas.limpio(raiz))) {
720
+ // `sinCommitear` y no `pendientes`: la pregunta aquí es «qué falta por COMMITEAR»,
721
+ // y además `pendientes` compara contra la ref de seguimiento, que en el alta de una
722
+ // carpeta que todavía no es repo no existe siquiera.
723
+ return { tipo: "arbol-sucio", accion, pendientes: await piezas.sinCommitear(raiz) };
724
+ }
725
+ const sesion = await piezas.sesion(config.url, { scopes: config.scopes, entornoId: config.entorno });
726
+ try {
727
+ const puerto = piezas.cliente(sesion.invocar, config.proyecto.nombre);
728
+ if (accion === "bajar") {
729
+ // `ramaOrigen` fija explícitamente de qué rama se baja (`descargarProyecto` la
730
+ // deja posicionada y la restaura al terminar): sin esto se traería lo que
731
+ // estuviera ACTIVO en la sesión de Studio, y una subida posterior firmaría ese
732
+ // contenido como si fuera de `config.rama`, en silencio.
733
+ const bajada = await piezas.descargar({ puerto, raiz, proyecto: config.proyecto, ramaOrigen: config.rama, informar });
734
+ await piezas.preparar(raiz, bajada.rama, informar);
735
+ return { tipo: "texto", texto: `bajados ${bajada.descargados.length} ficheros (${bajada.via})\n` };
736
+ }
737
+ if (accion === "subir") {
738
+ const informe = await piezas.subirProyecto({
739
+ puerto,
740
+ raiz,
741
+ // La MISMA rama de la que se bajó: lo que se pide es aplicar cambios y subirlos a
742
+ // la rama del proyecto, no a una rama de trabajo aparte que nadie mira en Studio.
743
+ ramaOrigen: config.rama,
744
+ proyecto: config.proyecto,
745
+ // Fail-closed si alguien llamara a `sincronizar("subir", ...)` sin política (el
746
+ // comando `/sync` siempre construye una): sin ella, no autoriza NADA en vez de
747
+ // asumir que sí. El hueco lo documenta `core/cloudstudio.ts#PoliticaDeAprobacion`.
748
+ politicaDeAprobacion: politicaDeAprobacion ?? (async () => false),
749
+ informar,
750
+ });
751
+ return { tipo: "texto", texto: `subidos ${informe.ok.length}, fallaron ${informe.fallos.length}\n` };
752
+ }
753
+ const pendientes = await piezas.pendientes(raiz, config.rama);
754
+ return { tipo: "texto", texto: `rama ${config.rama}: ${pendientes.length} ficheros por subir\n` };
755
+ }
756
+ finally {
757
+ await sesion.cerrar();
758
+ }
759
+ };
760
+ }
761
+ /**
762
+ * El adaptador real de `consola.ramasDeCloudStudio`: mismo patrón de sesión VIVA +
763
+ * `finally`. Se usa durante el alta, con `proyecto` y `url` ya guardados pero la rama
764
+ * todavía sin elegir — por eso lee el config en cada llamada en vez de recibirlo cerrado.
765
+ */
766
+ export function crearListaDeRamas(raiz, piezas = PIEZAS_DE_SINCRONIZACION_REALES) {
767
+ return async (proyectoNombre) => {
768
+ const proyectoConfig = piezas.leerConfig(raiz).config.proyecto;
769
+ const cloudstudio = proyectoConfig?.cloudstudio;
770
+ if (cloudstudio?.url === undefined)
771
+ return [];
772
+ // Mismo motivo, y misma resolución por URL, que en `cloudStudioDeProyecto`: sin el id
773
+ // del entorno un proyecto ya migrado a `webstudio` volvería a autenticar bajo `legado`.
774
+ const sesion = await piezas.sesion(cloudstudio.url, {
775
+ scopes: cloudstudio.scopes,
776
+ entornoId: proyectoConfig?.entorno ?? entornoDeUrl(cloudstudio.url, piezas.leerSettings().settings.entornos),
777
+ });
778
+ try {
779
+ const puerto = piezas.cliente(sesion.invocar, proyectoNombre);
780
+ await puerto.abrir(proyectoNombre);
781
+ return await puerto.ramas();
782
+ }
783
+ finally {
784
+ await sesion.cerrar();
785
+ }
786
+ };
787
+ }
312
788
  export async function entrarEnConsola(fuentes, raiz = process.cwd(), escribir = escribirEnStdout, inspeccionarProyecto = inspeccionar, crearRl = () => readline.createInterface({
313
789
  input: process.stdin,
314
790
  output: process.stdout,
@@ -320,7 +796,70 @@ export async function entrarEnConsola(fuentes, raiz = process.cwd(), escribir =
320
796
  prompt: PROMPT,
321
797
  }), guion = false,
322
798
  /** Costura de test del «¿hay alguien al otro lado?»: sin TTY no se pregunta nada. */
323
- interactivo = process.stdin.isTTY === true) {
799
+ interactivo = process.stdin.isTTY === true,
800
+ /**
801
+ * ¿Arranca la TUI? Costura de test: los tests corren sin TTY y pasan `false` (o no
802
+ * la pasan, y `decidirTui` sin banderas cae en el `isTTY` del proceso). En la rama
803
+ * TUI este parámetro de `interactivo` no se usa: la TUI es interactiva por diseño.
804
+ */
805
+ usarTui = decidirTui(),
806
+ /** ¿Ratón en la TUI? Solo la rama TUI lo mira. */
807
+ raton = true,
808
+ /** Adaptadores compartidos por stdio y TUI; `main` construye una sola instancia real. */
809
+ dependencias = {
810
+ catalogoModelos: new CatalogoModelos(undefined, undefined, proveedoresPersonalizados),
811
+ guardarModeloGlobal,
812
+ conectarCloudStudio: (url, scopes, informar) =>
813
+ // El entorno se resuelve por URL, no se omite: sin esto, un `/connect-studio` sobre
814
+ // un proyecto del vestíbulo abría OAuth en `legado` —un hueco que `/sync` no lee— y
815
+ // dejaba al proyecto reautenticando en cada sincronización.
816
+ conectarCloudStudio(url, { informar, scopes, entornoId: entornoDeUrl(url, cargarSettings().settings.entornos) }),
817
+ }) {
818
+ // La elección de modelo y las credenciales se hidratan ANTES de decidir la piel:
819
+ // stdio y TUI reciben exactamente las mismas fuentes y el catálogo leerá la clave
820
+ // ya aplicada solo cuando `/modelos` provoque su primera consulta.
821
+ const cargado = cargar(raiz);
822
+ aplicarAuth(cargado.auth);
823
+ // El tema es una preferencia del proyecto: un config global no puede cambiar cómo se
824
+ // presenta otro repositorio. Un valor manual desconocido conserva XOne por omisión.
825
+ seleccionarTema("xone");
826
+ const temaDeProyecto = cargado.config.proyecto?.tema;
827
+ if (temaDeProyecto !== undefined && esTema(temaDeProyecto))
828
+ seleccionarTema(temaDeProyecto);
829
+ // `let`: el asistente de cuenta y el alta de proyecto (más abajo, ambos solo con TTY)
830
+ // pueden escribir modelo global o de proyecto, y la cabecera no puede quedarse leyendo
831
+ // las fuentes de ANTES de esa escritura.
832
+ let fuentesHidratadas = {
833
+ ...fuentes,
834
+ proyecto: cargado.config.proyecto,
835
+ global: cargado.config.global,
836
+ };
837
+ if (usarTui) {
838
+ // La TUI es la MISMA consola: `entrarEnConsola` no la duplica, le entrega las
839
+ // piezas que son de esta capa (la inspección, el asistente de creación, el
840
+ // ejecutor real, el tope compartido) y el montaje vive en `tui/correrTui.ts`,
841
+ // que no puede importar de aquí porque sería un ciclo.
842
+ //
843
+ // Import DINÁMICO: `correrTui.ts` arrastra ink y react, y un import estático los
844
+ // cargaría también en `run`, `describe`, `config` y en cada pipe — que no los
845
+ // necesitan ni los van a pintar. Solo la rama TUI paga el módulo.
846
+ const { correrConsolaTui } = await import("./tui/correrTui.js");
847
+ return correrConsolaTui({
848
+ fuentes: fuentesHidratadas,
849
+ raiz,
850
+ guion,
851
+ ...dependencias,
852
+ ...adaptadoresDeProyecto(raiz),
853
+ inspeccionarProyecto,
854
+ ofrecer: ofrecerCrearProyecto,
855
+ crearEjecutor: guion ? undefined : crearEjecutorReal,
856
+ topeDe: crearTopeDelModelo(raiz),
857
+ raton,
858
+ // Una TUI ya implica interacción real. No se vuelve a consultar isTTY dentro de
859
+ // Ink: algunos emuladores integrados lo exponen falso tras capturar stdin.
860
+ asistenteInicial: true,
861
+ });
862
+ }
324
863
  // `let` porque el asistente de creación puede cambiarlo TODO: si el usuario acepta,
325
864
  // la carpeta pasa de «0 colls, sin app.xml» a proyecto de verdad, y la cabecera se
326
865
  // re-inspecciona para no mentir en la primera línea que se ve.
@@ -330,7 +869,7 @@ interactivo = process.stdin.isTTY === true) {
330
869
  // con /describe. `let` porque /modelo <p>/<m> la cambia EN CALIENTE dentro de la sesión.
331
870
  let modeloTrabajo;
332
871
  {
333
- const { proveedor, modelo } = resolver(fuentes).trabajo;
872
+ const { proveedor, modelo } = resolver(fuentesHidratadas).trabajo;
334
873
  modeloTrabajo = `${proveedor}/${modelo}`;
335
874
  }
336
875
  /**
@@ -347,28 +886,11 @@ interactivo = process.stdin.isTTY === true) {
347
886
  // al arrancar y tras cada /nuevo o /modelo. La lista de comandos se GENERA recorriendo
348
887
  // el registro — una lista escrita a mano se queda vieja en cuanto alguien añade uno.
349
888
  /**
350
- * El tope de la ventana del modelo ACTUAL: tabla de `core/contextos.ts` + lo que el
351
- * usuario haya fijado en `config.json` (lo de proyecto gana a lo global, como los
352
- * modelos). Se lee en cada barra y no en el arranque porque `modeloTrabajo` cambia
353
- * en caliente con /modelo, y un tope del modelo equivocado es un porcentaje que miente.
354
- * Si el id no parsea —no debería, viene de `resolver`—, no hay tope y no pasa nada.
889
+ * El tope de la ventana del modelo ACTUAL. La lógica vive en `crearTopeDelModelo`
890
+ * (módulo, y compartida con la TUI): la barra la llama en cada emisión porque
891
+ * /modelo cambia el modelo en caliente.
355
892
  */
356
- const topeDelModelo = (id) => {
357
- try {
358
- const { proveedor, modelo } = parsear(id);
359
- const { config } = cargar(raiz);
360
- // Misma resolución que /config (`topeResuelto`): proyecto gana a global, y la
361
- // tabla es el último recurso. El origen aquí no se mira, pero así no puede
362
- // divergir entre lo que la barra calcula y lo que /config declara.
363
- return topeResuelto(proveedor, modelo, {
364
- proyecto: config.proyecto?.contextos,
365
- global: config.global?.contextos,
366
- })?.tope;
367
- }
368
- catch {
369
- return undefined;
370
- }
371
- };
893
+ const topeDelModelo = crearTopeDelModelo(raiz);
372
894
  /**
373
895
  * La barra de estado, al estilo de la de opencode: lo que uno necesita tener delante sin
374
896
  * pedirlo. En stdio no se puede clavar al fondo de la pantalla —eso pide control de
@@ -408,15 +930,11 @@ interactivo = process.stdin.isTTY === true) {
408
930
  lineas: conPrompt(rl, escribir, barraDeEstado),
409
931
  escribir: (t) => {
410
932
  escribir(t);
411
- // La línea de estado se reimprime tras /nuevo y /modelo (los DOS, no
412
- // /modelo-rapido/-trabajo/-afilado: así lo pide la tarea). Ambos manejadores
413
- // escriben su resultado en UNA sola llamada a escribir, así que basta con anclar
414
- // al principio de esa cadena exacta:
415
- // "modelo (los tres papeles): <p>/<m>\n" → /modelo
416
- // "hilo nuevo: <id>\n" → /nuevo
417
- const comoModelo = /^modelo \(los tres papeles\): (.+)\n$/.exec(t);
418
- if (comoModelo) {
419
- modeloTrabajo = comoModelo[1];
933
+ // La TUI usa el mismo parser: un acuse de los tres papeles, o el del papel
934
+ // `trabajo` individual de `/modelos`, actualiza la cabecera sin duplicar regex.
935
+ const acuse = modeloDeAcuse(t);
936
+ if (acuse !== undefined && (acuse.papel === undefined || acuse.papel === "trabajo")) {
937
+ modeloTrabajo = acuse.modelo;
420
938
  escribir(lineaDeEstado());
421
939
  }
422
940
  else if (/^hilo nuevo: /.test(t)) {
@@ -425,8 +943,39 @@ interactivo = process.stdin.isTTY === true) {
425
943
  },
426
944
  preguntar: crearPreguntar(rl),
427
945
  interactivo,
946
+ eof: crearDetectorDeEof(rl),
428
947
  leerSecreto: crearLeerSecreto(rl),
948
+ catalogoModelos: dependencias.catalogoModelos,
949
+ guardarModeloGlobal: dependencias.guardarModeloGlobal,
950
+ conectarCloudStudio: dependencias.conectarCloudStudio,
951
+ ...adaptadoresDeProyecto(raiz),
429
952
  };
953
+ // Los dobles de readline de tests (y una tubería) pueden declararse interactivos para
954
+ // probar otro diálogo, pero no son un terminal en el que abrir OAuth. El asistente de
955
+ // arranque es exclusivamente de una sesión TTY real.
956
+ if (process.stdin.isTTY === true) {
957
+ // 1. Cuenta: proveedor y modelo, SOLO si nadie eligió nunca (mira `origen`, no un
958
+ // flag de «primer arranque» aparte). Antes que el proyecto: es configuración de la
959
+ // persona, no de la carpeta.
960
+ await asistenteDeModelo(consola, {
961
+ origenDeTrabajo: resolver(fuentesHidratadas).trabajo.origen,
962
+ hayCredencial: (proveedor) => hayCredencial(proveedor, raiz),
963
+ guardarCredencial: (proveedor, clave) => guardarCredencial(proveedor, clave),
964
+ // La clave se PRUEBA antes de escribirse: esto la pone en el proceso (que es de
965
+ // donde la lee `CatalogoModelos`) sin tocar `auth.json`.
966
+ aplicarCredencial: aplicarCredencialAlProceso,
967
+ });
968
+ // 2-3-4. Modo, proyecto de CloudStudio + rama + descarga, y modelo propio opcional.
969
+ await configurarModoInicial(raiz, consola);
970
+ // El asistente y el alta pueden haber escrito modelo global o de proyecto (y, con
971
+ // cloud, ficheros nuevos): se relee TODO antes de fijar la cabecera, o mentiría sobre
972
+ // lo que se acaba de elegir.
973
+ const recargado = cargar(raiz);
974
+ fuentesHidratadas = { ...fuentesHidratadas, proyecto: recargado.config.proyecto, global: recargado.config.global };
975
+ entorno = await inspeccionarProyecto(raiz);
976
+ const elegidoTrasAlta = resolver(fuentesHidratadas).trabajo;
977
+ modeloTrabajo = `${elegidoTrasAlta.proveedor}/${elegidoTrasAlta.modelo}`;
978
+ }
430
979
  // El asistente de creación: la única escritura fuera de un turno del agente, y
431
980
  // por eso la única que pregunta ANTES de escribir. Si se crea, la carpeta ya ES
432
981
  // un proyecto XOne y `entorno` se re-inspecciona; si no, la consola sigue viva
@@ -440,7 +989,11 @@ interactivo = process.stdin.isTTY === true) {
440
989
  }
441
990
  escribir(cabecera());
442
991
  // Mismo patrón de prefijo que run.ts y el manejador /nuevo de consola.ts.
443
- const estado = { hilo: `xonecode-${randomUUID()}`, raiz, fuentes };
992
+ const estado = {
993
+ hilo: `xonecode-${randomUUID()}`,
994
+ raiz,
995
+ fuentes: fuentesHidratadas,
996
+ };
444
997
  // `--guion` conserva el agente de pega (el valor por omisión de `correrConsola`): es lo
445
998
  // que permite ver la consola correr sin gastar. Sin ella, el ejecutor real corre cada
446
999
  // línea de prosa sobre una sesión que se abre en el PRIMER turno y sobrevive a los demás.
@@ -449,6 +1002,21 @@ interactivo = process.stdin.isTTY === true) {
449
1002
  : crearEjecutorReal((sesion) => {
450
1003
  tracker = sesion.tracker;
451
1004
  });
1005
+ // Una memoria .xonecode pertenece al PROYECTO y significa que hay trabajo que
1006
+ // retomar. El saludo de primera visita no debe ocultar ese contexto. `--guion` no
1007
+ // simula esta lectura: no hay modelo real que pueda analizarlo.
1008
+ if (hayEstadoDeProyecto(raiz) && !guion) {
1009
+ consola.escribir(MENSAJE_REANUDANDO);
1010
+ try {
1011
+ await ejecutar(PETICION_REANUDAR_PROYECTO, estado, consola);
1012
+ }
1013
+ catch (e) {
1014
+ consola.escribir(`${describirError(e)}\n`);
1015
+ }
1016
+ }
1017
+ else {
1018
+ consola.escribir(MENSAJE_BIENVENIDA);
1019
+ }
452
1020
  const codigo = await correrConsola(consola, estado, ejecutar);
453
1021
  // Siempre, tanto si se sale por /salir como por EOF: sin rl.close() el proceso queda
454
1022
  // escuchando stdin y `xonecode` no vuelve nunca a la shell.
@@ -457,26 +1025,117 @@ interactivo = process.stdin.isTTY === true) {
457
1025
  }
458
1026
  export async function main(argv) {
459
1027
  const [comando, ...resto] = argv;
460
- // Las banderas de la consola (`--guion` y las de modelo) no son subcomandos:
461
- // `xonecode --guion` entra en la consola igual que `xonecode` a secas, pero con el
462
- // agente de pega. `--help`/`-h` NO entran aquí: son el subcomando de ayuda de abajo.
463
- // El resto de banderas (`--real`, `--json`, …) pertenecen a otros subcomandos y siguen
464
- // cayendo en «no conozco el comando».
465
- // `--guion` se quita del argv ANTES de extraerBanderasDeModelo: no es una bandera de
466
- // modelo y no debe llegar como `resto` a ninguna parte.
467
- if (!comando || comando === "--guion" || comando.startsWith("--modelo")) {
468
- const guion = argv.includes("--guion");
469
- // extraerBanderasDeModelo también con argv vacío, para que fuentes.entorno
470
- // .XONECODE_MODELO se rellene igual que en todos los demás subcomandos: la consola
471
- // no puede ser la única vía que ignora esa variable.
472
- const { fuentes } = extraerBanderasDeModelo(argv.filter((a) => a !== "--guion"));
473
- return entrarEnConsola(fuentes, undefined, undefined, undefined, undefined, guion);
474
- }
475
- if (comando === "--help" || comando === "-h") {
476
- process.stdout.write(AYUDA);
477
- return 0;
478
- }
479
1028
  try {
1029
+ // Las banderas de la consola (`--guion` y las de modelo) no son subcomandos:
1030
+ // `xonecode --guion` entra en la consola igual que `xonecode` a secas, pero con el
1031
+ // agente de pega. `--help`/`-h` NO entran aquí: son el subcomando de ayuda de abajo.
1032
+ // El resto de banderas (`--real`, `--json`, …) pertenecen a otros subcomandos y siguen
1033
+ // cayendo en «no conozco el comando».
1034
+ // `--guion` se quita del argv ANTES de extraerBanderasDeModelo: no es una bandera de
1035
+ // modelo y no debe llegar como `resto` a ninguna parte.
1036
+ if (!comando ||
1037
+ comando === "--guion" ||
1038
+ comando === "--tui" ||
1039
+ comando === "--no-tui" ||
1040
+ comando === "--sin-raton" ||
1041
+ // Las cuatro de la consola web. Sin ellas, `xonecode --web` cae en «no conozco el
1042
+ // comando» y sale 64 antes de que `decidirPiel` llegue a decidir nada — medido.
1043
+ comando === "--web" ||
1044
+ comando === "--cli" ||
1045
+ comando === "--no-abrir" ||
1046
+ comando === "--puerto" ||
1047
+ comando.startsWith("--modelo")) {
1048
+ // La bifurcación va ANTES del alta de cuatro pasos de `entrarEnConsola`: si no,
1049
+ // `xonecode` con TTY preguntaría el proveedor en el terminal y DESPUÉS abriría un
1050
+ // navegador para volver a preguntarlo.
1051
+ if (decidirPiel(argv) === "web") {
1052
+ const { puerto, abrir, anfitrion } = parsearOpcionesWeb(argv);
1053
+ // Import DINÁMICO, por el mismo motivo que el de la TUI: esta rama arrastra el
1054
+ // vestíbulo entero (grafo del agente, MCP, sesiones) y `run`, `describe` y
1055
+ // cualquier tubería no lo necesitan ni lo van a levantar.
1056
+ const { arrancarConsolaWeb } = await import("../web/servidor/arranque.js");
1057
+ return await arrancarConsolaWeb({
1058
+ puerto,
1059
+ ...(anfitrion === undefined ? {} : { anfitrion }),
1060
+ abrir,
1061
+ cwd: process.cwd(),
1062
+ guion: argv.includes("--guion"),
1063
+ // Las dos piezas de ESTA capa que el vestíbulo necesita, pasadas y no importadas
1064
+ // desde allí: `arranque.ts` importando `main.ts` sería un ciclo entre el
1065
+ // despachador y la piel que monta. Y no son opcionales de verdad — sin
1066
+ // `crearEjecutor` el vestíbulo cae en el turno GUIONIZADO, y sin
1067
+ // `dependenciasDeProyecto` no hay `/sync`, o sea que la descarga del alta no
1068
+ // bajaría nada. Las dos cosas serían mudas.
1069
+ // Con checkpointer PERSISTENTE, al revés que la consola de terminal: aquí cada
1070
+ // conversación tiene id en el índice, así que su hilo se puede reanudar — y esa
1071
+ // es la diferencia entre reabrir y releer.
1072
+ // Y la carpeta de ADJUNTOS cuando la apertura es de una tarea con alguno: el
1073
+ // vestíbulo la reenvía desde `abrirParaTarea`, que es quien la recibe del
1074
+ // corredor — el único que sabe de qué tarea se trata.
1075
+ crearEjecutor: (alAbrir, opcionesDeConsola) => crearEjecutorReal(alAbrir, crearCheckpointerDeProyecto, carpetaDeArtefactosDeSesion, opcionesDeConsola?.adjuntos),
1076
+ // El tope de la ventana para el contador de contexto de la web, con LA MISMA
1077
+ // función que la barra de stdio y la de la TUI. Dos resoluciones del tope serían
1078
+ // dos porcentajes distintos para el mismo modelo, y esa función ya respeta la
1079
+ // precedencia que `/config` declara (proyecto > global > tabla).
1080
+ topeDeContexto: (raiz, modelo) => crearTopeDelModelo(raiz)(modelo),
1081
+ // Con qué código corre este proceso. Se lee aquí y no allí porque toca disco y
1082
+ // lanza `git`, y los tests de `arranque.ts` llaman a esa función entera.
1083
+ version: versionEnMarcha,
1084
+ // La cola de tareas de la MÁQUINA, y con ella el corredor. Se pasa desde aquí y
1085
+ // no se construye allí por la misma razón que las dos de arriba, más una: la
1086
+ // omisión de `arrancarConsolaWeb` tiene que ser NO ejecutar tareas, porque sus
1087
+ // propios tests lo llaman entero y tomarían el cerrojo de quien los corre.
1088
+ tareas: (informar) => crearTareasEnDisco({ informar }),
1089
+ dependenciasDeProyecto: (raiz) => ({
1090
+ ...adaptadoresDeProyecto(raiz),
1091
+ catalogoModelos: new CatalogoModelos(undefined, undefined, proveedoresPersonalizados),
1092
+ guardarModeloGlobal,
1093
+ conectarCloudStudio: (url, scopes, informar) => conectarCloudStudio(url, {
1094
+ informar,
1095
+ scopes,
1096
+ entornoId: entornoDeUrl(url, cargarSettings().settings.entornos),
1097
+ }),
1098
+ }),
1099
+ });
1100
+ }
1101
+ const guion = argv.includes("--guion");
1102
+ const pedirTui = argv.includes("--tui");
1103
+ // Por defecto, TUI siempre. `--no-tui` fuerza stdio; sin banderas, TUI.
1104
+ const usarTui = !argv.includes("--no-tui");
1105
+ // `--tui` fuerza la TUI, pero no puede fabricar un terminal: sin stdin interactivo
1106
+ // no hay teclado que leer, y sin stdout TTY ink pintaría códigos de escape en la
1107
+ // tubería. Error de USO (64), no un crash — la bandera fue imposible, no el entorno.
1108
+ if (pedirTui && usarTui && (process.stdin.isTTY !== true || process.stdout.isTTY !== true)) {
1109
+ process.stderr.write("la TUI necesita un terminal interactivo: usa --no-tui o corre sin tubería\n");
1110
+ return 64;
1111
+ }
1112
+ // extraerBanderasDeModelo también con argv vacío, para que fuentes.entorno
1113
+ // .XONECODE_MODELO se rellene igual que en todos los demás subcomandos: la consola
1114
+ // no puede ser la única vía que ignora esa variable.
1115
+ // `--puerto` se quita CON su valor: un `4300` suelto no es una bandera de modelo,
1116
+ // pero tampoco tiene por qué llegar a `resto` como si fuera un argumento del usuario.
1117
+ const sinBanderasDeConsola = argv.filter((a, i) => {
1118
+ if (["--guion", "--sin-raton", "--web", "--cli", "--no-abrir", "--puerto"].includes(a))
1119
+ return false;
1120
+ return argv[i - 1] !== "--puerto";
1121
+ });
1122
+ const { fuentes } = extraerBanderasDeModelo(sinBanderasDeConsola);
1123
+ const catalogoModelos = new CatalogoModelos(undefined, undefined, proveedoresPersonalizados);
1124
+ // Por defecto, TUI siempre. `--no-tui` fuerza stdio.
1125
+ return await entrarEnConsola(fuentes, undefined, undefined, undefined, undefined, guion, undefined, !argv.includes("--no-tui"), quiereRaton(argv), {
1126
+ catalogoModelos,
1127
+ guardarModeloGlobal,
1128
+ conectarCloudStudio: (url, scopes, informar) =>
1129
+ // El entorno se resuelve por URL, no se omite: sin esto, un `/connect-studio` sobre
1130
+ // un proyecto del vestíbulo abría OAuth en `legado` —un hueco que `/sync` no lee— y
1131
+ // dejaba al proyecto reautenticando en cada sincronización.
1132
+ conectarCloudStudio(url, { informar, scopes, entornoId: entornoDeUrl(url, cargarSettings().settings.entornos) }),
1133
+ });
1134
+ }
1135
+ if (comando === "--help" || comando === "-h") {
1136
+ process.stdout.write(AYUDA);
1137
+ return 0;
1138
+ }
480
1139
  if (comando === "run") {
481
1140
  const { fuentes, resto: sinModelo } = extraerBanderasDeModelo(resto);
482
1141
  const peticion = sinModelo.filter((a) => !a.startsWith("--")).join(" ");
@@ -508,7 +1167,7 @@ export async function main(argv) {
508
1167
  catch (e) {
509
1168
  // Un modelo mal escrito es un error del USUARIO, no del harness: su mensaje, sin
510
1169
  // traza, y código de uso (64). No se propaga como un fallo de software.
511
- if (e instanceof ModeloMalEscrito) {
1170
+ if (e instanceof ModeloMalEscrito || e instanceof ErrorDeUso) {
512
1171
  process.stderr.write(`${e.message}\n`);
513
1172
  return 64; // EX_USAGE
514
1173
  }