xonecode 0.3.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (408) hide show
  1. package/README.md +50 -8
  2. package/THIRD_PARTY_NOTICES.md +160 -0
  3. package/apps/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  4. package/apps/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  5. package/apps/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  6. package/apps/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  7. package/apps/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  8. package/apps/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  9. package/apps/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  10. package/apps/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  11. package/apps/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  12. package/apps/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  13. package/apps/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  14. package/apps/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  15. package/apps/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  16. package/apps/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  17. package/apps/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  18. package/apps/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  19. package/apps/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  20. package/apps/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  21. package/apps/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  22. package/apps/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  23. package/apps/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  24. package/apps/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  25. package/apps/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  26. package/apps/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  27. package/apps/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  28. package/apps/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  29. package/apps/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  30. package/apps/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  31. package/apps/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  32. package/apps/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  33. package/apps/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  34. package/apps/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  35. package/apps/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  36. package/apps/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  37. package/apps/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  38. package/apps/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  39. package/apps/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  40. package/apps/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  41. package/apps/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  42. package/apps/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  43. package/apps/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  44. package/apps/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  45. package/apps/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  46. package/apps/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  47. package/apps/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  48. package/apps/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  49. package/apps/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  50. package/apps/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  51. package/apps/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  52. package/apps/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  53. package/apps/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  54. package/apps/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  55. package/apps/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  56. package/apps/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  57. package/apps/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  58. package/apps/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  59. package/apps/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  60. package/apps/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  61. package/apps/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  62. package/apps/web/dist/assets/c-BIGW1oBm.js +1 -0
  63. package/apps/web/dist/assets/cpp-DddhVWhK.js +1 -0
  64. package/apps/web/dist/assets/csharp-DSvCPggb.js +1 -0
  65. package/apps/web/dist/assets/css-CLj8gQPS.js +1 -0
  66. package/apps/web/dist/assets/go-C27-OAKa.js +1 -0
  67. package/apps/web/dist/assets/html-CfGypltT.js +1 -0
  68. package/apps/web/dist/assets/index-BB3SamhR.css +1 -0
  69. package/apps/web/dist/assets/index-CFYAoHdw.js +477 -0
  70. package/apps/web/dist/assets/ini-BEwlwnbL.js +1 -0
  71. package/apps/web/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  72. package/apps/web/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  73. package/apps/web/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  74. package/apps/web/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  75. package/apps/web/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  76. package/apps/web/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  77. package/apps/web/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  78. package/apps/web/dist/assets/java-CylS5w8V.js +1 -0
  79. package/apps/web/dist/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  80. package/apps/web/dist/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  81. package/apps/web/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  82. package/apps/web/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  83. package/apps/web/dist/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  84. package/apps/web/dist/assets/kotlin-BdnUsdx6.js +1 -0
  85. package/apps/web/dist/assets/less-B1dDrJ26.js +1 -0
  86. package/apps/web/dist/assets/lua-BaeVxFsk.js +1 -0
  87. package/apps/web/dist/assets/markdown-Cvjx9yec.js +1 -0
  88. package/apps/web/dist/assets/mdx-Cmh6b_Ma.js +1 -0
  89. package/apps/web/dist/assets/php-D_2--9WK.js +1 -0
  90. package/apps/web/dist/assets/python-B6aJPvgy.js +1 -0
  91. package/apps/web/dist/assets/ruby-DSHSDbk1.js +1 -0
  92. package/apps/web/dist/assets/rust-B1yitclQ.js +1 -0
  93. package/apps/web/dist/assets/scss-D5BDwBP9.js +1 -0
  94. package/apps/web/dist/assets/sql-CRqJ_cUM.js +1 -0
  95. package/apps/web/dist/assets/swift-C2oV4EkX.js +1 -0
  96. package/apps/web/dist/assets/toml-vGWfd6FD.js +1 -0
  97. package/apps/web/dist/assets/xml-sdJ4AIDG.js +1 -0
  98. package/apps/web/dist/assets/yaml-Buea-lGh.js +1 -0
  99. package/apps/web/dist/iconos/xonecode.png +0 -0
  100. package/apps/web/dist/index.html +20 -0
  101. package/dist/agent/agentesEnDisco.js +539 -0
  102. package/dist/agent/agentesEnDisco.js.map +1 -0
  103. package/dist/agent/arbolDeProyecto.js +245 -0
  104. package/dist/agent/arbolDeProyecto.js.map +1 -0
  105. package/dist/agent/artefactosEnDisco.js +139 -0
  106. package/dist/agent/artefactosEnDisco.js.map +1 -0
  107. package/dist/agent/aumentador.js +175 -0
  108. package/dist/agent/aumentador.js.map +1 -0
  109. package/dist/agent/authEnDisco.js +76 -1
  110. package/dist/agent/authEnDisco.js.map +1 -1
  111. package/dist/agent/busquedaRegex.js +92 -0
  112. package/dist/agent/busquedaRegex.js.map +1 -0
  113. package/dist/agent/catalogoModelos.js +345 -0
  114. package/dist/agent/catalogoModelos.js.map +1 -0
  115. package/dist/agent/checkpointer.js +127 -0
  116. package/dist/agent/checkpointer.js.map +1 -0
  117. package/dist/agent/cloudstudioClient.js +194 -0
  118. package/dist/agent/cloudstudioClient.js.map +1 -0
  119. package/dist/agent/cloudstudioMcp.js +722 -0
  120. package/dist/agent/cloudstudioMcp.js.map +1 -0
  121. package/dist/agent/configEnDisco.js +309 -11
  122. package/dist/agent/configEnDisco.js.map +1 -1
  123. package/dist/agent/consumoExterno.js +99 -0
  124. package/dist/agent/consumoExterno.js.map +1 -0
  125. package/dist/agent/descarga.js +149 -0
  126. package/dist/agent/descarga.js.map +1 -0
  127. package/dist/agent/diagnosticoDeTools.js +51 -0
  128. package/dist/agent/diagnosticoDeTools.js.map +1 -0
  129. package/dist/agent/dispositivosEnMaquina.js +596 -0
  130. package/dist/agent/dispositivosEnMaquina.js.map +1 -0
  131. package/dist/agent/escrituraDeCodex.js +134 -0
  132. package/dist/agent/escrituraDeCodex.js.map +1 -0
  133. package/dist/agent/escrituraDeOpencode.js +80 -0
  134. package/dist/agent/escrituraDeOpencode.js.map +1 -0
  135. package/dist/agent/escrituraExterna.js +742 -0
  136. package/dist/agent/escrituraExterna.js.map +1 -0
  137. package/dist/agent/gemini.js +81 -0
  138. package/dist/agent/gemini.js.map +1 -0
  139. package/dist/agent/git.js +83 -0
  140. package/dist/agent/git.js.map +1 -0
  141. package/dist/agent/gitSync.js +441 -0
  142. package/dist/agent/gitSync.js.map +1 -0
  143. package/dist/agent/hotswap.js +448 -0
  144. package/dist/agent/hotswap.js.map +1 -0
  145. package/dist/agent/instalacionEnMaquina.js +252 -0
  146. package/dist/agent/instalacionEnMaquina.js.map +1 -0
  147. package/dist/agent/instantanea.js +7 -10
  148. package/dist/agent/instantanea.js.map +1 -1
  149. package/dist/agent/interrupts.js +11 -2
  150. package/dist/agent/interrupts.js.map +1 -1
  151. package/dist/agent/juezDeTarea.js +345 -0
  152. package/dist/agent/juezDeTarea.js.map +1 -0
  153. package/dist/agent/lanzamientoEnMaquina.js +377 -0
  154. package/dist/agent/lanzamientoEnMaquina.js.map +1 -0
  155. package/dist/agent/manifiesto.js +59 -0
  156. package/dist/agent/manifiesto.js.map +1 -0
  157. package/dist/agent/memoriaDeProyecto.js +60 -0
  158. package/dist/agent/memoriaDeProyecto.js.map +1 -0
  159. package/dist/agent/modelos.js +105 -17
  160. package/dist/agent/modelos.js.map +1 -1
  161. package/dist/agent/modelosDeMotor.js +204 -0
  162. package/dist/agent/modelosDeMotor.js.map +1 -0
  163. package/dist/agent/paqueteDelProyecto.js +181 -0
  164. package/dist/agent/paqueteDelProyecto.js.map +1 -0
  165. package/dist/agent/perfiles.js +92 -31
  166. package/dist/agent/perfiles.js.map +1 -1
  167. package/dist/agent/persona.js +40 -0
  168. package/dist/agent/persona.js.map +1 -0
  169. package/dist/agent/procesosEnMaquina.js +204 -0
  170. package/dist/agent/procesosEnMaquina.js.map +1 -0
  171. package/dist/agent/proyecto.js +311 -13
  172. package/dist/agent/proyecto.js.map +1 -1
  173. package/dist/agent/puente.js +63 -6
  174. package/dist/agent/puente.js.map +1 -1
  175. package/dist/agent/resumenDeContexto.js +30 -0
  176. package/dist/agent/resumenDeContexto.js.map +1 -0
  177. package/dist/agent/resumenDeTool.js +64 -18
  178. package/dist/agent/resumenDeTool.js.map +1 -1
  179. package/dist/agent/sesionGit.js +481 -0
  180. package/dist/agent/sesionGit.js.map +1 -0
  181. package/dist/agent/settingsEnDisco.js +189 -0
  182. package/dist/agent/settingsEnDisco.js.map +1 -0
  183. package/dist/agent/subagenteCodex.js +368 -0
  184. package/dist/agent/subagenteCodex.js.map +1 -0
  185. package/dist/agent/subagenteExterno.js +423 -0
  186. package/dist/agent/subagenteExterno.js.map +1 -0
  187. package/dist/agent/subagenteOpencode.js +389 -0
  188. package/dist/agent/subagenteOpencode.js.map +1 -0
  189. package/dist/agent/subida.js +202 -0
  190. package/dist/agent/subida.js.map +1 -0
  191. package/dist/agent/tareasEnDisco.js +466 -0
  192. package/dist/agent/tareasEnDisco.js.map +1 -0
  193. package/dist/agent/turnoReal.js +663 -50
  194. package/dist/agent/turnoReal.js.map +1 -1
  195. package/dist/agent/versionEnDisco.js +48 -0
  196. package/dist/agent/versionEnDisco.js.map +1 -0
  197. package/dist/agent/xoneAgent.js +290 -49
  198. package/dist/agent/xoneAgent.js.map +1 -1
  199. package/dist/agent/zip.js +52 -0
  200. package/dist/agent/zip.js.map +1 -0
  201. package/dist/cli/acuseDeModelo.js +30 -0
  202. package/dist/cli/acuseDeModelo.js.map +1 -0
  203. package/dist/cli/aprobar.js +55 -2
  204. package/dist/cli/aprobar.js.map +1 -1
  205. package/dist/cli/config.js +9 -13
  206. package/dist/cli/config.js.map +1 -1
  207. package/dist/cli/consola.js +750 -19
  208. package/dist/cli/consola.js.map +1 -1
  209. package/dist/cli/main.js +740 -81
  210. package/dist/cli/main.js.map +1 -1
  211. package/dist/cli/markdown.js +188 -2
  212. package/dist/cli/markdown.js.map +1 -1
  213. package/dist/cli/run.js +32 -4
  214. package/dist/cli/run.js.map +1 -1
  215. package/dist/cli/stdio.js +46 -19
  216. package/dist/cli/stdio.js.map +1 -1
  217. package/dist/cli/tema.js +96 -8
  218. package/dist/cli/tema.js.map +1 -1
  219. package/dist/cli/tokens.js +29 -0
  220. package/dist/cli/tokens.js.map +1 -0
  221. package/dist/cli/tui/app.js +171 -0
  222. package/dist/cli/tui/app.js.map +1 -0
  223. package/dist/cli/tui/aprobarTui.js +80 -0
  224. package/dist/cli/tui/aprobarTui.js.map +1 -0
  225. package/dist/cli/tui/barra.js +37 -0
  226. package/dist/cli/tui/barra.js.map +1 -0
  227. package/dist/cli/tui/correrTui.js +478 -0
  228. package/dist/cli/tui/correrTui.js.map +1 -0
  229. package/dist/cli/tui/entrada.js +166 -0
  230. package/dist/cli/tui/entrada.js.map +1 -0
  231. package/dist/cli/tui/filas.js +24 -0
  232. package/dist/cli/tui/filas.js.map +1 -0
  233. package/dist/cli/tui/logo.js +49 -0
  234. package/dist/cli/tui/logo.js.map +1 -0
  235. package/dist/cli/tui/pielTui.js +14 -0
  236. package/dist/cli/tui/pielTui.js.map +1 -0
  237. package/dist/cli/tui/raton.js +126 -0
  238. package/dist/cli/tui/raton.js.map +1 -0
  239. package/dist/cli/tui/sidebar.js +86 -0
  240. package/dist/cli/tui/sidebar.js.map +1 -0
  241. package/dist/cli/tui/store.js +143 -0
  242. package/dist/cli/tui/store.js.map +1 -0
  243. package/dist/cli/tui/tarjeta.js +19 -0
  244. package/dist/cli/tui/tarjeta.js.map +1 -0
  245. package/dist/cli/tui/temaInk.js +114 -0
  246. package/dist/cli/tui/temaInk.js.map +1 -0
  247. package/dist/cli/tui/transcript.js +254 -0
  248. package/dist/cli/tui/transcript.js.map +1 -0
  249. package/dist/cli/wizardInicial.js +240 -0
  250. package/dist/cli/wizardInicial.js.map +1 -0
  251. package/dist/core/actos.js +113 -0
  252. package/dist/core/actos.js.map +1 -0
  253. package/dist/core/adjuntos.js +97 -0
  254. package/dist/core/adjuntos.js.map +1 -0
  255. package/dist/core/agentes.js +227 -0
  256. package/dist/core/agentes.js.map +1 -0
  257. package/dist/core/artefactos.js +149 -0
  258. package/dist/core/artefactos.js.map +1 -0
  259. package/dist/core/cloudstudio.js +7 -0
  260. package/dist/core/cloudstudio.js.map +1 -0
  261. package/dist/core/config.js +212 -5
  262. package/dist/core/config.js.map +1 -1
  263. package/dist/core/contextos.js +53 -1
  264. package/dist/core/contextos.js.map +1 -1
  265. package/dist/core/descargas.js +106 -0
  266. package/dist/core/descargas.js.map +1 -0
  267. package/dist/core/descriptoresDeApp.js +279 -0
  268. package/dist/core/descriptoresDeApp.js.map +1 -0
  269. package/dist/core/dispositivos.js +466 -0
  270. package/dist/core/dispositivos.js.map +1 -0
  271. package/dist/core/entrega.js +141 -0
  272. package/dist/core/entrega.js.map +1 -0
  273. package/dist/core/entrelazar.js +90 -0
  274. package/dist/core/entrelazar.js.map +1 -0
  275. package/dist/core/esqueleto.js +10 -0
  276. package/dist/core/esqueleto.js.map +1 -1
  277. package/dist/core/modelos.js +235 -3
  278. package/dist/core/modelos.js.map +1 -1
  279. package/dist/core/notify.js +27 -9
  280. package/dist/core/notify.js.map +1 -1
  281. package/dist/core/planDeSubida.js +127 -0
  282. package/dist/core/planDeSubida.js.map +1 -0
  283. package/dist/core/ports.js +189 -1
  284. package/dist/core/ports.js.map +1 -1
  285. package/dist/core/puedeLanzarse.js +239 -0
  286. package/dist/core/puedeLanzarse.js.map +1 -0
  287. package/dist/core/rutaVirtual.js +42 -0
  288. package/dist/core/rutaVirtual.js.map +1 -0
  289. package/dist/core/settings.js +246 -0
  290. package/dist/core/settings.js.map +1 -0
  291. package/dist/core/tareas.js +230 -0
  292. package/dist/core/tareas.js.map +1 -0
  293. package/dist/core/textos.js +36 -0
  294. package/dist/core/textos.js.map +1 -0
  295. package/dist/core/turno.js +56 -8
  296. package/dist/core/turno.js.map +1 -1
  297. package/dist/core/version.js +31 -0
  298. package/dist/core/version.js.map +1 -0
  299. package/dist/vendor/sqliteSaver.js +389 -0
  300. package/dist/vendor/sqliteSaver.js.map +1 -0
  301. package/dist/vendor/tokenTracking.js +22 -1
  302. package/dist/vendor/tokenTracking.js.map +1 -1
  303. package/dist/web/servidor/arranque.js +3964 -0
  304. package/dist/web/servidor/arranque.js.map +1 -0
  305. package/dist/web/servidor/consolaDeTarea.js +161 -0
  306. package/dist/web/servidor/consolaDeTarea.js.map +1 -0
  307. package/dist/web/servidor/consolaWeb.js +389 -0
  308. package/dist/web/servidor/consolaWeb.js.map +1 -0
  309. package/dist/web/servidor/corredorDeTareas.js +1119 -0
  310. package/dist/web/servidor/corredorDeTareas.js.map +1 -0
  311. package/dist/web/servidor/pielWeb.js +269 -0
  312. package/dist/web/servidor/pielWeb.js.map +1 -0
  313. package/dist/web/servidor/servidor.js +257 -0
  314. package/dist/web/servidor/servidor.js.map +1 -0
  315. package/dist/web/servidor/sesiones.js +440 -0
  316. package/dist/web/servidor/sesiones.js.map +1 -0
  317. package/dist/web/servidor/transporte.js +169 -0
  318. package/dist/web/servidor/transporte.js.map +1 -0
  319. package/dist/web/servidor/vestibulo.js +1276 -0
  320. package/dist/web/servidor/vestibulo.js.map +1 -0
  321. package/package.json +33 -9
  322. package/skills/archify/LICENSE +22 -0
  323. package/skills/archify/SKILL.md +109 -0
  324. package/skills/archify/THIRD_PARTY_NOTICES.md +56 -0
  325. package/skills/archify/assets/template.html +14787 -0
  326. package/skills/archify/bin/archify.mjs +1990 -0
  327. package/skills/archify/bin/open-artifact.mjs +86 -0
  328. package/skills/archify/bin/preview.mjs +648 -0
  329. package/skills/archify/bin/visual-check.mjs +829 -0
  330. package/skills/archify/brand-marks/README.md +31 -0
  331. package/skills/archify/brand-marks/catalog.json +131 -0
  332. package/skills/archify/delta/architecture-delta.mjs +1214 -0
  333. package/skills/archify/examples/agent-run.lifecycle.json +71 -0
  334. package/skills/archify/examples/agent-tool-call.workflow.json +94 -0
  335. package/skills/archify/examples/async-job-roundtrip.sequence.json +61 -0
  336. package/skills/archify/examples/brand-aware-delivery.architecture.json +47 -0
  337. package/skills/archify/examples/cache-miss-request.sequence.json +81 -0
  338. package/skills/archify/examples/checkout-platform.base.architecture.json +31 -0
  339. package/skills/archify/examples/checkout-platform.head.architecture.json +31 -0
  340. package/skills/archify/examples/dataflow-product-analytics.html +14898 -0
  341. package/skills/archify/examples/deployment-release.lifecycle.json +49 -0
  342. package/skills/archify/examples/event-stream.dataflow.json +57 -0
  343. package/skills/archify/examples/incident-response.workflow.json +64 -0
  344. package/skills/archify/examples/lifecycle-agent-run.html +14847 -0
  345. package/skills/archify/examples/product-analytics.dataflow.json +76 -0
  346. package/skills/archify/examples/production-deployment.architecture.json +71 -0
  347. package/skills/archify/examples/release-delivery.workflow.json +62 -0
  348. package/skills/archify/examples/sequence-cache-miss-request.html +14913 -0
  349. package/skills/archify/examples/web-app-rendered.html +14862 -0
  350. package/skills/archify/examples/web-app.architecture.json +46 -0
  351. package/skills/archify/examples/workflow-agent-tool-call-rendered.html +14904 -0
  352. package/skills/archify/migrations/workflow-v2.mjs +279 -0
  353. package/skills/archify/package.json +17 -0
  354. package/skills/archify/recipes/scenarios.mjs +391 -0
  355. package/skills/archify/references/authoring-contract.md +198 -0
  356. package/skills/archify/references/brand-marks.md +69 -0
  357. package/skills/archify/references/delivery-contract.md +116 -0
  358. package/skills/archify/references/viewer-runtime.md +45 -0
  359. package/skills/archify/renderers/architecture/grid.mjs +62 -0
  360. package/skills/archify/renderers/architecture/render-architecture.mjs +1078 -0
  361. package/skills/archify/renderers/dataflow/README.md +104 -0
  362. package/skills/archify/renderers/dataflow/render-dataflow.mjs +483 -0
  363. package/skills/archify/renderers/lifecycle/README.md +115 -0
  364. package/skills/archify/renderers/lifecycle/render-lifecycle.mjs +561 -0
  365. package/skills/archify/renderers/sequence/README.md +114 -0
  366. package/skills/archify/renderers/sequence/render-sequence.mjs +453 -0
  367. package/skills/archify/renderers/shared/brand-marks.mjs +563 -0
  368. package/skills/archify/renderers/shared/cli.mjs +218 -0
  369. package/skills/archify/renderers/shared/desktop-readability.mjs +26 -0
  370. package/skills/archify/renderers/shared/diagnostics.mjs +127 -0
  371. package/skills/archify/renderers/shared/engineering-profiles.mjs +157 -0
  372. package/skills/archify/renderers/shared/generated-brand-marks.mjs +2003 -0
  373. package/skills/archify/renderers/shared/generated-validators.mjs +13 -0
  374. package/skills/archify/renderers/shared/geometry.mjs +1423 -0
  375. package/skills/archify/renderers/shared/i18n.mjs +594 -0
  376. package/skills/archify/renderers/shared/layout-report.mjs +40 -0
  377. package/skills/archify/renderers/shared/legend.mjs +217 -0
  378. package/skills/archify/renderers/shared/output-path.mjs +321 -0
  379. package/skills/archify/renderers/shared/repository-evidence.mjs +235 -0
  380. package/skills/archify/renderers/shared/text-fit.mjs +49 -0
  381. package/skills/archify/renderers/shared/utils.mjs +232 -0
  382. package/skills/archify/renderers/shared/validator.mjs +86 -0
  383. package/skills/archify/renderers/workflow/README.md +223 -0
  384. package/skills/archify/renderers/workflow/render-workflow.mjs +35 -0
  385. package/skills/archify/renderers/workflow/workflow-compiler.mjs +4400 -0
  386. package/skills/archify/renderers/workflow/workflow-migration-geometry.mjs +144 -0
  387. package/skills/archify/schemas/README.md +211 -0
  388. package/skills/archify/schemas/architecture.schema.json +176 -0
  389. package/skills/archify/schemas/common.schema.json +115 -0
  390. package/skills/archify/schemas/dataflow.schema.json +243 -0
  391. package/skills/archify/schemas/lifecycle.schema.json +266 -0
  392. package/skills/archify/schemas/sequence.schema.json +223 -0
  393. package/skills/archify/schemas/workflow.schema.json +428 -0
  394. package/skills/archify/scripts/check-render-output.mjs +835 -0
  395. package/skills/archify/scripts/check-update.mjs +1667 -0
  396. package/skills/archify/scripts/render-examples.mjs +26 -0
  397. package/skills/archify/scripts/update-contract.mjs +182 -0
  398. package/skills/archify/skill-release.json +10 -0
  399. package/skills/artifacts-builder/SKILL.md +120 -0
  400. package/skills/artifacts-builder/reference/diagramas.md +123 -0
  401. package/skills/artifacts-builder/reference/estilo.md +123 -0
  402. package/skills/artifacts-builder/reference/exportar-pdf.md +56 -0
  403. package/skills/artifacts-builder/reference/graficas.md +22 -0
  404. package/skills/artifacts-builder/scripts/bundle_artifact.py +109 -0
  405. package/skills/artifacts-builder/scripts/validate_artifact.py +345 -0
  406. package/skills/xone-hotswap/SKILL.md +185 -0
  407. package/skills/xone-hotswap/references/comandos.md +706 -0
  408. package/skills/xone-hotswap/references/conexion-y-despliegue.md +226 -0
@@ -7,6 +7,7 @@
7
7
  * lista de líneas y un acumulador. Sin esa costura la consola no se puede probar.
8
8
  */
9
9
  import { randomUUID } from "node:crypto";
10
+ import { existsSync } from "node:fs";
10
11
  import { homedir } from "node:os";
11
12
  import { join } from "node:path";
12
13
  import { cmdConfig } from "./config.js";
@@ -15,21 +16,250 @@ import { cmdDoctor } from "./doctor.js";
15
16
  import { cmdVerify } from "./verify.js";
16
17
  import { AgenteGuionizado } from "../agent/guionizado.js";
17
18
  import { correrTurno } from "../core/turno.js";
18
- import { PAPELES, POR_OMISION, ModeloMalEscrito, parsear, PROVEEDORES } from "../core/modelos.js";
19
- import { esDoble } from "../core/ports.js";
19
+ import { PAPELES, POR_OMISION, ModeloMalEscrito, parsear, PROVEEDORES, VARIABLES_POR_PROVEEDOR, esProveedorPersonalizado, variableDeProveedor, } from "../core/modelos.js";
20
+ import { motivoDeClaveInaceptable } from "../core/config.js";
21
+ import { ES_DOBLE, esDoble } from "../core/ports.js";
22
+ import { interpretAnswer } from "../vendor/hitl.js";
20
23
  import { crearPielStdio } from "./stdio.js";
24
+ import { esTema, seleccionarTema, TEMAS } from "./tema.js";
25
+ import { acuseDeModelo } from "./acuseDeModelo.js";
21
26
  import { guardarCredencial, AuthRotoEnDisco } from "../agent/authEnDisco.js";
22
- import { cargar, NOMBRE_CARPETA } from "../agent/configEnDisco.js";
27
+ import { cargarSettings, guardarSinAprobacion } from "../agent/settingsEnDisco.js";
28
+ import { seAplicaSinAprobacion } from "../core/settings.js";
29
+ import { URL_CLOUDSTUDIO_POR_OMISION } from "../agent/cloudstudioMcp.js";
30
+ import { cargar, cloudstudioDelProyecto, NOMBRE_CARPETA } from "../agent/configEnDisco.js";
31
+ import { rutaMemoriaDeProyecto } from "../agent/memoriaDeProyecto.js";
23
32
  /**
24
- * Duplicado a propósito de `cli/config.ts` (que a su vez lo duplica de
25
- * `agent/configEnDisco.ts`, no exportado y no tocable): misma regla y misma
26
- * omisión — `ollama` no lleva variable porque no necesita credencial.
33
+ * Saludo local de la consola. No procede del modelo ni se incorpora al hilo: debe hacer
34
+ * visible que xonecode está listo sin gastar tokens ni sesgar la primera petición.
27
35
  */
28
- const VARIABLE_POR_PROVEEDOR = {
29
- anthropic: "ANTHROPIC_API_KEY",
30
- openai: "OPENAI_API_KEY",
31
- gemini: "GOOGLE_API_KEY",
32
- };
36
+ export const MENSAJE_BIENVENIDA = "¡Bienvenido a xonecode! Puedo analizar, explicar y modificar tu proyecto XOne. Escribe `/` para ver los comandos o cuéntame qué necesitas.\n";
37
+ /**
38
+ * Hay estado que retomar solo cuando existe la memoria del PROYECTO. Una carpeta
39
+ * `.xonecode` con `config.json` aislado no representa trabajo anterior y debe conservar
40
+ * el saludo de primera visita.
41
+ */
42
+ export function hayEstadoDeProyecto(raiz) {
43
+ return existsSync(rutaMemoriaDeProyecto(raiz));
44
+ }
45
+ /**
46
+ * Petición interna de reanudación. Es deliberadamente de solo lectura y estrecha para
47
+ * que abrir una sesión existente no se convierta en otra exploración exhaustiva.
48
+ */
49
+ export const PETICION_REANUDAR_PROYECTO = "Reanuda el proyecto. Lee solo /MEMORIA_PROYECTO.md y resume brevemente el contexto, " +
50
+ "los pendientes confirmados y el siguiente paso recomendado. No modifiques nada, no cargues skills " +
51
+ "ni explores el código salvo que la memoria cite una ruta imprescindible para aclarar un pendiente; " +
52
+ "en ese caso lee solo sus primeras 50 líneas. Termina preguntando si quieres continuar ese paso.";
53
+ export const MENSAJE_REANUDANDO = "Analizando el estado guardado del repositorio…\n";
54
+ // Movida a `agent/cloudstudioMcp.ts`: es la dirección de un servidor, no un detalle de
55
+ // esta piel, y `adoptarLegadoSiProcede` (Task 4 del plan de la consola web) la necesita
56
+ // desde `agent/`, que no puede importar de `cli/`. Se reexporta para no romper a quien
57
+ // la importaba de aquí.
58
+ export { URL_CLOUDSTUDIO_POR_OMISION };
59
+ const SCOPES_STUDIO_LECTURA = ["openid", "profile", "email", "offline_access", "mcp.read"];
60
+ const SCOPES_STUDIO_AGENTE = [
61
+ "openid", "profile", "email", "offline_access", "xonewebstudioapi",
62
+ "mcp.read", "mcp.write", "mcp.execute", "mcp.branch",
63
+ ];
64
+ function modoStudio(valor) {
65
+ if (valor === "lectura")
66
+ return { nombre: "lectura", scopes: SCOPES_STUDIO_LECTURA };
67
+ if (valor === undefined || valor === "agente")
68
+ return { nombre: "agente", scopes: SCOPES_STUDIO_AGENTE };
69
+ return undefined;
70
+ }
71
+ /**
72
+ * Primer contacto con una carpeta. En pipes no se crea estado ni se pregunta: el modo
73
+ * offline histórico permanece exactamente igual. Cloud se persiste SOLO después de
74
+ * autenticar y descubrir el MCP, de modo que un login cancelado deja la carpeta virgen.
75
+ */
76
+ export async function configurarModoInicial(raiz, consola) {
77
+ if (existsSync(join(raiz, NOMBRE_CARPETA)) || !consola.interactivo || consola.guardarModoDeProyecto === undefined)
78
+ return;
79
+ let modo;
80
+ if (consola.seleccionar !== undefined) {
81
+ const elegido = await consola.seleccionar({
82
+ titulo: "Modo de proyecto",
83
+ opciones: [
84
+ { id: "cloud", etiqueta: "Cloud connected", detalle: "Conecta CloudStudio y trabaja sobre una copia sincronizada." },
85
+ { id: "offline", etiqueta: "Offline", detalle: "Trabaja solo con los ficheros presentes en esta carpeta." },
86
+ ],
87
+ });
88
+ modo = elegido === "cloud" || elegido === "offline" ? elegido : undefined;
89
+ }
90
+ else {
91
+ consola.escribir("modo de proyecto:\n 1. Cloud connected\n 2. Offline\n");
92
+ const elegido = (await consola.preguntar("número (Enter cancela): ")).trim();
93
+ modo = elegido === "1" ? "cloud" : elegido === "2" ? "offline" : undefined;
94
+ }
95
+ if (modo === undefined) {
96
+ consola.escribir("inicio cancelado: elige un modo para crear .xonecode\n");
97
+ return;
98
+ }
99
+ if (modo === "offline") {
100
+ consola.guardarModoDeProyecto("offline");
101
+ consola.escribir("→ Modo offline listo\n");
102
+ return;
103
+ }
104
+ if (consola.conectarCloudStudio === undefined || consola.guardarCloudStudioDeProyecto === undefined || consola.guardarProyectoCloudStudioDeProyecto === undefined) {
105
+ consola.escribir("CloudStudio no está disponible en esta ejecución; no se ha creado .xonecode\n");
106
+ return;
107
+ }
108
+ // El endpoint es parte de la identidad del entorno: incluso teniendo uno oficial
109
+ // por omisión, el primer arranque debe permitir elegir el CloudStudio concreto.
110
+ // Enter conserva la URL pública sin obligar a teclearla.
111
+ const escrita = await consola.preguntar(`URL MCP de CloudStudio [${URL_CLOUDSTUDIO_POR_OMISION}]: `);
112
+ const url = escrita.trim() || URL_CLOUDSTUDIO_POR_OMISION;
113
+ consola.escribir("Configurando CloudStudio…\n");
114
+ let resultado;
115
+ try {
116
+ resultado = await consola.conectarCloudStudio(url, SCOPES_STUDIO_AGENTE, consola.escribir);
117
+ }
118
+ catch (error) {
119
+ // El asistente inicial nunca puede tirar abajo la consola: ni un OAuth cancelado
120
+ // ni un MCP con un catálogo distinto deben dejar la carpeta a medio configurar.
121
+ const detalle = error instanceof Error ? error.message : String(error);
122
+ consola.escribir(`No se pudo conectar a CloudStudio: ${detalle}\n`);
123
+ consola.escribir("No se ha creado .xonecode. Reinicia xonecode para elegir Offline o probar otra URL.\n");
124
+ return;
125
+ }
126
+ if (resultado.proyectos.length === 0) {
127
+ consola.escribir("CloudStudio no devolvió proyectos seleccionables; no se ha creado .xonecode\n");
128
+ return;
129
+ }
130
+ let id;
131
+ if (consola.seleccionar !== undefined) {
132
+ id = await consola.seleccionar({
133
+ titulo: "Proyecto de CloudStudio",
134
+ opciones: resultado.proyectos.map((proyecto) => ({ id: proyecto.id, etiqueta: proyecto.nombre, detalle: proyecto.id })),
135
+ });
136
+ }
137
+ else {
138
+ for (const [indice, proyecto] of resultado.proyectos.entries())
139
+ consola.escribir(` ${indice + 1}. ${proyecto.nombre}\n`);
140
+ const indice = Number((await consola.preguntar("número (Enter cancela): ")).trim()) - 1;
141
+ id = Number.isInteger(indice) ? resultado.proyectos[indice]?.id : undefined;
142
+ }
143
+ const proyecto = resultado.proyectos.find((candidato) => candidato.id === id);
144
+ if (proyecto === undefined) {
145
+ consola.escribir("selección cancelada; no se ha creado .xonecode\n");
146
+ return;
147
+ }
148
+ consola.guardarCloudStudioDeProyecto(resultado.url, resultado.scopes);
149
+ consola.guardarProyectoCloudStudioDeProyecto(proyecto);
150
+ // El modo se guarda YA: `sincronizar` (más abajo) relee la config del disco, y un
151
+ // fallo en la descarga no puede dejar «cloudstudio» apuntado sin ningún «modo».
152
+ consola.guardarModoDeProyecto("cloud");
153
+ // Paso 3: rama ORIGEN (de la que se baja y contra la que se compara) y descarga de la
154
+ // copia local. Las ramas entran inyectadas, hermanas de `conectarCloudStudio`: esta capa
155
+ // tampoco conoce MCP para esto. Hoy los proyectos suelen tener solo `master`, y con una
156
+ // sola opción no se pregunta: preguntar lo que no tiene alternativa es ruido.
157
+ let ramas = [];
158
+ try {
159
+ ramas = (await consola.ramasDeCloudStudio?.(proyecto.nombre)) ?? [];
160
+ }
161
+ catch (error) {
162
+ // Igual que el fallo de `conectarCloudStudio`: no puede tirar abajo un alta que ya
163
+ // guardó proyecto y modo. La rama cae al valor por defecto y se avisa.
164
+ const detalle = error instanceof Error ? error.message : String(error);
165
+ consola.escribir(`no se pudieron listar las ramas de CloudStudio: ${detalle}\n`);
166
+ }
167
+ let rama = ramas[0] ?? "master";
168
+ if (ramas.length > 1) {
169
+ let elegida;
170
+ if (consola.seleccionar !== undefined) {
171
+ elegida = await consola.seleccionar({
172
+ titulo: "Rama origen",
173
+ opciones: ramas.map((r) => ({ id: r, etiqueta: r })),
174
+ });
175
+ }
176
+ else {
177
+ consola.escribir("rama origen:\n");
178
+ for (const [indice, r] of ramas.entries())
179
+ consola.escribir(` ${indice + 1}. ${r}\n`);
180
+ const indice = Number((await consola.preguntar("número (Enter cancela): ")).trim()) - 1;
181
+ elegida = Number.isInteger(indice) ? ramas[indice] : undefined;
182
+ }
183
+ if (elegida === undefined) {
184
+ // A diferencia de la cancelación del modo o del proyecto, aquí YA se guardó
185
+ // `cloudstudio` y `modo: "cloud"`: decir «no se ha creado .xonecode» sería mentir.
186
+ // Se cae a la primera rama en vez de dejar el proyecto sin rama origen.
187
+ consola.escribir(`selección cancelada; se usa «${rama}» como rama origen\n`);
188
+ }
189
+ else {
190
+ rama = elegida;
191
+ }
192
+ }
193
+ consola.guardarRamaDeProyecto?.(rama);
194
+ if (consola.sincronizar !== undefined) {
195
+ consola.escribir("Descargando el proyecto…\n");
196
+ try {
197
+ const bajada = await consola.sincronizar("bajar", raiz, undefined, consola.escribir);
198
+ if (bajada.tipo === "texto")
199
+ consola.escribir(bajada.texto);
200
+ else {
201
+ // El alta es el camino por el que se pierde trabajo sin haber escrito `/sync`:
202
+ // quien arranca xonecode en una carpeta con trabajo y elige modo cloud. Bajar
203
+ // sobrescribe el disco, así que aquí se dice qué hay y NO se baja nada.
204
+ consola.escribir(`no se ha descargado nada: hay trabajo local sin commitear (${bajada.pendientes.join(", ")}). ` +
205
+ "La descarga sobrescribe el disco; commitea (o guarda) y usa /sync bajar\n");
206
+ }
207
+ }
208
+ catch (error) {
209
+ // Un fallo de red o de servidor durante la descarga no puede tirar abajo el alta:
210
+ // .xonecode ya quedó escrito y el usuario puede reintentar con /sync bajar.
211
+ const detalle = error instanceof Error ? error.message : String(error);
212
+ consola.escribir(`no se pudo descargar el proyecto: ${detalle}; prueba /sync bajar\n`);
213
+ }
214
+ }
215
+ // Paso 4: modelo propio del proyecto, opcional. Por omisión NO: hereda el global, que
216
+ // es lo que el usuario acaba de elegir en el asistente de cuenta.
217
+ if (consola.guardarModelosDeProyecto !== undefined) {
218
+ let quierePropio = false;
219
+ if (consola.seleccionar !== undefined) {
220
+ const elegido = await consola.seleccionar({
221
+ titulo: "Modelo de este proyecto",
222
+ opciones: [
223
+ { id: "global", etiqueta: "Usar el modelo global", detalle: "lo normal" },
224
+ { id: "propio", etiqueta: "Elegir uno solo para este proyecto" },
225
+ ],
226
+ });
227
+ quierePropio = elegido === "propio";
228
+ }
229
+ else {
230
+ consola.escribir("modelo de este proyecto:\n 1. Usar el modelo global\n 2. Elegir uno para este proyecto\n");
231
+ quierePropio = (await consola.preguntar("número (Enter = usar el global): ")).trim() === "2";
232
+ }
233
+ if (quierePropio) {
234
+ let proveedor;
235
+ if (consola.seleccionar !== undefined) {
236
+ const elegido = await consola.seleccionar({
237
+ titulo: "Proveedor del modelo de este proyecto",
238
+ opciones: PROVEEDORES.map((p) => ({ id: p, etiqueta: p })),
239
+ });
240
+ proveedor = validarProveedor(elegido);
241
+ }
242
+ else {
243
+ consola.escribir(`proveedores: ${PROVEEDORES.join(", ")}\n`);
244
+ proveedor = validarProveedor((await consola.preguntar("proveedor: ")).trim());
245
+ }
246
+ if (proveedor === undefined) {
247
+ consola.escribir("proveedor inválido; se mantiene el modelo global\n");
248
+ }
249
+ else {
250
+ const modeloId = await seleccionarModelo(proveedor, consola);
251
+ if (modeloId !== undefined) {
252
+ const id = `${proveedor}/${modeloId}`;
253
+ for (const papel of PAPELES)
254
+ consola.guardarModelosDeProyecto(papel, id);
255
+ consola.escribir(`→ ${id} guardado para este proyecto\n`);
256
+ }
257
+ }
258
+ }
259
+ }
260
+ // No se vuelca el catálogo: es configuración de transporte, no parte de la conversación.
261
+ consola.escribir("→ Entorno cloud listo\n");
262
+ }
33
263
  /**
34
264
  * Un turno con `AgenteGuionizado`, igual que el `cmdRun` sin `--real`.
35
265
  *
@@ -39,7 +269,9 @@ const VARIABLE_POR_PROVEEDOR = {
39
269
  */
40
270
  export async function ejecutarTurnoGuionizado(peticion, _estado, consola) {
41
271
  const agente = new AgenteGuionizado();
42
- const piel = crearPielStdio(consola.escribir);
272
+ // La costura: la piel de la consola si la aporta (la TUI), y el render stdio de siempre
273
+ // si no — el camino de hoy, byte-idéntico cuando el campo no está.
274
+ const piel = consola.piel?.() ?? crearPielStdio(consola.escribir);
43
275
  if (esDoble(agente)) {
44
276
  consola.escribir("⚠ AGENTE DE PEGA: esto es un guion, no ha corrido ningún modelo.\n\n");
45
277
  }
@@ -47,6 +279,13 @@ export async function ejecutarTurnoGuionizado(peticion, _estado, consola) {
47
279
  avisos: (b) => (b.corrio("verify") && esDoble(agente) ? ["⚠ El veredicto es de pega."] : []),
48
280
  });
49
281
  }
282
+ // La función entera es de pega, no un turno suyo en concreto: TODO turno que pase por aquí
283
+ // usa `new AgenteGuionizado()`, así que la marca no depende de qué pasó en esta llamada.
284
+ // `web/servidor/vestibulo.ts#volcar` la mira para decidir si un turno se escribe a
285
+ // `.xonecode/sesiones/` — un transcript de pega indistinguible de uno real ensuciaría el
286
+ // historial del proyecto, y la decisión tiene que tomarla esta marca y no un booleano ni
287
+ // una bandera leída del argv, por la misma razón de siempre: se puede olvidar.
288
+ ejecutarTurnoGuionizado[ES_DOBLE] = true;
50
289
  /** El nombre del tipo de error y su mensaje: «ModeloMalEscrito: ...» no cuenta lo mismo que «Error: ...». */
51
290
  function describirError(e) {
52
291
  if (e instanceof Error)
@@ -61,6 +300,221 @@ function describirError(e) {
61
300
  function rutaAuthParaAviso() {
62
301
  return join(homedir(), NOMBRE_CARPETA, "auth.json");
63
302
  }
303
+ /** Filtra por id o nombre sin alterar el orden publicado por el proveedor. */
304
+ export function filtrarModelos(modelos, filtro) {
305
+ const buscado = filtro.trim().toLowerCase();
306
+ if (buscado === "")
307
+ return [...modelos];
308
+ return modelos.filter((modelo) => modelo.id.toLowerCase().includes(buscado) || modelo.nombre?.toLowerCase().includes(buscado));
309
+ }
310
+ /** Un número visible es uno basado en uno; Enter significa cancelar, no elegir el primero. */
311
+ export function elegirPorNumero(modelos, respuesta) {
312
+ const texto = respuesta.trim();
313
+ if (texto === "")
314
+ return { tipo: "cancelado" };
315
+ if (!/^\d+$/.test(texto))
316
+ return { tipo: "invalido" };
317
+ const modelo = modelos[Number(texto) - 1];
318
+ return modelo === undefined ? { tipo: "invalido" } : { tipo: "elegido", modelo };
319
+ }
320
+ /** Un papel vacío permite salir del flujo, pero cualquier otro texto debe ser exacto. */
321
+ export function elegirPapel(respuesta) {
322
+ const texto = respuesta.trim().toLowerCase();
323
+ return PAPELES.includes(texto) ? texto : undefined;
324
+ }
325
+ /** La global solo tiene efecto inmediato si no hay una fuente de rango superior para ese papel. */
326
+ export function fuenteQueEclipsaGlobal(papel, fuentes, seleccionesDeCatalogo = {}) {
327
+ if ((fuentes.porPapel?.[papel] !== undefined &&
328
+ fuentes.porPapel[papel] !== seleccionesDeCatalogo[papel]) ||
329
+ fuentes.bandera !== undefined) {
330
+ return "bandera";
331
+ }
332
+ if (fuentes.entorno?.XONECODE_MODELO !== undefined)
333
+ return "entorno";
334
+ if (fuentes.proyecto?.modelos?.[papel] !== undefined || fuentes.proyecto?.modelo !== undefined) {
335
+ return "proyecto";
336
+ }
337
+ return undefined;
338
+ }
339
+ /**
340
+ * El proveedor que se teclea en `/modelos` y en `/provider`.
341
+ *
342
+ * Acepta también un personalizado por su FORMA (`custom:<slug>`), igual que `parsear`: que
343
+ * ese slug esté dado de alta lo dirá el catálogo, con un mensaje que dice dónde darlo de
344
+ * alta. Rechazarlo aquí obligaría a este módulo a leer el `config.json` global solo para
345
+ * validar un nombre.
346
+ */
347
+ function validarProveedor(nombre) {
348
+ if (nombre === undefined)
349
+ return undefined;
350
+ if (PROVEEDORES.includes(nombre))
351
+ return nombre;
352
+ return esProveedorPersonalizado(nombre) ? nombre : undefined;
353
+ }
354
+ /** Exportada: `main.ts` la usa para el contexto de `asistenteDeModelo` (stdio y TUI). */
355
+ export function hayCredencial(proveedor, raiz) {
356
+ const variable = variableDeProveedor(proveedor);
357
+ if (variable === undefined)
358
+ return true;
359
+ const enEntorno = process.env[variable];
360
+ if (enEntorno !== undefined && enEntorno.trim() !== "")
361
+ return true;
362
+ return cargar(raiz).auth[proveedor] !== undefined;
363
+ }
364
+ function describirModelo(modelo) {
365
+ const etiqueta = modelo.nombre ?? modelo.id;
366
+ const contexto = modelo.contexto === undefined ? "" : `, ctx ${modelo.contexto}`;
367
+ return `${etiqueta} (${modelo.proveedor}/${modelo.id}${contexto})`;
368
+ }
369
+ /**
370
+ * El selector de modelo de un catálogo ya elegido: la parte que `/modelos` y el alta de
371
+ * proyecto (`configurarModoInicial`) COMPARTEN, para que no haya dos copias del mismo
372
+ * flujo (rico con `seleccionar`, o numerado por `preguntar` cuando esa costura no existe).
373
+ * Devuelve el id crudo del catálogo (sin el prefijo `<proveedor>/`) o `undefined` si se
374
+ * canceló o no había nada entre lo que elegir — los mensajes de cada caso ya se escriben
375
+ * aquí, así que quien llama no repite el aviso.
376
+ */
377
+ export async function seleccionarModelo(proveedor, consola) {
378
+ const modelos = await consola.catalogoModelos.listar(proveedor);
379
+ if (modelos.length === 0) {
380
+ consola.escribir(`no hay modelos disponibles para ${proveedor}\n`);
381
+ return undefined;
382
+ }
383
+ if (consola.seleccionar !== undefined) {
384
+ const opciones = modelos.map((modelo) => ({
385
+ id: modelo.id,
386
+ etiqueta: modelo.nombre ?? modelo.id,
387
+ detalle: describirModelo(modelo),
388
+ }));
389
+ const elegido = await consola.seleccionar({ titulo: `Modelos de ${proveedor}`, opciones });
390
+ const modelo = modelos.find((candidato) => candidato.id === elegido);
391
+ if (modelo === undefined) {
392
+ consola.escribir("selección cancelada\n");
393
+ return undefined;
394
+ }
395
+ return modelo.id;
396
+ }
397
+ const filtro = await consola.preguntar("filtro (Enter para todos): ");
398
+ const filtrados = filtrarModelos(modelos, filtro);
399
+ if (filtrados.length === 0) {
400
+ consola.escribir("no hay modelos que coincidan con el filtro\n");
401
+ return undefined;
402
+ }
403
+ for (const [indice, modelo] of filtrados.entries()) {
404
+ consola.escribir(`${indice + 1}. ${describirModelo(modelo)}\n`);
405
+ }
406
+ const eleccion = elegirPorNumero(filtrados, await consola.preguntar("número (Enter cancela): "));
407
+ if (eleccion.tipo === "cancelado") {
408
+ consola.escribir("selección cancelada\n");
409
+ return undefined;
410
+ }
411
+ if (eleccion.tipo === "invalido") {
412
+ consola.escribir("número inválido\n");
413
+ return undefined;
414
+ }
415
+ return eleccion.modelo.id;
416
+ }
417
+ /** Flujo interactivo de catálogo; los fallos previstos nunca devuelven un estado nuevo. */
418
+ async function elegirModelo(args, estado, consola) {
419
+ const proveedor = validarProveedor(args[0]);
420
+ if (proveedor === undefined) {
421
+ const escrito = args[0];
422
+ consola.escribir(escrito === undefined
423
+ ? `uso: /modelos <proveedor> — proveedores: ${PROVEEDORES.join(", ")} (o «custom:<id>»)\n`
424
+ : `proveedor «${escrito}» desconocido. Los que hay: ${PROVEEDORES.join(", ")} (o «custom:<id>», dado de alta en Ajustes)\n`);
425
+ return { seguir: true };
426
+ }
427
+ if (!hayCredencial(proveedor, estado.raiz)) {
428
+ consola.escribir(`falta la credencial para ${proveedor}; usa /provider ${proveedor}\n`);
429
+ return { seguir: true };
430
+ }
431
+ const modeloId = await seleccionarModelo(proveedor, consola);
432
+ if (modeloId === undefined)
433
+ return { seguir: true };
434
+ let papel;
435
+ if (consola.seleccionar !== undefined) {
436
+ const papelElegido = await consola.seleccionar({
437
+ titulo: "Asignar modelo a",
438
+ opciones: PAPELES.map((p) => ({ id: p, etiqueta: p })),
439
+ });
440
+ papel = elegirPapel(papelElegido ?? "");
441
+ }
442
+ else {
443
+ const respuestaPapel = await consola.preguntar("papel (rapido/trabajo/afilado): ");
444
+ if (respuestaPapel.trim() === "") {
445
+ consola.escribir("selección cancelada\n");
446
+ return { seguir: true };
447
+ }
448
+ papel = elegirPapel(respuestaPapel);
449
+ if (papel === undefined) {
450
+ consola.escribir(`papel inválido; elige: ${PAPELES.join(", ")}\n`);
451
+ return { seguir: true };
452
+ }
453
+ }
454
+ if (papel === undefined) {
455
+ consola.escribir("selección cancelada\n");
456
+ return { seguir: true };
457
+ }
458
+ return guardarEleccionDeModelo(proveedor, modeloId, papel, estado, consola);
459
+ }
460
+ /** Selector de tema: misma costura rica que /modelos, con pregunta numerada en stdio. */
461
+ async function elegirTema(_args, _estado, consola) {
462
+ let id;
463
+ if (consola.seleccionar !== undefined) {
464
+ id = await consola.seleccionar({
465
+ titulo: "Tema de xonecode",
466
+ opciones: TEMAS.map((tema) => ({ id: tema.id, etiqueta: tema.etiqueta, detalle: tema.detalle })),
467
+ });
468
+ }
469
+ else {
470
+ consola.escribir("temas:\n");
471
+ for (const [indice, tema] of TEMAS.entries()) {
472
+ consola.escribir(` ${indice + 1}. ${tema.etiqueta} — ${tema.detalle}\n`);
473
+ }
474
+ const respuesta = (await consola.preguntar("número (Enter cancela): ")).trim();
475
+ if (respuesta === "")
476
+ return { seguir: true };
477
+ const indice = Number(respuesta) - 1;
478
+ id = Number.isInteger(indice) ? TEMAS[indice]?.id : undefined;
479
+ }
480
+ if (id === undefined) {
481
+ consola.escribir("selección cancelada\n");
482
+ return { seguir: true };
483
+ }
484
+ if (!esTema(id)) {
485
+ consola.escribir("tema inválido\n");
486
+ return { seguir: true };
487
+ }
488
+ (consola.aplicarTema ?? seleccionarTema)(id);
489
+ const tema = TEMAS.find((candidato) => candidato.id === id);
490
+ const guardado = consola.guardarTemaDeProyecto?.(id);
491
+ consola.escribir(guardado === undefined
492
+ ? `tema activo: ${tema.etiqueta}\n`
493
+ : `tema activo: ${tema.etiqueta} · guardado en ${guardado.ruta}\n`);
494
+ return { seguir: true };
495
+ }
496
+ function guardarEleccionDeModelo(proveedor, modeloId, papel, estado, consola) {
497
+ const id = `${proveedor}/${modeloId}`;
498
+ consola.guardarModeloGlobal(papel, id);
499
+ const eclipsa = fuenteQueEclipsaGlobal(papel, estado.fuentes, estado.seleccionesDeCatalogo);
500
+ if (eclipsa !== undefined) {
501
+ consola.escribir(`modelo ${papel} guardado en global; sigue activo el de ${eclipsa}\n`);
502
+ return { seguir: true };
503
+ }
504
+ const fuentes = {
505
+ ...estado.fuentes,
506
+ porPapel: { ...estado.fuentes.porPapel, [papel]: id },
507
+ };
508
+ consola.escribir(acuseDeModelo(papel, id));
509
+ return {
510
+ seguir: true,
511
+ estado: {
512
+ ...estado,
513
+ fuentes,
514
+ seleccionesDeCatalogo: { ...estado.seleccionesDeCatalogo, [papel]: id },
515
+ },
516
+ };
517
+ }
64
518
  /**
65
519
  * El `completer` de readline (ver docs de node:readline: firma síncrona
66
520
  * `(line: string) => [string[], string]`). Dos mundos:
@@ -148,8 +602,92 @@ function manejadorDeModelo(papel) {
148
602
  const fuentes = papel === undefined
149
603
  ? { ...estado.fuentes, bandera: valor }
150
604
  : { ...estado.fuentes, porPapel: { ...estado.fuentes.porPapel, [papel]: valor } };
151
- consola.escribir(papel === undefined ? `modelo (los tres papeles): ${valor}\n` : `modelo ${papel}: ${valor}\n`);
152
- return { seguir: true, estado: { ...estado, fuentes } };
605
+ // La frase vive en `acuseDeModelo.ts`: la TUI la re-parsea para su sidebar, así
606
+ // que escribir y leer comparten módulo retocar la frase aquí no rompe a ciegas.
607
+ consola.escribir(acuseDeModelo(papel, valor));
608
+ const seleccionesDeCatalogo = { ...estado.seleccionesDeCatalogo };
609
+ if (papel === undefined) {
610
+ for (const p of PAPELES)
611
+ delete seleccionesDeCatalogo[p];
612
+ }
613
+ else {
614
+ delete seleccionesDeCatalogo[papel];
615
+ }
616
+ return {
617
+ seguir: true,
618
+ estado: {
619
+ ...estado,
620
+ fuentes,
621
+ seleccionesDeCatalogo: Object.keys(seleccionesDeCatalogo).length === 0 ? undefined : seleccionesDeCatalogo,
622
+ },
623
+ };
624
+ };
625
+ }
626
+ /**
627
+ * La ÚNICA política de aprobación que existe hoy: rellena el hueco de
628
+ * `core/cloudstudio.ts#PoliticaDeAprobacion` con un humano que ve el plan y responde.
629
+ * Vive aquí, en `cli/`, y no en `agent/subida.ts`: la política es de la piel, el hueco es
630
+ * del motor.
631
+ *
632
+ * **Es MÁS estricta que `cli/aprobar.ts`, a propósito: aquí el Enter a secas no aprueba
633
+ * ni con TTY.** No es el mismo acto. En `aprobar.ts` se autoriza una escritura con su
634
+ * diff delante, línea a línea, y el Enter que aprueba lo pulsa alguien que está mirando
635
+ * el cambio; aquí se PUBLICA en un servidor remoto, lo que hay en pantalla es una lista
636
+ * de rutas —no el contenido— y es la operación menos reversible del producto. El detalle
637
+ * que lo hace peligroso no se ve: `APPROVALS_TTY` (`vendor/hitl.ts`) contiene la cadena
638
+ * VACÍA, así que pasarle `interactive` a `interpretAnswer` convertiría en «sube» toda
639
+ * respuesta vacía — y `consola.preguntar` devuelve "" también cuando el readline ya está
640
+ * cerrado (Ctrl-D, un stdin agotado a mitad de turno). Un EOF no puede publicar. Por eso
641
+ * se llama sin opciones, contra `APPROVALS_NO_TTY`, y el prompt enseña `[s/N]` siempre:
642
+ * un default visible que no fuera el real sería mentir en el paso que sube.
643
+ *
644
+ * **Y el plan viaja CON la pregunta, no solo delante de ella** (`DecisionDeConsola`): la
645
+ * tarjeta de la consola web enseña la lista y ofrece Aceptar/Cancelar, sin campo donde
646
+ * teclear, y para eso hay que decirle que la respuesta es sí o no. Adivinarlo del `[s/N]`
647
+ * sería leer la sintaxis, y el día que ese prompt cambie la tarjeta ofrecería un editor
648
+ * para una decisión —o dos botones sobre una pregunta abierta— sin un solo error.
649
+ *
650
+ * La política AUTÓNOMA (el papel `afilado` decidiendo sin que nadie mire) todavía no
651
+ * existe — hace falta, además del veredicto del juez, que el código compruebe
652
+ * condiciones deterministas (verificador en verde, árbol limpio, plan sin pendientes):
653
+ * ver el comentario de `PoliticaDeAprobacion`.
654
+ *
655
+ * **`decir` es el sumidero de la operación**, y entra por parámetro porque el plan NO siempre
656
+ * se cuenta en el scrollback: una piel con registro propio (`anotarSincronizacion`) guarda la
657
+ * operación entera y no quiere ni una línea suelta en el hilo. Por omisión es `consola.escribir`,
658
+ * así que quien no pase nada —los tests, cualquier otro llamador— tiene el comportamiento de
659
+ * siempre.
660
+ */
661
+ export function politicaInteractiva(consola, decir = (texto) => consola.escribir(texto)) {
662
+ return async (plan) => {
663
+ // El plan se compone ANTES de escribirlo, y no es por estilo: las MISMAS líneas que van
664
+ // al scrollback viajan en la PREGUNTA, para que una piel con tarjeta las enseñe dentro
665
+ // de ella —el paso donde se decide— en vez de dejarlas en un transcript que puede estar
666
+ // a varias pantallas de scroll. Se escriben una a una, como siempre: en la web cada
667
+ // acto de sistema es una línea, y las pieles de terminal no cambian.
668
+ //
669
+ // Cada línea lleva ADEMÁS lo que el signo significa (`cambio`), y el signo se deriva de
670
+ // ahí en vez de al revés: una piel que quiera enseñar qué se añade y qué se borra no
671
+ // puede leer el `+`/`~`/`-`, que es sintaxis. Y la clase sale de la OPERACIÓN, que es
672
+ // de donde la sabe `planDeSubida`: un `borrado` no la lleva —su `tipo` ya la dice—, así
673
+ // que la línea de un borrado la toma del `tipo`.
674
+ const lineas = [
675
+ { texto: `SUBIDA A CLOUDSTUDIO — ${plan.length} ${plan.length === 1 ? "operación" : "operaciones"}` },
676
+ ...plan.map((operacion) => {
677
+ const cambio = operacion.tipo === "borrado" ? "borrado" : operacion.clase;
678
+ const signo = cambio === "borrado" ? "-" : cambio === "nuevo" ? "+" : "~";
679
+ return { texto: ` ${signo} ${operacion.ruta}`, cambio };
680
+ }),
681
+ ];
682
+ decir(`\n${"─".repeat(60)}\n`);
683
+ for (const linea of lineas)
684
+ decir(`${linea.texto}\n`);
685
+ // Sin pista de tecleo en el enunciado: la pone la piel que se contesta escribiendo
686
+ // (`PISTA_DE_DECISION`), y por eso viaja un `decision` con esta pregunta.
687
+ const respuesta = await consola.preguntar("¿Subir a CloudStudio?", { lineas });
688
+ const decision = interpretAnswer(respuesta);
689
+ decir(decision.type === "approve" ? " → APROBADO\n" : " → rechazado, no se ha aplicado nada\n");
690
+ return decision.type === "approve";
153
691
  };
154
692
  }
155
693
  export const COMANDOS = {
@@ -211,6 +749,174 @@ export const COMANDOS = {
211
749
  descripcion: "cambia el papel `afilado` en caliente: /modelo-afilado <proveedor>/<modelo>",
212
750
  manejador: manejadorDeModelo("afilado"),
213
751
  },
752
+ modelos: {
753
+ descripcion: "elige un modelo del catálogo de un proveedor y lo guarda globalmente: /modelos <proveedor>",
754
+ manejador: elegirModelo,
755
+ },
756
+ themes: {
757
+ descripcion: "elige tema visual: XOne, Clear, Midnight, Graphite o Ember",
758
+ manejador: elegirTema,
759
+ },
760
+ "connect-studio": {
761
+ descripcion: "conecta CloudStudio una vez: /connect-studio [url] [agente|lectura]",
762
+ manejador: async (args, _estado, consola) => {
763
+ if (consola.conectarCloudStudio === undefined || consola.guardarCloudStudioDeProyecto === undefined) {
764
+ consola.escribir("la conexión CloudStudio no está disponible en esta ejecución\n");
765
+ return { seguir: true };
766
+ }
767
+ const primerArgEsModo = args[0] === "lectura" || args[0] === "agente";
768
+ const escrita = primerArgEsModo ? "" : args[0] ?? await consola.preguntar(`URL MCP de CloudStudio [${URL_CLOUDSTUDIO_POR_OMISION}]: `);
769
+ const url = escrita.trim() || URL_CLOUDSTUDIO_POR_OMISION;
770
+ const modo = modoStudio(primerArgEsModo ? args[0] : args[1]);
771
+ if (modo === undefined) {
772
+ consola.escribir("modo inválido; elige agente o lectura\n");
773
+ return { seguir: true };
774
+ }
775
+ consola.escribir("Configurando CloudStudio…\n");
776
+ const resultado = await consola.conectarCloudStudio(url, modo.scopes, consola.escribir);
777
+ const guardado = consola.guardarCloudStudioDeProyecto(resultado.url, resultado.scopes);
778
+ // El catálogo completo es material de configuración, no una respuesta de consola:
779
+ // varias descripciones ocupan cientos de líneas y apartan al usuario de su tarea.
780
+ // La URL y los scopes siguen guardados en `.xonecode/config.json` para diagnóstico.
781
+ void guardado;
782
+ consola.escribir("→ Entorno listo\n");
783
+ return { seguir: true };
784
+ },
785
+ },
786
+ sync: {
787
+ descripcion: "sincroniza con CloudStudio: /sync [estado|bajar|subir]",
788
+ manejador: async (args, estado, consola) => {
789
+ if (consola.sincronizar === undefined) {
790
+ consola.escribir("la sincronización no está disponible en esta ejecución\n");
791
+ return { seguir: true };
792
+ }
793
+ const accion = args[0] ?? "estado";
794
+ if (accion !== "estado" && accion !== "bajar" && accion !== "subir") {
795
+ consola.escribir("uso: /sync [estado|bajar|subir]\n");
796
+ return { seguir: true };
797
+ }
798
+ // Estas dos salidas de arriba NO son una operación y no van al registro: son el mismo
799
+ // aviso de uso que da cualquier comando mal escrito, y no hay nada que contarle a nadie
800
+ // sobre una sincronización que no llegó a empezar.
801
+ /**
802
+ * **Dónde se cuenta esta operación.** Es la decisión de todo el comando, y por eso está
803
+ * en una sola variable.
804
+ *
805
+ * Sin `anotarSincronizacion` —stdio, la TUI, la consola de una tarea, los dobles— el
806
+ * sumidero ES `escribir`: al vuelo, en orden, byte a byte como siempre. Con él, las
807
+ * líneas se acumulan y se entrega UNA operación al final, ya con su acción y su hora:
808
+ * en el navegador el recorrido de una subida no tiene por qué meterse en el hilo, donde
809
+ * son nueve renglones de consola cruda entre dos mensajes de verdad (medido).
810
+ *
811
+ * Las líneas se guardan TAL CUAL y solo se les quita el `\n` final y las que van vacías:
812
+ * el salto de línea es del scrollback —donde cada `escribir` es una línea— y no del dato,
813
+ * y la raya que `politicaInteractiva` dibuja arriba del plan es parte de lo que se dijo.
814
+ * Recomponer aquí sería una segunda versión de algo que ya está contado en `agent/`, y
815
+ * la copia que nadie vuelve a leer es la que se queda vieja.
816
+ */
817
+ const anotar = consola.anotarSincronizacion;
818
+ const lineas = [];
819
+ const decir = anotar === undefined
820
+ ? (texto) => consola.escribir(texto)
821
+ : (texto) => {
822
+ for (const linea of texto.replace(/\n$/, "").split("\n")) {
823
+ if (linea.trim() !== "")
824
+ lineas.push(linea);
825
+ }
826
+ };
827
+ const cuando = new Date().toISOString();
828
+ try {
829
+ // El hueco de política solo se rellena para «subir»: es la única acción que
830
+ // escribe. `agent/subida.ts#subir` la invoca con el plan YA CONSTRUIDO, así que
831
+ // esto puede pasarse siempre — si el árbol está sucio o no hay nada que subir, ni
832
+ // siquiera llega a invocarse.
833
+ const politicaDeAprobacion = accion === "subir" ? politicaInteractiva(consola, decir) : undefined;
834
+ const resultado = await consola.sincronizar(accion, estado.raiz, politicaDeAprobacion, decir);
835
+ if (resultado.tipo === "arbol-sucio") {
836
+ // Al subir se sube el estado de un COMMIT, no un borrador: así «lo que está
837
+ // arriba» es siempre un commit concreto y mover la ref significa algo. Al bajar
838
+ // el motivo es otro y hay que decirlo: la descarga SOBRESCRIBE el disco.
839
+ const porque = resultado.accion === "subir"
840
+ ? "commitea antes de subir"
841
+ : "la descarga sobrescribe el disco; commitea antes de bajar (o guarda una copia, si no llevas git)";
842
+ decir(`hay cambios sin commitear (${resultado.pendientes.join(", ")}); ${porque}\n`);
843
+ }
844
+ else {
845
+ decir(resultado.texto);
846
+ }
847
+ }
848
+ finally {
849
+ // En el `finally` y no al final del `try`, por un motivo medido: `crearSincronizador`
850
+ // puede LANZAR —`limpio`, `descargar`, `subirProyecto` y hasta el `sesion.cerrar()` de
851
+ // su propio `finally` son `await` de cosas que tocan la red y el disco—, y con las
852
+ // líneas acumuladas una excepción se llevaría por delante todo lo que ya se había
853
+ // contado. En el scrollback de stdio, que es append-only, no se perdía nada.
854
+ //
855
+ // La excepción se deja PROPAGAR: un fallo inesperado lo dice el harness en el hilo,
856
+ // que es donde tiene que verse. Esconderlo en el registro sería lo contrario de lo
857
+ // que esto busca — el registro es de las operaciones que corrieron, no de las que no.
858
+ anotar?.({ accion, cuando, lineas });
859
+ }
860
+ return { seguir: true };
861
+ },
862
+ },
863
+ aprobacion: {
864
+ descripcion: "quién aprueba las escrituras aquí: /aprobacion [humana|automatica]",
865
+ manejador: async (args, estado, consola) => {
866
+ const raiz = estado.raiz;
867
+ // Del DISCO y por la raíz: en la consola web `fuentes.proyecto` no se rellena, así
868
+ // que preguntárselo daría «offline» para cualquier proyecto, conectado o no.
869
+ const cloudstudio = cloudstudioDelProyecto(raiz);
870
+ const vigente = () => seAplicaSinAprobacion({
871
+ raiz,
872
+ sinAprobacion: cargarSettings().settings.sinAprobacion,
873
+ cloudstudio,
874
+ // La misma cuenta que `pedirDecisiones`: `interactivo` a secas es `true` a fuego
875
+ // en la consola web, así que sin el `eof` este comando diría «automatica» con la
876
+ // pestaña ya cerrada.
877
+ interactivo: consola.interactivo && !(consola.eof?.() ?? false),
878
+ });
879
+ const que = args[0];
880
+ if (que === undefined) {
881
+ consola.escribir(vigente()
882
+ ? "automatica: las escrituras de este proyecto se aplican SIN preguntar\n"
883
+ : "humana: cada escritura pide aprobación con su diff delante\n");
884
+ // Las tres condiciones se dicen cuando la respuesta es «humana», porque entonces la
885
+ // pregunta que sigue siempre es «¿y por qué?». Un «no» sin motivo manda a adivinar.
886
+ if (!vigente() && cargarSettings().settings.sinAprobacion?.[raiz] === true) {
887
+ consola.escribir(cloudstudio !== undefined
888
+ ? " (está puesta, pero no se aplica: este proyecto sube a CloudStudio)\n"
889
+ : " (está puesta, pero no se aplica: no hay nadie delante que pueda decidir)\n");
890
+ }
891
+ return { seguir: true };
892
+ }
893
+ if (que !== "humana" && que !== "automatica") {
894
+ consola.escribir("uso: /aprobacion [humana|automatica]\n");
895
+ return { seguir: true };
896
+ }
897
+ // Se RECHAZA en vez de guardarse y no aplicarse: un ajuste escrito que no hace nada
898
+ // es peor que no poder ponerlo, porque quien lo puso se cree protegido al revés.
899
+ if (que === "automatica" && cloudstudio !== undefined) {
900
+ consola.escribir("no: este proyecto está conectado a CloudStudio, y lo que se escriba aquí sube al\n" +
901
+ "trabajo de otras personas. La aprobación no es una preferencia ahí.\n");
902
+ return { seguir: true };
903
+ }
904
+ const { ruta } = guardarSinAprobacion(undefined, raiz, que === "automatica");
905
+ if (que === "automatica") {
906
+ // La ruta del SETTINGS sí se dice —es donde se edita a mano, y `/config` y `/tema`
907
+ // ya imprimen la suya—, pero la del proyecto no: quien teclea esto sabe dónde está,
908
+ // y por esta salida pasa un acto del transcript que viaja por el cable.
909
+ consola.escribir("hecho: las escrituras de este proyecto se aplicarán SIN preguntar.\n" +
910
+ ` Cada turno que escriba lo dirá, con los nombres de los ficheros.\n` +
911
+ ` Se guarda en ${ruta} —en tu máquina, no en el proyecto— y renombrar la\n` +
912
+ " carpeta lo pierde: entonces se vuelve a preguntar.\n");
913
+ }
914
+ else {
915
+ consola.escribir(`hecho: cada escritura vuelve a pedir aprobación. Guardado en ${ruta}.\n`);
916
+ }
917
+ return { seguir: true };
918
+ },
919
+ },
214
920
  hilo: {
215
921
  descripcion: "muestra el hilo (thread_id) de esta sesión",
216
922
  manejador: async (_args, estado, consola) => {
@@ -223,7 +929,17 @@ export const COMANDOS = {
223
929
  manejador: async (_args, estado, consola) => {
224
930
  // Mismo patrón que run.ts: prefijo xonecode- + uuid.
225
931
  const nuevoHilo = `xonecode-${randomUUID()}`;
226
- consola.escribir(`hilo nuevo: ${nuevoHilo}\n`);
932
+ // Y se DICE lo que cuesta desde que el hilo se guarda en disco: en la web el hilo de
933
+ // una sesión ES su id, así que este hilo nuevo no es de ninguna sesión — al reabrirla
934
+ // se vuelve al de antes, y lo que se hable a partir de aquí no se podrá reabrir.
935
+ // Callarlo sería prometer que la conversación que empieza ahora se guarda como las
936
+ // demás. Quien quiere empezar de cero Y poder volver, abre una sesión nueva.
937
+ //
938
+ // En UN `escribir`, no en dos: `main.ts` repinta la barra de estado al ver una línea
939
+ // que empieza por «hilo nuevo:», así que partido en dos la barra caía en medio de las
940
+ // dos frases. El regex sigue casando porque la primera línea no cambia.
941
+ consola.escribir(`hilo nuevo: ${nuevoHilo}\n` +
942
+ "ojo: este hilo no es el de la sesión; al reabrirla se vuelve al anterior\n");
227
943
  return { seguir: true, estado: { ...estado, hilo: nuevoHilo } };
228
944
  },
229
945
  },
@@ -236,7 +952,7 @@ export const COMANDOS = {
236
952
  // existía): mismo orden de mirón que cmdConfig — primero env, luego disco.
237
953
  const cargado = cargar(estado.raiz);
238
954
  for (const p of PROVEEDORES) {
239
- const variable = VARIABLE_POR_PROVEEDOR[p];
955
+ const variable = VARIABLES_POR_PROVEEDOR[p];
240
956
  const enEntorno = variable !== undefined && process.env[variable] !== undefined;
241
957
  const enAuth = !enEntorno && cargado.auth[p] !== undefined;
242
958
  // ollama no se marca «sin credencial»: no la necesita, no le falta nada.
@@ -270,6 +986,14 @@ export const COMANDOS = {
270
986
  consola.escribir("clave vacía: no se guardó nada\n");
271
987
  return { seguir: true };
272
988
  }
989
+ // La MISMA criba que el asistente de cuenta (`core/config.ts`), no una copia: lo que
990
+ // el propio campo ya delata —una línea `NOMBRE=valor` pegada entera, comillas,
991
+ // espacios— no llega a `auth.json`, donde solo se ve al fallar una petición.
992
+ const motivo = motivoDeClaveInaceptable(clave);
993
+ if (motivo !== undefined) {
994
+ consola.escribir(`no se guardó nada: ${motivo}\n`);
995
+ return { seguir: true };
996
+ }
273
997
  try {
274
998
  const { ruta, avisos } = guardarCredencial(nombre, clave);
275
999
  consola.escribir(`credencial de ${nombre} guardada en ${ruta}\n`);
@@ -304,11 +1028,14 @@ export const COMANDOS = {
304
1028
  */
305
1029
  export async function correrConsola(consola, estado, ejecutarTurno = ejecutarTurnoGuionizado) {
306
1030
  for await (const cruda of consola.lineas) {
307
- const linea = cruda.trim();
1031
+ // Una `string` a secas es una piel donde el teclado manda (stdio, la TUI, una consola de
1032
+ // tarea, los dobles): se lee como siempre. Ver `LineaDeConsola`.
1033
+ const { texto, comoComando } = typeof cruda === "string" ? { texto: cruda, comoComando: true } : cruda;
1034
+ const linea = texto.trim();
308
1035
  // Un Enter de más no gasta ni una llamada al modelo ni un error: no hay nada que hacer.
309
1036
  if (linea === "")
310
1037
  continue;
311
- if (linea.startsWith("/")) {
1038
+ if (comoComando && linea.startsWith("/")) {
312
1039
  // El comando se compara en minúsculas; los args respetan lo tecleado (p.ej. un id
313
1040
  // de Ollama con mayúsculas tiene que llegar entero a `parsear`).
314
1041
  const trozos = linea.split(/\s+/);
@@ -325,8 +1052,12 @@ export async function correrConsola(consola, estado, ejecutarTurno = ejecutarTur
325
1052
  return 0;
326
1053
  // El estado MÁS RECIENTE manda: el turno siguiente parte de lo que devolvió el
327
1054
  // último manejador, no del estado de arranque.
328
- if (resultado.estado !== undefined)
1055
+ if (resultado.estado !== undefined) {
329
1056
  estado = resultado.estado;
1057
+ // Solo cuando cambia de verdad: avisar en cada línea haría que una piel que
1058
+ // reemite por esto mandara un mensaje por cada `/ayuda`.
1059
+ consola.alEstado?.(estado);
1060
+ }
330
1061
  }
331
1062
  catch (e) {
332
1063
  consola.escribir(`${describirError(e)}\n`);