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
@@ -0,0 +1,1667 @@
1
+ #!/usr/bin/env node
2
+
3
+ import crypto from 'node:crypto';
4
+ import { constants as fsConstants } from 'node:fs';
5
+ import fs from 'node:fs/promises';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
8
+ import { performance } from 'node:perf_hooks';
9
+ import { fileURLToPath } from 'node:url';
10
+
11
+ import {
12
+ DEFAULT_MANIFEST_URL,
13
+ SKILL_ID,
14
+ UpdateContractError,
15
+ compareSemver,
16
+ isStableCoreVersion,
17
+ parseSemver,
18
+ validateLocalRelease,
19
+ validateReleaseNotesUrl,
20
+ validateStableUpdateManifest,
21
+ } from './update-contract.mjs';
22
+
23
+ const OPERATION_STATE_FILE = 'state.json';
24
+ const OPERATION_OWNER_FILE = 'owner.json';
25
+ const ACTIVE_CLAIM_DIRECTORY = 'active-claim';
26
+ const OPERATION_NAME = /^(reserved|pending|committed|fenced|cancelled)-(\d{1,20})$/;
27
+ const MAX_OPERATION_GENERATION = (10n ** 20n) - 1n;
28
+ const CHECK_TTL_MS = 72 * 60 * 60 * 1_000;
29
+ const MAX_CACHE_HORIZON_MS = Math.ceil(CHECK_TTL_MS * 1.2);
30
+ const FIRST_FAILURE_DELAY_MS = 6 * 60 * 60 * 1_000;
31
+ const LATER_FAILURE_DELAY_MS = 24 * 60 * 60 * 1_000;
32
+ const DEFAULT_TIMEOUT_MS = 1_000;
33
+ const MAX_RESPONSE_BYTES = 32 * 1_024;
34
+ const MAX_LOCAL_RELEASE_BYTES = 4 * 1_024;
35
+ const MAX_CACHE_STATE_BYTES = 64 * 1_024;
36
+ const MAX_CLAIM_OWNER_BYTES = 1_024;
37
+ const LONGEST_ISO_TIMESTAMP = '+275760-09-13T00:00:00.000Z';
38
+ const LOCK_STALE_MS = 30_000;
39
+ const ACK_LOCK_WAIT_MS = 1_200;
40
+ const LOCK_RETRY_DELAY_MS = 20;
41
+ const DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;
42
+ const scriptDirectory = path.dirname(fileURLToPath(import.meta.url));
43
+ const defaultReleasePath = path.resolve(scriptDirectory, '..', 'skill-release.json');
44
+
45
+ class FileIdentityChangedError extends Error {}
46
+
47
+ function silent(reason) {
48
+ return { status: 'silent', reason };
49
+ }
50
+
51
+ function isPlainObject(value) {
52
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
53
+ }
54
+
55
+ function hasOnlyKeys(value, allowed) {
56
+ return Object.keys(value).every((key) => allowed.has(key));
57
+ }
58
+
59
+ function validateManifest(value) {
60
+ const manifest = validateStableUpdateManifest(value);
61
+ return {
62
+ version: manifest.version,
63
+ targetDigest: `sha256:${manifest.artifact.sha256}`,
64
+ severity: manifest.severity,
65
+ releaseNotes: manifest.releaseNotes,
66
+ };
67
+ }
68
+
69
+ function validateCachedCandidate(value) {
70
+ if (!isPlainObject(value)
71
+ || !hasOnlyKeys(value, new Set([
72
+ 'version', 'targetDigest', 'severity', 'releaseNotes',
73
+ ]))) return null;
74
+ try {
75
+ if (!isStableCoreVersion(value.version)) return null;
76
+ if (!DIGEST_PATTERN.test(value.targetDigest)
77
+ || !['normal', 'security'].includes(value.severity)) return null;
78
+ validateReleaseNotesUrl(value.releaseNotes, value.version);
79
+ return { ...value };
80
+ } catch {
81
+ return null;
82
+ }
83
+ }
84
+
85
+ function emptyState(installedVersion = null) {
86
+ return {
87
+ schemaVersion: 1,
88
+ skillId: SKILL_ID,
89
+ installedVersion,
90
+ check: {
91
+ nextCheckAt: null,
92
+ consecutiveFailures: 0,
93
+ },
94
+ notification: {
95
+ offeredDigests: [],
96
+ acknowledgedDigests: [],
97
+ },
98
+ };
99
+ }
100
+
101
+ function encodeBoundedState(state) {
102
+ const encoded = Buffer.from(`${JSON.stringify(state)}\n`, 'utf8');
103
+ return encoded.length <= MAX_CACHE_STATE_BYTES ? encoded : null;
104
+ }
105
+
106
+ function stateAfterAcknowledgement(state, targetDigest) {
107
+ const alreadyAcknowledged = state.notification.acknowledgedDigests.includes(targetDigest);
108
+ return {
109
+ ...state,
110
+ notification: {
111
+ offeredDigests: state.notification.offeredDigests
112
+ .filter((digest) => digest !== targetDigest),
113
+ acknowledgedDigests: alreadyAcknowledged
114
+ ? [...state.notification.acknowledgedDigests]
115
+ : [...state.notification.acknowledgedDigests, targetDigest],
116
+ },
117
+ };
118
+ }
119
+
120
+ function stateWithFailureCheck(state, nextCheckAt, consecutiveFailures, withdrawCandidate = false) {
121
+ const failedState = {
122
+ ...state,
123
+ check: { nextCheckAt, consecutiveFailures },
124
+ };
125
+ if (withdrawCandidate) delete failedState.candidate;
126
+ return failedState;
127
+ }
128
+
129
+ function encodeRecoverableState(state) {
130
+ const encoded = encodeBoundedState(state);
131
+ if (!encoded) return null;
132
+ if (!encodeBoundedState(stateWithFailureCheck(state, LONGEST_ISO_TIMESTAMP, 2))) return null;
133
+ let acknowledgementState = state;
134
+ for (const targetDigest of state.notification.offeredDigests) {
135
+ acknowledgementState = stateAfterAcknowledgement(acknowledgementState, targetDigest);
136
+ if (!encodeBoundedState(acknowledgementState)
137
+ || !encodeBoundedState(stateWithFailureCheck(
138
+ acknowledgementState,
139
+ LONGEST_ISO_TIMESTAMP,
140
+ 2,
141
+ ))) return null;
142
+ }
143
+ return encoded;
144
+ }
145
+
146
+ function versionCacheDirectory(cacheDirectory, installedVersion) {
147
+ const partition = crypto.createHash('sha256').update(installedVersion).digest('hex').slice(0, 24);
148
+ return path.join(cacheDirectory, `version-${partition}`);
149
+ }
150
+
151
+ function normalizeState(value, installedVersion) {
152
+ const effectiveInstalledVersion = installedVersion ?? value?.installedVersion ?? null;
153
+ if (!isPlainObject(value) || value.schemaVersion !== 1 || value.skillId !== SKILL_ID
154
+ || (installedVersion !== null && value.installedVersion !== installedVersion)
155
+ || (value.installedVersion !== null && typeof value.installedVersion !== 'string')
156
+ || !isPlainObject(value.check)
157
+ || !(value.check.nextCheckAt === null || typeof value.check.nextCheckAt === 'string')
158
+ || !Number.isSafeInteger(value.check.consecutiveFailures)
159
+ || value.check.consecutiveFailures < 0
160
+ || !isPlainObject(value.notification)) return null;
161
+ if (effectiveInstalledVersion !== null) {
162
+ try {
163
+ parseSemver(effectiveInstalledVersion);
164
+ } catch {
165
+ return null;
166
+ }
167
+ }
168
+
169
+ const state = emptyState(effectiveInstalledVersion);
170
+ state.check.nextCheckAt = value.check.nextCheckAt;
171
+ state.check.consecutiveFailures = value.check.consecutiveFailures;
172
+ const { offeredDigests, acknowledgedDigests } = value.notification;
173
+ if (!Array.isArray(offeredDigests)
174
+ || offeredDigests.some((digest) => typeof digest !== 'string' || !DIGEST_PATTERN.test(digest))) {
175
+ return null;
176
+ }
177
+ state.notification.offeredDigests = [...new Set(offeredDigests)];
178
+
179
+ if (!Array.isArray(acknowledgedDigests)
180
+ || acknowledgedDigests.some((digest) => typeof digest !== 'string' || !DIGEST_PATTERN.test(digest))) {
181
+ return null;
182
+ }
183
+ state.notification.acknowledgedDigests = [...new Set(acknowledgedDigests)];
184
+ const hasCandidate = Object.hasOwn(value, 'candidate');
185
+ const candidate = validateCachedCandidate(value.candidate);
186
+ if (hasCandidate && !candidate) return null;
187
+ if (candidate) {
188
+ if (effectiveInstalledVersion !== null
189
+ && compareSemver(candidate.version, effectiveInstalledVersion) > 0
190
+ && !state.notification.offeredDigests.includes(candidate.targetDigest)
191
+ && !state.notification.acknowledgedDigests.includes(candidate.targetDigest)) return null;
192
+ state.candidate = candidate;
193
+ }
194
+ return encodeRecoverableState(state) ? state : null;
195
+ }
196
+
197
+ function isSameFile(left, right) {
198
+ if (left.dev !== right.dev || left.ino !== right.ino) return false;
199
+ if (left.ino !== 0n) return true;
200
+ if (left.birthtimeNs === 0n || right.birthtimeNs === 0n) return false;
201
+ return left.birthtimeNs === right.birthtimeNs
202
+ && left.mode === right.mode;
203
+ }
204
+
205
+ function assertBoundedRegularFile(metadata, maxBytes) {
206
+ if (!metadata.isFile()
207
+ || metadata.isSymbolicLink()
208
+ || metadata.size > BigInt(maxBytes)) {
209
+ throw new Error('unsafe JSON file');
210
+ }
211
+ }
212
+
213
+ async function readJsonFile(target, maxBytes, expectedMetadata = null) {
214
+ const pathMetadata = await fs.lstat(target, { bigint: true });
215
+ if (expectedMetadata && !isSameFile(expectedMetadata, pathMetadata)) {
216
+ throw new FileIdentityChangedError('JSON file identity changed');
217
+ }
218
+ assertBoundedRegularFile(pathMetadata, maxBytes);
219
+ const flags = fsConstants.O_RDONLY
220
+ | (fsConstants.O_NOFOLLOW ?? 0)
221
+ | (fsConstants.O_NONBLOCK ?? 0);
222
+ const handle = await fs.open(target, flags);
223
+ try {
224
+ const handleMetadata = await handle.stat({ bigint: true });
225
+ const currentPathMetadata = await fs.lstat(target, { bigint: true });
226
+ assertBoundedRegularFile(handleMetadata, maxBytes);
227
+ assertBoundedRegularFile(currentPathMetadata, maxBytes);
228
+ if (!isSameFile(pathMetadata, handleMetadata)
229
+ || !isSameFile(currentPathMetadata, handleMetadata)) {
230
+ throw new FileIdentityChangedError('JSON file changed while opening');
231
+ }
232
+
233
+ const chunks = [];
234
+ let size = 0;
235
+ while (size <= maxBytes) {
236
+ const chunk = Buffer.allocUnsafe(Math.min(8 * 1_024, (maxBytes + 1) - size));
237
+ const { bytesRead } = await handle.read(chunk, 0, chunk.length, size);
238
+ if (bytesRead === 0) break;
239
+ chunks.push(chunk.subarray(0, bytesRead));
240
+ size += bytesRead;
241
+ }
242
+ if (size > maxBytes) throw new Error('JSON file is too large');
243
+ return JSON.parse(Buffer.concat(chunks, size).toString('utf8'));
244
+ } finally {
245
+ await handle.close();
246
+ }
247
+ }
248
+
249
+ function isWithinDirectory(directory, target) {
250
+ const relative = path.relative(directory, target);
251
+ return relative === ''
252
+ || (!path.isAbsolute(relative) && relative !== '..' && !relative.startsWith(`..${path.sep}`));
253
+ }
254
+
255
+ async function canonicalizeTrustedDirectoryPrefix(target) {
256
+ const absoluteTarget = path.resolve(target);
257
+ const trustedDirectories = [...new Set([os.homedir(), os.tmpdir()].map((directory) => (
258
+ path.resolve(directory)
259
+ )))].sort((left, right) => right.length - left.length);
260
+ for (const trustedDirectory of trustedDirectories) {
261
+ if (!isWithinDirectory(trustedDirectory, absoluteTarget)) continue;
262
+ try {
263
+ const canonicalDirectory = await fs.realpath(trustedDirectory);
264
+ return path.resolve(canonicalDirectory, path.relative(trustedDirectory, absoluteTarget));
265
+ } catch {
266
+ // Fall back to validating the absolute path from its filesystem root.
267
+ }
268
+ }
269
+ return target;
270
+ }
271
+
272
+ function assertSafeDirectory(metadata) {
273
+ if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
274
+ throw new Error('unsafe cache directory');
275
+ }
276
+ }
277
+
278
+ class CachePathChangedError extends Error {}
279
+ class CacheOperationRaceError extends Error {}
280
+ class OperationFencedError extends Error {}
281
+
282
+ const preparedCacheDirectories = new Map();
283
+
284
+ function invalidateCacheToken(token, message, cause) {
285
+ token.trusted = false;
286
+ if (cause instanceof CachePathChangedError) return cause;
287
+ const error = new CachePathChangedError(message);
288
+ if (cause) error.cause = cause;
289
+ return error;
290
+ }
291
+
292
+ async function verifyDirectorySnapshots(token, snapshots, description) {
293
+ if (!token.trusted) throw new CachePathChangedError('cache directory is no longer trusted');
294
+ try {
295
+ for (const snapshot of snapshots) {
296
+ const metadata = await fs.lstat(snapshot.directory, { bigint: true });
297
+ assertSafeDirectory(metadata);
298
+ if (!isSameFile(snapshot.metadata, metadata)) {
299
+ throw new Error(`${description} changed`);
300
+ }
301
+ }
302
+ } catch (error) {
303
+ throw invalidateCacheToken(token, `${description} changed`, error);
304
+ }
305
+ }
306
+
307
+ async function verifyCacheToken(token) {
308
+ await verifyDirectorySnapshots(token, token.ancestorSnapshots, 'cache directory');
309
+ }
310
+
311
+ async function guardedCacheRead(token, read) {
312
+ await verifyCacheToken(token);
313
+ let result;
314
+ try {
315
+ result = await read();
316
+ } catch (error) {
317
+ await verifyCacheToken(token);
318
+ throw error;
319
+ }
320
+ await verifyCacheToken(token);
321
+ return result;
322
+ }
323
+
324
+ function cacheTokenFor(cacheDirectory) {
325
+ const token = preparedCacheDirectories.get(path.resolve(cacheDirectory));
326
+ if (!token || !token.trusted) {
327
+ throw new CachePathChangedError('cache directory has not been prepared safely');
328
+ }
329
+ return token;
330
+ }
331
+
332
+ function resolveCacheTarget(token, target) {
333
+ const resolved = path.resolve(target);
334
+ if (resolved === token.directory || !isWithinDirectory(token.directory, resolved)) {
335
+ throw invalidateCacheToken(token, 'cache mutation escaped its prepared directory');
336
+ }
337
+ return resolved;
338
+ }
339
+
340
+ async function captureMutationParentSnapshots(token, targets) {
341
+ await verifyCacheToken(token);
342
+ const snapshots = new Map();
343
+ try {
344
+ for (const target of targets) {
345
+ const parent = path.dirname(resolveCacheTarget(token, target));
346
+ const relative = path.relative(token.directory, parent);
347
+ let current = token.directory;
348
+ for (const component of relative.split(path.sep).filter(Boolean)) {
349
+ current = path.join(current, component);
350
+ if (snapshots.has(current)) continue;
351
+ const metadata = await fs.lstat(current, { bigint: true });
352
+ assertSafeDirectory(metadata);
353
+ snapshots.set(current, { directory: current, metadata });
354
+ }
355
+ }
356
+ } catch (error) {
357
+ throw invalidateCacheToken(token, 'cache mutation parent changed', error);
358
+ }
359
+ const captured = [...snapshots.values()];
360
+ await verifyMutationParentSnapshots(token, captured);
361
+ await verifyCacheToken(token);
362
+ return captured;
363
+ }
364
+
365
+ async function verifyMutationParentSnapshots(token, snapshots) {
366
+ try {
367
+ for (const snapshot of snapshots) {
368
+ const metadata = await fs.lstat(snapshot.directory, { bigint: true });
369
+ assertSafeDirectory(metadata);
370
+ if (!isSameFile(snapshot.metadata, metadata)) {
371
+ throw new Error('cache mutation parent changed');
372
+ }
373
+ }
374
+ } catch (error) {
375
+ const raced = new CacheOperationRaceError('cache mutation parent changed');
376
+ raced.cause = error;
377
+ throw raced;
378
+ }
379
+ }
380
+
381
+ async function verifyMutationContext(token, parentSnapshots) {
382
+ await verifyCacheToken(token);
383
+ await verifyMutationParentSnapshots(token, parentSnapshots);
384
+ await verifyCacheToken(token);
385
+ }
386
+
387
+ function assertSafeCacheEntry(metadata) {
388
+ if (metadata.isSymbolicLink() || (!metadata.isDirectory() && !metadata.isFile())) {
389
+ throw new Error('unsafe cache entry');
390
+ }
391
+ }
392
+
393
+ function assertSafeRegularFile(metadata) {
394
+ if (!metadata.isFile() || metadata.isSymbolicLink()) {
395
+ throw new Error('unsafe cache file');
396
+ }
397
+ }
398
+
399
+ function assertRenameableCacheEntry(metadata) {
400
+ if (!metadata.isDirectory() && !metadata.isFile() && !metadata.isSymbolicLink()) {
401
+ throw new Error('unsafe cache rename entry');
402
+ }
403
+ }
404
+
405
+ async function stableCacheEntry(
406
+ token,
407
+ target,
408
+ validate,
409
+ expectedMetadata = null,
410
+ missingIsOperationRace = false,
411
+ ) {
412
+ const resolved = resolveCacheTarget(token, target);
413
+ try {
414
+ const first = await fs.lstat(resolved, { bigint: true });
415
+ validate(first);
416
+ if (expectedMetadata && !isSameFile(expectedMetadata, first)) {
417
+ throw new Error('cache entry identity changed');
418
+ }
419
+ await verifyCacheToken(token);
420
+ const second = await fs.lstat(resolved, { bigint: true });
421
+ validate(second);
422
+ if (!isSameFile(first, second)
423
+ || (expectedMetadata && !isSameFile(expectedMetadata, second))) {
424
+ throw new Error('cache entry identity changed');
425
+ }
426
+ return second;
427
+ } catch (error) {
428
+ if (missingIsOperationRace && error?.code === 'ENOENT') {
429
+ await verifyCacheToken(token);
430
+ throw error;
431
+ }
432
+ throw invalidateCacheToken(token, 'cache mutation produced an untrusted object', error);
433
+ }
434
+ }
435
+
436
+ async function guardedCacheEntryRead(
437
+ token,
438
+ target,
439
+ validate,
440
+ read,
441
+ expectedMetadata = null,
442
+ ) {
443
+ const metadata = await stableCacheEntry(
444
+ token,
445
+ target,
446
+ validate,
447
+ expectedMetadata,
448
+ true,
449
+ );
450
+ let result;
451
+ try {
452
+ result = await read(metadata);
453
+ } catch (error) {
454
+ await stableCacheEntry(token, target, validate, metadata, true);
455
+ throw error;
456
+ }
457
+ await stableCacheEntry(token, target, validate, metadata, true);
458
+ return result;
459
+ }
460
+
461
+ async function stablePreparedDirectory(token, target) {
462
+ try {
463
+ const first = await fs.lstat(target, { bigint: true });
464
+ assertSafeDirectory(first);
465
+ await verifyCacheToken(token);
466
+ const second = await fs.lstat(target, { bigint: true });
467
+ assertSafeDirectory(second);
468
+ if (!isSameFile(first, second)) throw new Error('prepared directory identity changed');
469
+ return second;
470
+ } catch (error) {
471
+ throw invalidateCacheToken(token, 'prepared directory could not be verified', error);
472
+ }
473
+ }
474
+
475
+ async function assertCacheTargetAbsent(token, target) {
476
+ const resolved = resolveCacheTarget(token, target);
477
+ try {
478
+ await fs.lstat(resolved, { bigint: true });
479
+ } catch (error) {
480
+ if (error?.code === 'ENOENT') return;
481
+ throw invalidateCacheToken(token, 'cache mutation target could not be verified', error);
482
+ }
483
+ throw invalidateCacheToken(token, 'cache mutation left an unexpected object');
484
+ }
485
+
486
+ async function guardedCacheMutation(token, parentSnapshots, {
487
+ mutate,
488
+ verifyBefore = null,
489
+ verifyAfter,
490
+ }) {
491
+ await verifyMutationContext(token, parentSnapshots);
492
+ if (verifyBefore) await verifyBefore();
493
+ let result;
494
+ try {
495
+ result = await mutate();
496
+ } catch (error) {
497
+ await verifyMutationContext(token, parentSnapshots);
498
+ throw error;
499
+ }
500
+ try {
501
+ await verifyMutationContext(token, parentSnapshots);
502
+ const verified = await verifyAfter(result);
503
+ await verifyMutationContext(token, parentSnapshots);
504
+ return verified ?? result;
505
+ } catch (error) {
506
+ if (error instanceof CacheOperationRaceError) throw error;
507
+ throw invalidateCacheToken(token, 'cache mutation could not be verified', error);
508
+ }
509
+ }
510
+
511
+ async function cacheMkdirWithToken(token, target, options, parentSnapshots = null) {
512
+ const resolved = path.resolve(target);
513
+ const parents = parentSnapshots ?? await captureMutationParentSnapshots(token, [resolved]);
514
+ return guardedCacheMutation(token, parents, {
515
+ mutate: () => fs.mkdir(resolved, options),
516
+ verifyAfter: () => (parentSnapshots === null
517
+ ? stableCacheEntry(token, resolved, assertSafeDirectory)
518
+ : stablePreparedDirectory(token, resolved)),
519
+ });
520
+ }
521
+
522
+ async function cacheMkdir(cacheDirectory, target, options) {
523
+ return cacheMkdirWithToken(cacheTokenFor(cacheDirectory), target, options);
524
+ }
525
+
526
+ async function cacheWriteFile(cacheDirectory, target, data, options) {
527
+ if (options?.flag !== 'wx') {
528
+ throw new Error('cache files must be created exclusively');
529
+ }
530
+ const token = cacheTokenFor(cacheDirectory);
531
+ const resolved = resolveCacheTarget(token, target);
532
+ const parents = await captureMutationParentSnapshots(token, [resolved]);
533
+ return guardedCacheMutation(token, parents, {
534
+ mutate: async () => {
535
+ const flags = fsConstants.O_WRONLY
536
+ | fsConstants.O_CREAT
537
+ | fsConstants.O_EXCL
538
+ | (fsConstants.O_NOFOLLOW ?? 0);
539
+ const handle = await fs.open(resolved, flags, options.mode);
540
+ try {
541
+ const openedMetadata = await handle.stat({ bigint: true });
542
+ if (!openedMetadata.isFile() || openedMetadata.isSymbolicLink()) {
543
+ throw new Error('cache write did not open a regular file');
544
+ }
545
+ await handle.writeFile(data, options.encoding ? { encoding: options.encoding } : undefined);
546
+ const writtenMetadata = await handle.stat({ bigint: true });
547
+ const expectedBytes = Buffer.isBuffer(data)
548
+ ? data.length
549
+ : Buffer.byteLength(data, options.encoding || 'utf8');
550
+ if (!isSameFile(openedMetadata, writtenMetadata)
551
+ || writtenMetadata.size !== BigInt(expectedBytes)) {
552
+ throw new Error('cache write did not preserve the opened file identity and size');
553
+ }
554
+ return writtenMetadata;
555
+ } finally {
556
+ await handle.close();
557
+ }
558
+ },
559
+ verifyAfter: (writtenMetadata) => stableCacheEntry(token, resolved, (metadata) => {
560
+ if (!metadata.isFile() || metadata.isSymbolicLink()) {
561
+ throw new Error('cache write did not create a regular file');
562
+ }
563
+ }, writtenMetadata),
564
+ });
565
+ }
566
+
567
+ async function cacheRename(
568
+ cacheDirectory,
569
+ source,
570
+ destination,
571
+ validate,
572
+ expectedSourceMetadata = null,
573
+ ) {
574
+ if (typeof validate !== 'function') {
575
+ throw new TypeError('cache rename requires an explicit entry validator');
576
+ }
577
+ const token = cacheTokenFor(cacheDirectory);
578
+ const resolvedSource = resolveCacheTarget(token, source);
579
+ const resolvedDestination = resolveCacheTarget(token, destination);
580
+ const parents = await captureMutationParentSnapshots(
581
+ token,
582
+ [resolvedSource, resolvedDestination],
583
+ );
584
+ const sourceMetadata = await stableCacheEntry(
585
+ token,
586
+ resolvedSource,
587
+ validate,
588
+ expectedSourceMetadata,
589
+ true,
590
+ );
591
+ return guardedCacheMutation(token, parents, {
592
+ verifyBefore: () => stableCacheEntry(
593
+ token,
594
+ resolvedSource,
595
+ validate,
596
+ sourceMetadata,
597
+ true,
598
+ ),
599
+ mutate: () => fs.rename(resolvedSource, resolvedDestination),
600
+ verifyAfter: async () => {
601
+ await assertCacheTargetAbsent(token, resolvedSource);
602
+ return stableCacheEntry(
603
+ token,
604
+ resolvedDestination,
605
+ validate,
606
+ sourceMetadata,
607
+ );
608
+ },
609
+ });
610
+ }
611
+
612
+ async function cacheRm(cacheDirectory, target, options) {
613
+ if (options?.recursive) {
614
+ throw new Error('recursive cache removal is not allowed');
615
+ }
616
+ const token = cacheTokenFor(cacheDirectory);
617
+ const resolved = resolveCacheTarget(token, target);
618
+ const parents = await captureMutationParentSnapshots(token, [resolved]);
619
+ let initialMetadata;
620
+ try {
621
+ initialMetadata = await fs.lstat(resolved, { bigint: true });
622
+ } catch (error) {
623
+ if (error?.code === 'ENOENT' && options?.force) {
624
+ await verifyMutationContext(token, parents);
625
+ return;
626
+ }
627
+ throw error;
628
+ }
629
+ try {
630
+ assertSafeCacheEntry(initialMetadata);
631
+ } catch (error) {
632
+ throw invalidateCacheToken(token, 'cache removal target is unsafe', error);
633
+ }
634
+ const targetMetadata = await stableCacheEntry(
635
+ token,
636
+ resolved,
637
+ assertSafeCacheEntry,
638
+ initialMetadata,
639
+ );
640
+ return guardedCacheMutation(token, parents, {
641
+ verifyBefore: () => stableCacheEntry(
642
+ token,
643
+ resolved,
644
+ assertSafeCacheEntry,
645
+ targetMetadata,
646
+ ),
647
+ mutate: () => fs.rm(resolved, options),
648
+ verifyAfter: () => assertCacheTargetAbsent(token, resolved),
649
+ });
650
+ }
651
+
652
+ async function prepareCacheDirectory(cacheDirectory) {
653
+ const absoluteTarget = path.resolve(cacheDirectory);
654
+ const target = await canonicalizeTrustedDirectoryPrefix(absoluteTarget);
655
+ const parsed = path.parse(target);
656
+ const components = target.slice(parsed.root.length).split(path.sep).filter(Boolean);
657
+ let current = parsed.root;
658
+ const token = {
659
+ directory: target,
660
+ ancestorSnapshots: [],
661
+ trusted: true,
662
+ };
663
+
664
+ for (const component of components) {
665
+ current = path.join(current, component);
666
+ let metadata;
667
+ try {
668
+ metadata = await fs.lstat(current, { bigint: true });
669
+ } catch (error) {
670
+ if (error?.code !== 'ENOENT') throw error;
671
+ try {
672
+ await cacheMkdirWithToken(token, current, { mode: 0o700 }, []);
673
+ } catch (mkdirError) {
674
+ if (mkdirError?.code !== 'EEXIST') throw mkdirError;
675
+ }
676
+ metadata = await fs.lstat(current, { bigint: true });
677
+ }
678
+ assertSafeDirectory(metadata);
679
+ token.ancestorSnapshots.push({ directory: current, metadata });
680
+ }
681
+
682
+ await verifyCacheToken(token);
683
+ preparedCacheDirectories.set(target, token);
684
+ return target;
685
+ }
686
+
687
+ function operationName(kind, generation) {
688
+ return `${kind}-${generation.toString().padStart(20, '0')}`;
689
+ }
690
+
691
+ function parseOperationName(name) {
692
+ const match = OPERATION_NAME.exec(name);
693
+ if (!match) return null;
694
+ return { kind: match[1], generation: BigInt(match[2]) };
695
+ }
696
+
697
+ async function listOperations(cacheDirectory, token = cacheTokenFor(cacheDirectory)) {
698
+ return guardedCacheRead(token, async () => {
699
+ try {
700
+ const entries = await fs.readdir(cacheDirectory, { withFileTypes: true });
701
+ return entries.flatMap((entry) => {
702
+ const parsed = parseOperationName(entry.name);
703
+ if (!parsed) return [];
704
+ if (!entry.isDirectory() || entry.isSymbolicLink()) {
705
+ throw invalidateCacheToken(
706
+ token,
707
+ `cache operation ${entry.name} is not a safe directory`,
708
+ );
709
+ }
710
+ return [{ ...parsed, name: entry.name, directory: path.join(cacheDirectory, entry.name) }];
711
+ });
712
+ } catch (error) {
713
+ if (error?.code === 'ENOENT') return [];
714
+ throw error;
715
+ }
716
+ });
717
+ }
718
+
719
+ async function readState(cacheDirectory, installedVersion = null) {
720
+ const token = cacheTokenFor(cacheDirectory);
721
+ return guardedCacheRead(token, async () => {
722
+ const operations = (await listOperations(cacheDirectory, token))
723
+ .filter((operation) => operation.kind === 'committed')
724
+ .sort((left, right) => (left.generation > right.generation ? -1 : 1));
725
+ for (const operation of operations) {
726
+ try {
727
+ const state = await guardedCacheEntryRead(
728
+ token,
729
+ operation.directory,
730
+ assertSafeDirectory,
731
+ async () => normalizeState(
732
+ await readJsonFile(
733
+ path.join(operation.directory, OPERATION_STATE_FILE),
734
+ MAX_CACHE_STATE_BYTES,
735
+ ),
736
+ installedVersion,
737
+ ),
738
+ );
739
+ if (state) return state;
740
+ } catch (error) {
741
+ if (error instanceof FileIdentityChangedError
742
+ || error instanceof CachePathChangedError
743
+ || error instanceof CacheOperationRaceError) throw error;
744
+ // An incomplete or corrupt generation is ignored in favor of the previous commit.
745
+ }
746
+ }
747
+ return emptyState(installedVersion);
748
+ });
749
+ }
750
+
751
+ async function operationIsActive(
752
+ cacheDirectory,
753
+ operation,
754
+ token = cacheTokenFor(cacheDirectory),
755
+ ) {
756
+ try {
757
+ return await guardedCacheEntryRead(
758
+ token,
759
+ operation.directory,
760
+ assertSafeDirectory,
761
+ async (directoryMetadata) => {
762
+ try {
763
+ const ownerMetadata = await fs.lstat(
764
+ path.join(operation.directory, OPERATION_OWNER_FILE),
765
+ { bigint: true },
766
+ );
767
+ if (!ownerMetadata.isFile() || ownerMetadata.isSymbolicLink()) return false;
768
+ return metadataIsWithinLease(ownerMetadata);
769
+ } catch (error) {
770
+ if (error?.code !== 'ENOENT') throw error;
771
+ return metadataIsWithinLease(directoryMetadata);
772
+ }
773
+ },
774
+ );
775
+ } catch (error) {
776
+ if (error?.code === 'ENOENT') return false;
777
+ throw error;
778
+ }
779
+ }
780
+
781
+ function validateClaimOwner(value) {
782
+ if (!isPlainObject(value)
783
+ || !hasOnlyKeys(value, new Set(['pid', 'token', 'generation']))
784
+ || !Number.isSafeInteger(value.pid)
785
+ || value.pid <= 0
786
+ || typeof value.token !== 'string'
787
+ || !/^[a-f0-9]{32}$/.test(value.token)
788
+ || typeof value.generation !== 'string'
789
+ || !/^[1-9]\d*$/.test(value.generation)) return null;
790
+ return { ...value, generation: BigInt(value.generation) };
791
+ }
792
+
793
+ function metadataIsWithinLease(metadata) {
794
+ if (typeof metadata.mtimeNs === 'bigint') {
795
+ const ageNs = (BigInt(Date.now()) * 1_000_000n) - metadata.mtimeNs;
796
+ const absoluteAgeNs = ageNs < 0n ? -ageNs : ageNs;
797
+ return absoluteAgeNs <= BigInt(LOCK_STALE_MS) * 1_000_000n;
798
+ }
799
+ return Math.abs(Date.now() - metadata.mtimeMs) <= LOCK_STALE_MS;
800
+ }
801
+
802
+ function corruptClaimStatus(directory, directoryMetadata, ownerMetadata = directoryMetadata) {
803
+ if (metadataIsWithinLease(ownerMetadata)) {
804
+ return { state: 'busy', directory, metadata: directoryMetadata, owner: null };
805
+ }
806
+ const fingerprint = crypto.createHash('sha256')
807
+ .update(JSON.stringify({
808
+ directory: {
809
+ dev: directoryMetadata.dev.toString(),
810
+ ino: directoryMetadata.ino.toString(),
811
+ mode: directoryMetadata.mode.toString(),
812
+ birthtimeNs: directoryMetadata.birthtimeNs.toString(),
813
+ },
814
+ }))
815
+ .digest('hex')
816
+ .slice(0, 24);
817
+ return {
818
+ state: 'releasable',
819
+ directory,
820
+ metadata: directoryMetadata,
821
+ key: `corrupt-${fingerprint}`,
822
+ owner: null,
823
+ };
824
+ }
825
+
826
+ async function inspectActiveClaim(cacheDirectory) {
827
+ const token = cacheTokenFor(cacheDirectory);
828
+ return guardedCacheRead(token, async () => {
829
+ const directory = path.join(cacheDirectory, ACTIVE_CLAIM_DIRECTORY);
830
+ let directoryMetadata;
831
+ try {
832
+ directoryMetadata = await fs.lstat(directory, { bigint: true });
833
+ } catch (error) {
834
+ if (error?.code === 'ENOENT') return { state: 'absent', directory };
835
+ throw error;
836
+ }
837
+ if (!directoryMetadata.isDirectory() || directoryMetadata.isSymbolicLink()) {
838
+ return corruptClaimStatus(directory, directoryMetadata);
839
+ }
840
+ return guardedCacheEntryRead(
841
+ token,
842
+ directory,
843
+ assertSafeDirectory,
844
+ async (stableDirectoryMetadata) => {
845
+ const ownerPath = path.join(directory, OPERATION_OWNER_FILE);
846
+ let ownerMetadata = stableDirectoryMetadata;
847
+ let owner = null;
848
+ try {
849
+ ownerMetadata = await fs.lstat(ownerPath, { bigint: true });
850
+ if (!ownerMetadata.isFile()
851
+ || ownerMetadata.isSymbolicLink()
852
+ || ownerMetadata.size > BigInt(MAX_CLAIM_OWNER_BYTES)) {
853
+ return corruptClaimStatus(directory, stableDirectoryMetadata, ownerMetadata);
854
+ }
855
+ const ownerSource = await readJsonFile(
856
+ ownerPath,
857
+ MAX_CLAIM_OWNER_BYTES,
858
+ ownerMetadata,
859
+ );
860
+ owner = validateClaimOwner(ownerSource);
861
+ } catch (error) {
862
+ if (error instanceof FileIdentityChangedError) throw error;
863
+ // A malformed claim is recoverable only after the same hard lease as a crashed owner.
864
+ }
865
+
866
+ if (owner) {
867
+ const operations = await listOperations(cacheDirectory, token);
868
+ if (operations.some((operation) => operation.generation === owner.generation
869
+ && ['committed', 'fenced', 'cancelled'].includes(operation.kind))) {
870
+ return {
871
+ state: 'releasable',
872
+ directory,
873
+ metadata: stableDirectoryMetadata,
874
+ key: owner.generation.toString(),
875
+ owner,
876
+ };
877
+ }
878
+ const pending = operations.find((operation) => operation.kind === 'pending'
879
+ && operation.generation === owner.generation);
880
+ if (pending) {
881
+ if (await operationIsActive(cacheDirectory, pending, token)) {
882
+ return {
883
+ state: 'busy',
884
+ directory,
885
+ metadata: stableDirectoryMetadata,
886
+ owner,
887
+ };
888
+ }
889
+ return {
890
+ state: 'releasable',
891
+ directory,
892
+ metadata: stableDirectoryMetadata,
893
+ key: owner.generation.toString(),
894
+ owner,
895
+ };
896
+ }
897
+ }
898
+
899
+ return corruptClaimStatus(directory, stableDirectoryMetadata, ownerMetadata);
900
+ },
901
+ directoryMetadata,
902
+ );
903
+ });
904
+ }
905
+
906
+ async function retireActiveClaim(cacheDirectory, claim) {
907
+ const retirement = path.join(cacheDirectory, `retired-claim-${claim.key}`);
908
+ await cacheMkdir(cacheDirectory, retirement, { mode: 0o700 }).catch((error) => {
909
+ if (error?.code !== 'EEXIST') throw error;
910
+ });
911
+ try {
912
+ const retirementMetadata = await fs.lstat(retirement);
913
+ if (!retirementMetadata.isDirectory() || retirementMetadata.isSymbolicLink()) return false;
914
+ } catch (error) {
915
+ if (error instanceof CacheOperationRaceError) return false;
916
+ if (error?.code === 'ENOENT') return false;
917
+ throw error;
918
+ }
919
+ try {
920
+ const metadata = await fs.lstat(claim.directory);
921
+ if (metadata.isDirectory() && !metadata.isSymbolicLink()) {
922
+ await cacheWriteFile(
923
+ cacheDirectory,
924
+ path.join(claim.directory, '.retirement-guard'),
925
+ `${claim.key}\n`,
926
+ { encoding: 'utf8', flag: 'wx', mode: 0o600 },
927
+ ).catch((error) => {
928
+ if (error?.code !== 'EEXIST') throw error;
929
+ });
930
+ }
931
+ } catch (error) {
932
+ if (error instanceof CacheOperationRaceError) return false;
933
+ if (error?.code === 'ENOENT') return false;
934
+ throw error;
935
+ }
936
+ try {
937
+ await cacheRename(
938
+ cacheDirectory,
939
+ claim.directory,
940
+ path.join(retirement, 'active'),
941
+ assertRenameableCacheEntry,
942
+ claim.metadata,
943
+ );
944
+ return true;
945
+ } catch (error) {
946
+ if (error instanceof CacheOperationRaceError) return false;
947
+ if (['ENOENT', 'EEXIST', 'ENOTEMPTY', 'EPERM'].includes(error?.code)) return false;
948
+ throw error;
949
+ }
950
+ }
951
+
952
+ async function prepareOperationClaim(cacheDirectory, operation) {
953
+ const directory = path.join(cacheDirectory, operationName('claim', operation.generation));
954
+ await cacheMkdir(cacheDirectory, directory, { mode: 0o700 });
955
+ await cacheWriteFile(
956
+ cacheDirectory,
957
+ path.join(directory, OPERATION_OWNER_FILE),
958
+ `${JSON.stringify({
959
+ pid: process.pid,
960
+ token: operation.token,
961
+ generation: operation.generation.toString(),
962
+ })}\n`,
963
+ { encoding: 'utf8', flag: 'wx', mode: 0o600 },
964
+ );
965
+ operation.preparedClaimDirectory = directory;
966
+ }
967
+
968
+ async function discardPreparedClaim(cacheDirectory, operation) {
969
+ if (!operation.preparedClaimDirectory) return;
970
+ const discarded = path.join(
971
+ cacheDirectory,
972
+ `discarded-claim-${operation.generation.toString().padStart(20, '0')}-${operation.token}`,
973
+ );
974
+ try {
975
+ await cacheRename(
976
+ cacheDirectory,
977
+ operation.preparedClaimDirectory,
978
+ discarded,
979
+ assertSafeDirectory,
980
+ );
981
+ } catch (error) {
982
+ if (error?.code !== 'ENOENT') throw error;
983
+ }
984
+ delete operation.preparedClaimDirectory;
985
+ }
986
+
987
+ async function promoteOperationClaim(cacheDirectory, operation) {
988
+ await prepareOperationClaim(cacheDirectory, operation);
989
+ const activeDirectory = path.join(cacheDirectory, ACTIVE_CLAIM_DIRECTORY);
990
+ for (let attempt = 0; attempt < 8; attempt += 1) {
991
+ if (await operationIsSuperseded(cacheDirectory, operation)) {
992
+ await discardPreparedClaim(cacheDirectory, operation);
993
+ return false;
994
+ }
995
+ const active = await inspectActiveClaim(cacheDirectory);
996
+ if (active.state === 'busy') {
997
+ await discardPreparedClaim(cacheDirectory, operation);
998
+ return false;
999
+ }
1000
+ if (active.state === 'releasable') {
1001
+ if (active.owner?.generation > operation.generation) {
1002
+ await discardPreparedClaim(cacheDirectory, operation);
1003
+ return false;
1004
+ }
1005
+ await retireActiveClaim(cacheDirectory, active);
1006
+ continue;
1007
+ }
1008
+ try {
1009
+ await cacheRename(
1010
+ cacheDirectory,
1011
+ operation.preparedClaimDirectory,
1012
+ activeDirectory,
1013
+ assertSafeDirectory,
1014
+ );
1015
+ delete operation.preparedClaimDirectory;
1016
+ if (!await operationIsActive(cacheDirectory, operation)) return false;
1017
+ return true;
1018
+ } catch (promotionError) {
1019
+ const raced = await inspectActiveClaim(cacheDirectory);
1020
+ if (raced.state === 'absent') {
1021
+ if (['EEXIST', 'ENOTEMPTY', 'EPERM'].includes(promotionError?.code)) continue;
1022
+ await discardPreparedClaim(cacheDirectory, operation);
1023
+ throw promotionError;
1024
+ }
1025
+ continue;
1026
+ }
1027
+ }
1028
+ await discardPreparedClaim(cacheDirectory, operation);
1029
+ return false;
1030
+ }
1031
+
1032
+ async function operationOwnsActiveClaim(cacheDirectory, operation) {
1033
+ const active = await inspectActiveClaim(cacheDirectory);
1034
+ return active.state === 'busy'
1035
+ && active.owner?.generation === operation.generation
1036
+ && active.owner.token === operation.token;
1037
+ }
1038
+
1039
+ async function hasActivePendingOperation(cacheDirectory) {
1040
+ const token = cacheTokenFor(cacheDirectory);
1041
+ return guardedCacheRead(token, async () => {
1042
+ const pending = (await listOperations(cacheDirectory, token))
1043
+ .filter((operation) => operation.kind === 'pending');
1044
+ return (await Promise.all(pending.map(
1045
+ (operation) => operationIsActive(cacheDirectory, operation, token),
1046
+ ))).some(Boolean);
1047
+ });
1048
+ }
1049
+
1050
+ async function reserveOperation(cacheDirectory) {
1051
+ cacheDirectory = await prepareCacheDirectory(cacheDirectory);
1052
+ const operations = await listOperations(cacheDirectory);
1053
+ const occupied = new Set(operations.map((operation) => operation.generation.toString()));
1054
+ let generation = 1n;
1055
+ while (true) {
1056
+ while (occupied.has(generation.toString())) generation += 1n;
1057
+ if (generation > MAX_OPERATION_GENERATION) throw new Error('operation generation space exhausted');
1058
+ const reservation = path.join(cacheDirectory, operationName('reserved', generation));
1059
+ try {
1060
+ await cacheMkdir(cacheDirectory, reservation, { mode: 0o700 });
1061
+ } catch (error) {
1062
+ if (error?.code !== 'EEXIST') throw error;
1063
+ occupied.add(generation.toString());
1064
+ generation += 1n;
1065
+ continue;
1066
+ }
1067
+ const directory = path.join(cacheDirectory, operationName('pending', generation));
1068
+ try {
1069
+ await cacheMkdir(cacheDirectory, directory, { mode: 0o700 });
1070
+ const operation = {
1071
+ generation,
1072
+ token: crypto.randomBytes(16).toString('hex'),
1073
+ directory,
1074
+ };
1075
+ try {
1076
+ await cacheWriteFile(
1077
+ cacheDirectory,
1078
+ path.join(directory, OPERATION_OWNER_FILE),
1079
+ `${JSON.stringify({ pid: process.pid, token: operation.token })}\n`,
1080
+ { encoding: 'utf8', flag: 'wx', mode: 0o600 },
1081
+ );
1082
+ } catch (error) {
1083
+ if (await operationWasFenced(cacheDirectory, operation)) {
1084
+ const fencedError = new OperationFencedError('operation was fenced while reserving');
1085
+ fencedError.cause = error;
1086
+ throw fencedError;
1087
+ }
1088
+ await cacheRename(
1089
+ cacheDirectory,
1090
+ directory,
1091
+ path.join(cacheDirectory, operationName('cancelled', generation)),
1092
+ assertSafeDirectory,
1093
+ ).catch(() => {});
1094
+ throw error;
1095
+ }
1096
+ return operation;
1097
+ } catch (error) {
1098
+ if (error?.code !== 'EEXIST') throw error;
1099
+ occupied.add(generation.toString());
1100
+ generation += 1n;
1101
+ }
1102
+ }
1103
+ }
1104
+
1105
+ async function operationIsSuperseded(cacheDirectory, operation) {
1106
+ return (await listOperations(cacheDirectory))
1107
+ .some((candidate) => candidate.generation > operation.generation);
1108
+ }
1109
+
1110
+ async function operationWasFenced(cacheDirectory, operation) {
1111
+ const token = cacheTokenFor(cacheDirectory);
1112
+ return guardedCacheRead(token, async () => {
1113
+ try {
1114
+ await fs.lstat(operation.directory, { bigint: true });
1115
+ return false;
1116
+ } catch (error) {
1117
+ if (error?.code !== 'ENOENT') throw error;
1118
+ }
1119
+ const fencedDirectory = path.join(
1120
+ cacheDirectory,
1121
+ operationName('fenced', operation.generation),
1122
+ );
1123
+ try {
1124
+ await stableCacheEntry(
1125
+ token,
1126
+ fencedDirectory,
1127
+ assertSafeDirectory,
1128
+ null,
1129
+ true,
1130
+ );
1131
+ return true;
1132
+ } catch (error) {
1133
+ if (error?.code === 'ENOENT') return false;
1134
+ throw error;
1135
+ }
1136
+ });
1137
+ }
1138
+
1139
+ async function transitionOperation(cacheDirectory, operation, kind) {
1140
+ const destination = path.join(cacheDirectory, operationName(kind, operation.generation));
1141
+ await cacheRename(cacheDirectory, operation.directory, destination, assertSafeDirectory);
1142
+ }
1143
+
1144
+ async function cancelOperation(cacheDirectory, operation) {
1145
+ try {
1146
+ await transitionOperation(cacheDirectory, operation, 'cancelled');
1147
+ } catch (error) {
1148
+ // A higher generation may already have fenced this unique pending directory.
1149
+ if (error?.code !== 'ENOENT') throw error;
1150
+ }
1151
+ }
1152
+
1153
+ async function acquireOperation(cacheDirectory) {
1154
+ cacheDirectory = await prepareCacheDirectory(cacheDirectory);
1155
+ if ((await inspectActiveClaim(cacheDirectory)).state === 'busy') return null;
1156
+ if (await hasActivePendingOperation(cacheDirectory)) return null;
1157
+ let operation;
1158
+ try {
1159
+ operation = await reserveOperation(cacheDirectory);
1160
+ } catch (error) {
1161
+ if (error instanceof OperationFencedError) return null;
1162
+ throw error;
1163
+ }
1164
+ if (!await operationIsSuperseded(cacheDirectory, operation)
1165
+ && await promoteOperationClaim(cacheDirectory, operation)) return operation;
1166
+ await cancelOperation(cacheDirectory, operation);
1167
+ return null;
1168
+ }
1169
+
1170
+ async function fenceLowerOperations(cacheDirectory, operation) {
1171
+ const lower = (await listOperations(cacheDirectory))
1172
+ .filter((candidate) => candidate.kind === 'pending'
1173
+ && candidate.generation < operation.generation)
1174
+ .sort((left, right) => (left.generation < right.generation ? -1 : 1));
1175
+ for (const candidate of lower) {
1176
+ try {
1177
+ await cacheRename(
1178
+ cacheDirectory,
1179
+ candidate.directory,
1180
+ path.join(cacheDirectory, operationName('fenced', candidate.generation)),
1181
+ assertSafeDirectory,
1182
+ );
1183
+ } catch (error) {
1184
+ if (error?.code !== 'ENOENT') throw error;
1185
+ }
1186
+ }
1187
+ }
1188
+
1189
+ async function commitOperation(cacheDirectory, operation, installedVersion, mutate) {
1190
+ if (!await operationOwnsActiveClaim(cacheDirectory, operation)) {
1191
+ await cancelOperation(cacheDirectory, operation);
1192
+ return { fenced: true, result: silent('check-in-progress') };
1193
+ }
1194
+ await fenceLowerOperations(cacheDirectory, operation);
1195
+ if (!await operationOwnsActiveClaim(cacheDirectory, operation)) {
1196
+ await cancelOperation(cacheDirectory, operation);
1197
+ return { fenced: true, result: silent('check-in-progress') };
1198
+ }
1199
+ const state = await readState(cacheDirectory, installedVersion);
1200
+ const decision = mutate(state);
1201
+ if (!decision.state) {
1202
+ await cancelOperation(cacheDirectory, operation);
1203
+ return { fenced: false, result: decision.result };
1204
+ }
1205
+ let committedState = decision.state;
1206
+ let committedResult = decision.result;
1207
+ let encodedState = encodeRecoverableState(committedState);
1208
+ if (!encodedState && decision.capacityFallback) {
1209
+ committedState = decision.capacityFallback.state;
1210
+ committedResult = decision.capacityFallback.result;
1211
+ encodedState = encodeRecoverableState(committedState);
1212
+ }
1213
+ if (!encodedState) {
1214
+ await cancelOperation(cacheDirectory, operation);
1215
+ return { fenced: false, result: silent('cache-unavailable') };
1216
+ }
1217
+ const temporary = path.join(
1218
+ operation.directory,
1219
+ `.state.${process.pid}.${crypto.randomBytes(8).toString('hex')}.tmp`,
1220
+ );
1221
+ try {
1222
+ await cacheWriteFile(cacheDirectory, temporary, encodedState, {
1223
+ flag: 'wx',
1224
+ mode: 0o600,
1225
+ });
1226
+ await cacheRename(
1227
+ cacheDirectory,
1228
+ temporary,
1229
+ path.join(operation.directory, OPERATION_STATE_FILE),
1230
+ assertSafeRegularFile,
1231
+ );
1232
+ await transitionOperation(cacheDirectory, operation, 'committed');
1233
+ return { fenced: false, result: committedResult };
1234
+ } catch (error) {
1235
+ if (await operationWasFenced(cacheDirectory, operation)) {
1236
+ return { fenced: true, result: silent('check-in-progress') };
1237
+ }
1238
+ await cacheRm(cacheDirectory, temporary, { force: true }).catch(() => {});
1239
+ throw error;
1240
+ }
1241
+ }
1242
+
1243
+ function nextSuccessfulCheck(nowMs, random) {
1244
+ const boundedRandom = Math.min(1, Math.max(0, Number(random()) || 0));
1245
+ const multiplier = 0.8 + (boundedRandom * 0.4);
1246
+ return new Date(nowMs + Math.round(CHECK_TTL_MS * multiplier)).toISOString();
1247
+ }
1248
+
1249
+ function nextFailedCheck(nowMs, failures) {
1250
+ const delay = failures <= 1 ? FIRST_FAILURE_DELAY_MS : LATER_FAILURE_DELAY_MS;
1251
+ return new Date(nowMs + delay).toISOString();
1252
+ }
1253
+
1254
+ function stateAfterFailedCheck(state, nowMs, withdrawCandidate = false) {
1255
+ const failures = Math.min(state.check.consecutiveFailures + 1, 2);
1256
+ return stateWithFailureCheck(
1257
+ state,
1258
+ nextFailedCheck(nowMs, failures),
1259
+ failures,
1260
+ withdrawCandidate,
1261
+ );
1262
+ }
1263
+
1264
+ function eventKeyForDigest(targetDigest) {
1265
+ return `${SKILL_ID}@${targetDigest}`;
1266
+ }
1267
+
1268
+ function digestForEventKey(eventKey) {
1269
+ const prefix = `${SKILL_ID}@`;
1270
+ if (typeof eventKey !== 'string' || !eventKey.startsWith(prefix)) return null;
1271
+ const digest = eventKey.slice(prefix.length);
1272
+ return DIGEST_PATTERN.test(digest) ? digest : null;
1273
+ }
1274
+
1275
+ function notification(localRelease, candidate) {
1276
+ return {
1277
+ status: 'update_available',
1278
+ eventKey: eventKeyForDigest(candidate.targetDigest),
1279
+ installedVersion: localRelease.version,
1280
+ latestVersion: candidate.version,
1281
+ targetDigest: candidate.targetDigest,
1282
+ severity: candidate.severity,
1283
+ summary: `Archify ${candidate.version} is available; see the official release notes for details.`,
1284
+ releaseNotes: candidate.releaseNotes,
1285
+ };
1286
+ }
1287
+
1288
+ function resultForCandidate(localRelease, state) {
1289
+ if (!state.candidate) return silent('cache-valid');
1290
+ const comparison = compareSemver(state.candidate.version, localRelease.version);
1291
+ if (comparison <= 0) return silent('current');
1292
+ if (state.notification.acknowledgedDigests.includes(state.candidate.targetDigest)) {
1293
+ return silent('already-notified');
1294
+ }
1295
+ return notification(localRelease, state.candidate);
1296
+ }
1297
+
1298
+ async function readBoundedBody(response) {
1299
+ const contentLength = Number(response.headers.get('content-length'));
1300
+ if (Number.isFinite(contentLength) && contentLength > MAX_RESPONSE_BYTES) {
1301
+ await cancelResponseBody(response);
1302
+ throw new UpdateContractError('manifest response is too large');
1303
+ }
1304
+ if (!response.body || typeof response.body.getReader !== 'function') {
1305
+ throw new UpdateContractError('manifest response has no bounded stream');
1306
+ }
1307
+ const reader = response.body.getReader();
1308
+ const chunks = [];
1309
+ let size = 0;
1310
+ try {
1311
+ while (true) {
1312
+ const { done, value } = await reader.read();
1313
+ if (done) break;
1314
+ size += value.byteLength;
1315
+ if (size > MAX_RESPONSE_BYTES) {
1316
+ await reader.cancel().catch(() => {});
1317
+ throw new UpdateContractError('manifest response is too large');
1318
+ }
1319
+ chunks.push(value);
1320
+ }
1321
+ } finally {
1322
+ reader.releaseLock();
1323
+ }
1324
+ const joined = new Uint8Array(size);
1325
+ let offset = 0;
1326
+ for (const chunk of chunks) {
1327
+ joined.set(chunk, offset);
1328
+ offset += chunk.byteLength;
1329
+ }
1330
+ try {
1331
+ return new TextDecoder('utf-8', { fatal: true }).decode(joined);
1332
+ } catch {
1333
+ throw new UpdateContractError('manifest is not valid UTF-8');
1334
+ }
1335
+ }
1336
+
1337
+ async function cancelResponseBody(response) {
1338
+ try {
1339
+ if (response.body && typeof response.body.cancel === 'function') {
1340
+ await response.body.cancel();
1341
+ }
1342
+ } catch {
1343
+ // Network cleanup is best-effort; the original response classification wins.
1344
+ }
1345
+ }
1346
+
1347
+ async function fetchCandidate({ fetchImpl, manifestUrl, timeoutMs }) {
1348
+ if (manifestUrl !== DEFAULT_MANIFEST_URL) throw new UpdateContractError('unexpected manifest URL');
1349
+ const controller = new AbortController();
1350
+ let timer;
1351
+ const timeout = new Promise((_resolve, reject) => {
1352
+ timer = setTimeout(() => {
1353
+ controller.abort();
1354
+ reject(new Error('update check timed out'));
1355
+ }, timeoutMs);
1356
+ });
1357
+ const request = (async () => {
1358
+ const headers = { accept: 'application/json' };
1359
+ const response = await fetchImpl(manifestUrl, {
1360
+ method: 'GET',
1361
+ headers,
1362
+ redirect: 'error',
1363
+ signal: controller.signal,
1364
+ });
1365
+ if (response.status !== 200) {
1366
+ await cancelResponseBody(response);
1367
+ throw new Error(`manifest returned HTTP ${response.status}`);
1368
+ }
1369
+ const mediaType = response.headers.get('content-type')?.split(';', 1)[0].trim().toLowerCase();
1370
+ if (mediaType !== 'application/json') {
1371
+ await cancelResponseBody(response);
1372
+ throw new UpdateContractError('manifest is not JSON');
1373
+ }
1374
+ const source = await readBoundedBody(response);
1375
+ let decoded;
1376
+ try {
1377
+ decoded = JSON.parse(source);
1378
+ } catch {
1379
+ throw new UpdateContractError('manifest is not valid bounded JSON');
1380
+ }
1381
+ return { candidate: validateManifest(decoded) };
1382
+ })();
1383
+ try {
1384
+ return await Promise.race([request, timeout]);
1385
+ } finally {
1386
+ clearTimeout(timer);
1387
+ }
1388
+ }
1389
+
1390
+ function defaultCacheDirectory() {
1391
+ const homeDirectory = os.homedir();
1392
+ if (process.platform === 'win32') {
1393
+ const localData = process.env.LOCALAPPDATA;
1394
+ return path.join(localData && path.isAbsolute(localData) ? localData : path.join(homeDirectory, 'AppData', 'Local'), 'archify-skill');
1395
+ }
1396
+ if (process.platform === 'darwin') return path.join(homeDirectory, 'Library', 'Caches', 'archify-skill');
1397
+ const xdgCache = process.env.XDG_CACHE_HOME;
1398
+ return path.join(xdgCache && path.isAbsolute(xdgCache) ? xdgCache : path.join(homeDirectory, '.cache'), 'archify-skill');
1399
+ }
1400
+
1401
+ function freshStateResult(localRelease, state, nowMs) {
1402
+ const nextCheckAt = Date.parse(state.check.nextCheckAt || '');
1403
+ if (Number.isFinite(nextCheckAt) && nextCheckAt > nowMs
1404
+ && nextCheckAt <= nowMs + MAX_CACHE_HORIZON_MS) {
1405
+ return resultForCandidate(localRelease, state);
1406
+ }
1407
+ return null;
1408
+ }
1409
+
1410
+ async function resultAfterLosingOperation(cacheDirectory, operation, localRelease) {
1411
+ await cancelOperation(cacheDirectory, operation);
1412
+ const cached = await readState(cacheDirectory, localRelease.version);
1413
+ if (cached.candidate
1414
+ && cached.notification.offeredDigests.includes(cached.candidate.targetDigest)) {
1415
+ return resultForCandidate(localRelease, cached);
1416
+ }
1417
+ return silent('check-in-progress');
1418
+ }
1419
+
1420
+ async function waitUntilRetry(deadline, monotonicNow) {
1421
+ let remaining;
1422
+ try {
1423
+ remaining = deadline - Number(monotonicNow());
1424
+ } catch {
1425
+ return false;
1426
+ }
1427
+ if (!Number.isFinite(remaining) || remaining <= 0) return false;
1428
+ await new Promise((resolve) => setTimeout(
1429
+ resolve,
1430
+ Math.min(LOCK_RETRY_DELAY_MS, remaining),
1431
+ ));
1432
+ return true;
1433
+ }
1434
+
1435
+ export async function checkForUpdate({
1436
+ releasePath = defaultReleasePath,
1437
+ cacheDirectory = defaultCacheDirectory(),
1438
+ fetchImpl = globalThis.fetch,
1439
+ now = Date.now,
1440
+ random = Math.random,
1441
+ timeoutMs = DEFAULT_TIMEOUT_MS,
1442
+ } = {}) {
1443
+ if (typeof fetchImpl !== 'function') return silent('runtime-unavailable');
1444
+ let localRelease;
1445
+ try {
1446
+ localRelease = validateLocalRelease(await readJsonFile(releasePath, MAX_LOCAL_RELEASE_BYTES));
1447
+ } catch {
1448
+ return silent('invalid-local-release');
1449
+ }
1450
+
1451
+ const nowMs = Number(now());
1452
+ if (!Number.isFinite(nowMs)) return silent('invalid-clock');
1453
+ let stateDirectory = versionCacheDirectory(cacheDirectory, localRelease.version);
1454
+ let state;
1455
+ try {
1456
+ stateDirectory = await prepareCacheDirectory(stateDirectory);
1457
+ state = await readState(stateDirectory, localRelease.version);
1458
+ } catch {
1459
+ return silent('cache-unavailable');
1460
+ }
1461
+ const cachedResult = freshStateResult(localRelease, state, nowMs);
1462
+ if (cachedResult) return cachedResult;
1463
+
1464
+ let operation;
1465
+ try {
1466
+ operation = await acquireOperation(stateDirectory);
1467
+ } catch {
1468
+ return silent('cache-unavailable');
1469
+ }
1470
+ if (!operation) {
1471
+ try {
1472
+ const cached = await readState(stateDirectory, localRelease.version);
1473
+ if (cached.candidate
1474
+ && cached.notification.offeredDigests.includes(cached.candidate.targetDigest)) {
1475
+ return resultForCandidate(localRelease, cached);
1476
+ }
1477
+ } catch {
1478
+ return silent('cache-unavailable');
1479
+ }
1480
+ return silent('check-in-progress');
1481
+ }
1482
+
1483
+ try {
1484
+ state = await readState(stateDirectory, localRelease.version);
1485
+ const racedResult = freshStateResult(localRelease, state, nowMs);
1486
+ if (racedResult) {
1487
+ await cancelOperation(stateDirectory, operation);
1488
+ return racedResult;
1489
+ }
1490
+
1491
+ if (!await operationOwnsActiveClaim(stateDirectory, operation)) {
1492
+ return await resultAfterLosingOperation(stateDirectory, operation, localRelease);
1493
+ }
1494
+
1495
+ await verifyCacheToken(cacheTokenFor(stateDirectory));
1496
+ if (!await operationOwnsActiveClaim(stateDirectory, operation)) {
1497
+ return await resultAfterLosingOperation(stateDirectory, operation, localRelease);
1498
+ }
1499
+ let fetched;
1500
+ try {
1501
+ fetched = await fetchCandidate({
1502
+ fetchImpl,
1503
+ manifestUrl: localRelease.updateManifestUrl,
1504
+ timeoutMs,
1505
+ });
1506
+ } catch (error) {
1507
+ const reason = error instanceof UpdateContractError ? 'invalid-manifest' : 'check-failed';
1508
+ try {
1509
+ const committed = await commitOperation(
1510
+ stateDirectory,
1511
+ operation,
1512
+ localRelease.version,
1513
+ (current) => ({
1514
+ state: stateAfterFailedCheck(current, nowMs),
1515
+ result: silent(reason),
1516
+ }),
1517
+ );
1518
+ return committed.result;
1519
+ } catch {
1520
+ await cancelOperation(stateDirectory, operation).catch(() => {});
1521
+ return silent('cache-unavailable');
1522
+ }
1523
+ }
1524
+
1525
+ try {
1526
+ const committed = await commitOperation(
1527
+ stateDirectory,
1528
+ operation,
1529
+ localRelease.version,
1530
+ (current) => {
1531
+ const next = {
1532
+ ...current,
1533
+ candidate: fetched.candidate,
1534
+ check: {
1535
+ nextCheckAt: nextSuccessfulCheck(nowMs, random),
1536
+ consecutiveFailures: 0,
1537
+ },
1538
+ notification: {
1539
+ offeredDigests: [...current.notification.offeredDigests],
1540
+ acknowledgedDigests: [...current.notification.acknowledgedDigests],
1541
+ },
1542
+ };
1543
+ const result = resultForCandidate(localRelease, next);
1544
+ if (result.status === 'update_available') {
1545
+ if (!next.notification.offeredDigests.includes(next.candidate.targetDigest)) {
1546
+ next.notification.offeredDigests.push(next.candidate.targetDigest);
1547
+ }
1548
+ }
1549
+ return {
1550
+ state: next,
1551
+ result,
1552
+ capacityFallback: {
1553
+ state: stateAfterFailedCheck(current, nowMs, true),
1554
+ result: silent('cache-unavailable'),
1555
+ },
1556
+ };
1557
+ },
1558
+ );
1559
+ return committed.result;
1560
+ } catch {
1561
+ await cancelOperation(stateDirectory, operation).catch(() => {});
1562
+ return silent('cache-unavailable');
1563
+ }
1564
+ } catch {
1565
+ await cancelOperation(stateDirectory, operation).catch(() => {});
1566
+ return silent('cache-unavailable');
1567
+ }
1568
+ }
1569
+
1570
+ export async function acknowledgeUpdate({
1571
+ releasePath = defaultReleasePath,
1572
+ cacheDirectory = defaultCacheDirectory(),
1573
+ eventKey,
1574
+ monotonicNow = () => performance.now(),
1575
+ } = {}) {
1576
+ const targetDigest = digestForEventKey(eventKey);
1577
+ if (!targetDigest || typeof monotonicNow !== 'function') {
1578
+ return silent('invalid-acknowledgement');
1579
+ }
1580
+ let localRelease;
1581
+ try {
1582
+ localRelease = validateLocalRelease(await readJsonFile(releasePath, MAX_LOCAL_RELEASE_BYTES));
1583
+ } catch {
1584
+ return silent('invalid-local-release');
1585
+ }
1586
+ let stateDirectory = versionCacheDirectory(cacheDirectory, localRelease.version);
1587
+ try {
1588
+ stateDirectory = await prepareCacheDirectory(stateDirectory);
1589
+ } catch {
1590
+ return silent('cache-unavailable');
1591
+ }
1592
+ let deadline;
1593
+ try {
1594
+ deadline = Number(monotonicNow()) + ACK_LOCK_WAIT_MS;
1595
+ } catch {
1596
+ return silent('invalid-acknowledgement');
1597
+ }
1598
+ if (!Number.isFinite(deadline)) return silent('invalid-acknowledgement');
1599
+ while (true) {
1600
+ let operation;
1601
+ try {
1602
+ operation = await acquireOperation(stateDirectory);
1603
+ } catch {
1604
+ return silent('cache-unavailable');
1605
+ }
1606
+ if (!operation) {
1607
+ if (!await waitUntilRetry(deadline, monotonicNow)) return silent('check-in-progress');
1608
+ continue;
1609
+ }
1610
+ try {
1611
+ const committed = await commitOperation(
1612
+ stateDirectory,
1613
+ operation,
1614
+ localRelease.version,
1615
+ (state) => {
1616
+ const wasOffered = state.notification.offeredDigests.includes(targetDigest);
1617
+ const wasAcknowledged = state.notification.acknowledgedDigests.includes(targetDigest);
1618
+ if (!wasOffered && !wasAcknowledged) {
1619
+ return { state: null, result: silent('invalid-acknowledgement') };
1620
+ }
1621
+ return {
1622
+ state: stateAfterAcknowledgement(state, targetDigest),
1623
+ result: { status: 'acknowledged', eventKey },
1624
+ };
1625
+ },
1626
+ );
1627
+ if (!committed.fenced) return committed.result;
1628
+ if (!await waitUntilRetry(deadline, monotonicNow)) return silent('check-in-progress');
1629
+ } catch {
1630
+ await cancelOperation(stateDirectory, operation).catch(() => {});
1631
+ return silent('cache-unavailable');
1632
+ }
1633
+ }
1634
+ }
1635
+
1636
+ async function runCli() {
1637
+ if (process.env.ARCHIFY_UPDATE_CHECK_DISABLED === '1') return silent('disabled');
1638
+ const argumentsList = process.argv.slice(2);
1639
+ if (argumentsList.length === 0) return checkForUpdate();
1640
+ if (argumentsList.length === 2 && argumentsList[0] === '--ack') {
1641
+ return acknowledgeUpdate({ eventKey: argumentsList[1] });
1642
+ }
1643
+ return silent('invalid-arguments');
1644
+ }
1645
+
1646
+ async function isMainModule() {
1647
+ if (!process.argv[1]) return false;
1648
+ try {
1649
+ const [entryPath, modulePath] = await Promise.all([
1650
+ fs.realpath(path.resolve(process.argv[1])),
1651
+ fs.realpath(fileURLToPath(import.meta.url)),
1652
+ ]);
1653
+ return entryPath === modulePath;
1654
+ } catch {
1655
+ return path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url));
1656
+ }
1657
+ }
1658
+
1659
+ if (await isMainModule()) {
1660
+ let result;
1661
+ try {
1662
+ result = await runCli();
1663
+ } catch {
1664
+ result = silent('check-failed');
1665
+ }
1666
+ process.stdout.write(`${JSON.stringify(result)}\n`);
1667
+ }