@runcofactor/cli 0.54.0 → 0.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/README.md +2 -1
  2. package/dist/{account-6YJAU53I.js → account-CZYZD2JS.js} +5 -5
  3. package/dist/{alert-Z7V2ZUEW.js → alert-JQ4SDEB7.js} +8 -8
  4. package/dist/{api-catalog-UEDTRM6E.js → api-catalog-IWQC35P2.js} +5 -5
  5. package/dist/{api-key-V5GEPRRG.js → api-key-GUDXIIEA.js} +5 -5
  6. package/dist/{attachment-M7VZ6GSE.js → attachment-CPZYOBUB.js} +5 -5
  7. package/dist/{auth-DVTMWJKF.js → auth-DS6MBVEI.js} +3 -3
  8. package/dist/{benchmark-KZJZYUTQ.js → benchmark-6HPCIQHD.js} +5 -5
  9. package/dist/{blackcat-QJXYDE66.js → blackcat-V3L63WFE.js} +3 -3
  10. package/dist/{chunk-FLDIN6VX.js → chunk-2E3STBHS.js} +2 -2
  11. package/dist/{chunk-KNWKOKM7.js → chunk-2OHYMT3A.js} +20 -11
  12. package/dist/chunk-2OHYMT3A.js.map +1 -0
  13. package/dist/{chunk-L4Y5PZUE.js → chunk-3LGCHNBC.js} +2 -2
  14. package/dist/{chunk-ZFN5FWN2.js → chunk-4MFMPC5P.js} +46 -2
  15. package/dist/chunk-4MFMPC5P.js.map +1 -0
  16. package/dist/{chunk-2M4PZK6Q.js → chunk-56HFZHQM.js} +8 -8
  17. package/dist/{chunk-AQOHNAEN.js → chunk-5OYWNS6D.js} +20 -9
  18. package/dist/chunk-5OYWNS6D.js.map +1 -0
  19. package/dist/{chunk-RA2NRAWU.js → chunk-62EXS76C.js} +6 -6
  20. package/dist/{chunk-HGEJHS45.js → chunk-6ILWJFKY.js} +368 -6
  21. package/dist/chunk-6ILWJFKY.js.map +1 -0
  22. package/dist/{chunk-4EWSONHO.js → chunk-BDOL622T.js} +4 -4
  23. package/dist/{chunk-T6EQFICG.js → chunk-C3CXVX2B.js} +4 -4
  24. package/dist/{chunk-33U56YGD.js → chunk-CWICLBLU.js} +2 -1
  25. package/dist/{chunk-33U56YGD.js.map → chunk-CWICLBLU.js.map} +1 -1
  26. package/dist/{chunk-MQQCUU5S.js → chunk-DEVD2WD2.js} +2 -2
  27. package/dist/{chunk-TRC63RSK.js → chunk-EWK44F6V.js} +3 -3
  28. package/dist/chunk-EWK44F6V.js.map +1 -0
  29. package/dist/{chunk-Q7UAEFW5.js → chunk-FNHPTTRF.js} +7 -5
  30. package/dist/chunk-FNHPTTRF.js.map +1 -0
  31. package/dist/{chunk-JAIMLQOU.js → chunk-GCD4EHCD.js} +6 -6
  32. package/dist/{chunk-7MRZWPVC.js → chunk-H6ZMKNP7.js} +3 -3
  33. package/dist/{chunk-ZOPVBINJ.js → chunk-IEWAH7W3.js} +5 -5
  34. package/dist/{chunk-74TU6PGO.js → chunk-IV4HRGKX.js} +77 -12
  35. package/dist/chunk-IV4HRGKX.js.map +1 -0
  36. package/dist/{chunk-ZO5MSM4Q.js → chunk-J2ZQULU3.js} +2 -2
  37. package/dist/{chunk-IVWWYKAM.js → chunk-K2IDR6FR.js} +3 -3
  38. package/dist/{chunk-QGP67JOK.js → chunk-KTZRI27M.js} +4 -4
  39. package/dist/{chunk-HESV3F6U.js → chunk-L644NMDI.js} +3 -3
  40. package/dist/{chunk-HESV3F6U.js.map → chunk-L644NMDI.js.map} +1 -1
  41. package/dist/{chunk-LPQTXBJF.js → chunk-LFT7GUWS.js} +63 -17
  42. package/dist/chunk-LFT7GUWS.js.map +1 -0
  43. package/dist/{chunk-O7MBDOMO.js → chunk-O7B2IYCZ.js} +30 -14
  44. package/dist/chunk-O7B2IYCZ.js.map +1 -0
  45. package/dist/{chunk-R2CVV7QT.js → chunk-RHWPZKRV.js} +28 -1
  46. package/dist/chunk-RHWPZKRV.js.map +1 -0
  47. package/dist/{chunk-3YNR6U6Q.js → chunk-UJSW72LG.js} +3 -3
  48. package/dist/{chunk-G2SCV2CY.js → chunk-VACACCD6.js} +3 -3
  49. package/dist/{chunk-CV22445U.js → chunk-VMAR67FF.js} +2 -2
  50. package/dist/{chunk-QYHJUHL7.js → chunk-VMJ7TBVM.js} +2 -2
  51. package/dist/chunk-WRRA4ATV.js +332 -0
  52. package/dist/chunk-WRRA4ATV.js.map +1 -0
  53. package/dist/{chunk-YACDH6MJ.js → chunk-XHEVDABB.js} +2 -2
  54. package/dist/{chunk-7VBX2M4F.js → chunk-XHGF6VC6.js} +4 -4
  55. package/dist/{credential-546ER7WQ.js → credential-GJW7LGYP.js} +5 -5
  56. package/dist/{credential-template-BWMEBGBN.js → credential-template-UWBYKKWD.js} +5 -5
  57. package/dist/dev-PBW7BSOD.js +605 -0
  58. package/dist/dev-PBW7BSOD.js.map +1 -0
  59. package/dist/{digest-NIVBDDI2.js → digest-XEHGCJM4.js} +8 -8
  60. package/dist/{docs-AK4MDP6K.js → docs-KH4BCEQF.js} +5 -5
  61. package/dist/{doctor-NCLA47EL.js → doctor-FOXXE5SK.js} +3 -3
  62. package/dist/{environment-X3PV5IN2.js → environment-EBDPQTIS.js} +7 -7
  63. package/dist/{eval-5FZKYZ6O.js → eval-QZSSEICV.js} +67 -27
  64. package/dist/eval-QZSSEICV.js.map +1 -0
  65. package/dist/{exploration-2RVGIUKT.js → exploration-LCGKRGOQ.js} +5 -5
  66. package/dist/{exploratory-test-SJ5HFI3L.js → exploratory-test-SGECQGIX.js} +13 -5
  67. package/dist/exploratory-test-SGECQGIX.js.map +1 -0
  68. package/dist/{external-run-4NDVAHQB.js → external-run-2AJAI5O7.js} +9 -9
  69. package/dist/{feature-flag-C4IRE724.js → feature-flag-P7N4WQIH.js} +5 -5
  70. package/dist/{feedback-337OZCKC.js → feedback-LAT7TQ2T.js} +6 -6
  71. package/dist/{flow-D7ZLBKCH.js → flow-NH2DC6AP.js} +5 -5
  72. package/dist/{github-LKXYIALH.js → github-FCEKBLXI.js} +6 -6
  73. package/dist/index.js +106 -408
  74. package/dist/index.js.map +1 -1
  75. package/dist/{init-GKEC6LPH.js → init-5U5EZOAW.js} +5 -5
  76. package/dist/{install-K45SAT2V.js → install-X3UNVXKY.js} +5 -5
  77. package/dist/{issues-2HMTQYWR.js → issues-4P6I3YQW.js} +8 -8
  78. package/dist/{knobs-KUCGLO5Z.js → knobs-JKTWJ6ZG.js} +5 -5
  79. package/dist/{landing-KAET2I73.js → landing-6AGLKI66.js} +7 -7
  80. package/dist/{list-GDV4UYWV.js → list-TWP2RP33.js} +5 -5
  81. package/dist/{local-NFOSJLLV.js → local-D75QE5TB.js} +7 -7
  82. package/dist/{login-Y77HWROY.js → login-RBET4TIP.js} +5 -5
  83. package/dist/{mcp-CPCFMU6B.js → mcp-562SUUMK.js} +11 -11
  84. package/dist/{metrics-Y245ST63.js → metrics-4WF2GEPA.js} +5 -5
  85. package/dist/{monitor-BUDWSC3S.js → monitor-WJXH54H2.js} +5 -5
  86. package/dist/{perf-3JOAXPHP.js → perf-C6N6P7YG.js} +12 -361
  87. package/dist/perf-C6N6P7YG.js.map +1 -0
  88. package/dist/{product-CXL3YTMD.js → product-CYEQ7HOB.js} +5 -5
  89. package/dist/{product-map-656BTU74.js → product-map-MSDLIFZY.js} +7 -7
  90. package/dist/{prompts-3SZ7O5E6.js → prompts-ME53WPN4.js} +5 -5
  91. package/dist/{proofs-4CSCCSNL.js → proofs-D3G6ZUFP.js} +11 -9
  92. package/dist/{property-IEKBXRVY.js → property-5GLJNAVB.js} +29 -10
  93. package/dist/property-5GLJNAVB.js.map +1 -0
  94. package/dist/{psql-M4CM6TSI.js → psql-EXAUDU3W.js} +5 -5
  95. package/dist/{record-QP4TYZBS.js → record-EE7NGNKY.js} +6 -6
  96. package/dist/{redis-KG47IGJV.js → redis-M4EYWKOJ.js} +5 -5
  97. package/dist/{release-QHPSQULK.js → release-4KUWVWWC.js} +5 -5
  98. package/dist/{runbook-YVBG2QK3.js → runbook-AHU77ANQ.js} +5 -5
  99. package/dist/{sandbox-ZXDZTCCN.js → sandbox-FBLJEYA2.js} +1917 -1633
  100. package/dist/sandbox-FBLJEYA2.js.map +1 -0
  101. package/dist/{scenarios-2SX2NUCD.js → scenarios-TU656T2B.js} +5 -5
  102. package/dist/{schedule-P6F7MLXX.js → schedule-DXJI6SYO.js} +8 -8
  103. package/dist/{session-SZMCZTPE.js → session-RUFTJHF4.js} +10 -10
  104. package/dist/{skill-D4XFW6QI.js → skill-ZK76GQ47.js} +111 -13
  105. package/dist/skill-ZK76GQ47.js.map +1 -0
  106. package/dist/skills/cofactor-debug-run/SKILL.md +6 -1
  107. package/dist/skills/cofactor-debug-run/history/2.0.10.md +320 -0
  108. package/dist/skills/cofactor-debug-run/history/2.0.9.md +319 -0
  109. package/dist/skills/cofactor-debug-run/version.json +1 -1
  110. package/dist/{slack-UDXTPOSW.js → slack-M3OKDVMY.js} +6 -6
  111. package/dist/{spec-YCBBIV27.js → spec-5MXEBLYR.js} +5 -5
  112. package/dist/{src-DY7GLRO2.js → src-2X5OQRPJ.js} +4 -4
  113. package/dist/{src-NG7DRE5A.js → src-72EFBA2R.js} +3 -3
  114. package/dist/{src-DEGBASCD.js → src-KH6WSBJV.js} +5 -5
  115. package/dist/{src-ZKVNVLXY.js → src-VLV36QYH.js} +127 -20
  116. package/dist/src-VLV36QYH.js.map +1 -0
  117. package/dist/{start-ZYZSORPY.js → start-PDYBRB5T.js} +5 -5
  118. package/dist/{status-ESIG6A7W.js → status-VMVPVCDA.js} +5 -5
  119. package/dist/{tag-3K5Y25AR.js → tag-7KTDLPVU.js} +6 -6
  120. package/dist/{task-JLNX7O3M.js → task-QLFYZNKR.js} +6 -6
  121. package/dist/{teams-F4IU3EXE.js → teams-Y7BXCY6P.js} +7 -7
  122. package/dist/{test-suite-QHDUW5W5.js → test-suite-6U22AYFG.js} +15 -14
  123. package/dist/test-suite-6U22AYFG.js.map +1 -0
  124. package/dist/{tickets-NBL5WACL.js → tickets-CQDJDNPY.js} +5 -5
  125. package/dist/{trace-ZKYAI5JF.js → trace-QDORWXYX.js} +8 -8
  126. package/dist/{triage-5NWCREUM.js → triage-GY7M3UGG.js} +6 -6
  127. package/dist/{trigger-KXYYY5IR.js → trigger-DLFAORX4.js} +9 -9
  128. package/dist/{update-check-Q2URVJLC.js → update-check-ES6XQ7OF.js} +4 -4
  129. package/dist/{upgrade-4XSASC7U.js → upgrade-WKEOW5GS.js} +5 -5
  130. package/dist/{usage-UY7VZYVY.js → usage-E6FDTR3W.js} +7 -7
  131. package/dist/{utilities-53NXNLA7.js → utilities-4Z6YX3L5.js} +6 -6
  132. package/dist/{visual-IJAT7X7D.js → visual-TA6LPVDJ.js} +5 -5
  133. package/dist/{workflow-OQNH2FIS.js → workflow-B5FXS2CJ.js} +92 -36
  134. package/dist/workflow-B5FXS2CJ.js.map +1 -0
  135. package/dist/{workflow-cache-FP6MKLVR.js → workflow-cache-MNLEMRMM.js} +16 -16
  136. package/dist/{workflow-cache-convergence-resolve-HS5KO7US.js → workflow-cache-convergence-resolve-G5HJM6MF.js} +5 -5
  137. package/dist/{workflow-debug-EM55H2QE.js → workflow-debug-TNBUGSBK.js} +11 -11
  138. package/dist/{workflow-feedback-GNWNWVHH.js → workflow-feedback-G2ZXAUGI.js} +5 -5
  139. package/dist/{workflow-fork-M5AQWST7.js → workflow-fork-6HHOMC2D.js} +5 -5
  140. package/dist/{workflow-promotion-HYULZDYL.js → workflow-promotion-RG7L6JZT.js} +5 -5
  141. package/dist/{workflow-proposals-EN327FSE.js → workflow-proposals-ZNF2F3UE.js} +5 -5
  142. package/dist/{workflow-revisions-ZMTVNWRW.js → workflow-revisions-TP64Z6FO.js} +5 -5
  143. package/dist/{workflow-status-FNAJG34W.js → workflow-status-5RG3JX56.js} +5 -5
  144. package/package.json +3 -2
  145. package/dist/chunk-74TU6PGO.js.map +0 -1
  146. package/dist/chunk-AQOHNAEN.js.map +0 -1
  147. package/dist/chunk-HGEJHS45.js.map +0 -1
  148. package/dist/chunk-KNWKOKM7.js.map +0 -1
  149. package/dist/chunk-LPQTXBJF.js.map +0 -1
  150. package/dist/chunk-O7MBDOMO.js.map +0 -1
  151. package/dist/chunk-Q7UAEFW5.js.map +0 -1
  152. package/dist/chunk-R2CVV7QT.js.map +0 -1
  153. package/dist/chunk-TRC63RSK.js.map +0 -1
  154. package/dist/chunk-ZFN5FWN2.js.map +0 -1
  155. package/dist/eval-5FZKYZ6O.js.map +0 -1
  156. package/dist/exploratory-test-SJ5HFI3L.js.map +0 -1
  157. package/dist/perf-3JOAXPHP.js.map +0 -1
  158. package/dist/property-IEKBXRVY.js.map +0 -1
  159. package/dist/sandbox-ZXDZTCCN.js.map +0 -1
  160. package/dist/skill-D4XFW6QI.js.map +0 -1
  161. package/dist/src-ZKVNVLXY.js.map +0 -1
  162. package/dist/test-suite-QHDUW5W5.js.map +0 -1
  163. package/dist/workflow-OQNH2FIS.js.map +0 -1
  164. /package/dist/{account-6YJAU53I.js.map → account-CZYZD2JS.js.map} +0 -0
  165. /package/dist/{alert-Z7V2ZUEW.js.map → alert-JQ4SDEB7.js.map} +0 -0
  166. /package/dist/{api-catalog-UEDTRM6E.js.map → api-catalog-IWQC35P2.js.map} +0 -0
  167. /package/dist/{api-key-V5GEPRRG.js.map → api-key-GUDXIIEA.js.map} +0 -0
  168. /package/dist/{attachment-M7VZ6GSE.js.map → attachment-CPZYOBUB.js.map} +0 -0
  169. /package/dist/{auth-DVTMWJKF.js.map → auth-DS6MBVEI.js.map} +0 -0
  170. /package/dist/{benchmark-KZJZYUTQ.js.map → benchmark-6HPCIQHD.js.map} +0 -0
  171. /package/dist/{blackcat-QJXYDE66.js.map → blackcat-V3L63WFE.js.map} +0 -0
  172. /package/dist/{chunk-FLDIN6VX.js.map → chunk-2E3STBHS.js.map} +0 -0
  173. /package/dist/{chunk-L4Y5PZUE.js.map → chunk-3LGCHNBC.js.map} +0 -0
  174. /package/dist/{chunk-2M4PZK6Q.js.map → chunk-56HFZHQM.js.map} +0 -0
  175. /package/dist/{chunk-RA2NRAWU.js.map → chunk-62EXS76C.js.map} +0 -0
  176. /package/dist/{chunk-4EWSONHO.js.map → chunk-BDOL622T.js.map} +0 -0
  177. /package/dist/{chunk-T6EQFICG.js.map → chunk-C3CXVX2B.js.map} +0 -0
  178. /package/dist/{chunk-MQQCUU5S.js.map → chunk-DEVD2WD2.js.map} +0 -0
  179. /package/dist/{chunk-JAIMLQOU.js.map → chunk-GCD4EHCD.js.map} +0 -0
  180. /package/dist/{chunk-7MRZWPVC.js.map → chunk-H6ZMKNP7.js.map} +0 -0
  181. /package/dist/{chunk-ZOPVBINJ.js.map → chunk-IEWAH7W3.js.map} +0 -0
  182. /package/dist/{chunk-ZO5MSM4Q.js.map → chunk-J2ZQULU3.js.map} +0 -0
  183. /package/dist/{chunk-IVWWYKAM.js.map → chunk-K2IDR6FR.js.map} +0 -0
  184. /package/dist/{chunk-QGP67JOK.js.map → chunk-KTZRI27M.js.map} +0 -0
  185. /package/dist/{chunk-3YNR6U6Q.js.map → chunk-UJSW72LG.js.map} +0 -0
  186. /package/dist/{chunk-G2SCV2CY.js.map → chunk-VACACCD6.js.map} +0 -0
  187. /package/dist/{chunk-CV22445U.js.map → chunk-VMAR67FF.js.map} +0 -0
  188. /package/dist/{chunk-QYHJUHL7.js.map → chunk-VMJ7TBVM.js.map} +0 -0
  189. /package/dist/{chunk-YACDH6MJ.js.map → chunk-XHEVDABB.js.map} +0 -0
  190. /package/dist/{chunk-7VBX2M4F.js.map → chunk-XHGF6VC6.js.map} +0 -0
  191. /package/dist/{credential-546ER7WQ.js.map → credential-GJW7LGYP.js.map} +0 -0
  192. /package/dist/{credential-template-BWMEBGBN.js.map → credential-template-UWBYKKWD.js.map} +0 -0
  193. /package/dist/{digest-NIVBDDI2.js.map → digest-XEHGCJM4.js.map} +0 -0
  194. /package/dist/{docs-AK4MDP6K.js.map → docs-KH4BCEQF.js.map} +0 -0
  195. /package/dist/{doctor-NCLA47EL.js.map → doctor-FOXXE5SK.js.map} +0 -0
  196. /package/dist/{environment-X3PV5IN2.js.map → environment-EBDPQTIS.js.map} +0 -0
  197. /package/dist/{exploration-2RVGIUKT.js.map → exploration-LCGKRGOQ.js.map} +0 -0
  198. /package/dist/{external-run-4NDVAHQB.js.map → external-run-2AJAI5O7.js.map} +0 -0
  199. /package/dist/{feature-flag-C4IRE724.js.map → feature-flag-P7N4WQIH.js.map} +0 -0
  200. /package/dist/{feedback-337OZCKC.js.map → feedback-LAT7TQ2T.js.map} +0 -0
  201. /package/dist/{flow-D7ZLBKCH.js.map → flow-NH2DC6AP.js.map} +0 -0
  202. /package/dist/{github-LKXYIALH.js.map → github-FCEKBLXI.js.map} +0 -0
  203. /package/dist/{init-GKEC6LPH.js.map → init-5U5EZOAW.js.map} +0 -0
  204. /package/dist/{install-K45SAT2V.js.map → install-X3UNVXKY.js.map} +0 -0
  205. /package/dist/{issues-2HMTQYWR.js.map → issues-4P6I3YQW.js.map} +0 -0
  206. /package/dist/{knobs-KUCGLO5Z.js.map → knobs-JKTWJ6ZG.js.map} +0 -0
  207. /package/dist/{landing-KAET2I73.js.map → landing-6AGLKI66.js.map} +0 -0
  208. /package/dist/{list-GDV4UYWV.js.map → list-TWP2RP33.js.map} +0 -0
  209. /package/dist/{local-NFOSJLLV.js.map → local-D75QE5TB.js.map} +0 -0
  210. /package/dist/{login-Y77HWROY.js.map → login-RBET4TIP.js.map} +0 -0
  211. /package/dist/{mcp-CPCFMU6B.js.map → mcp-562SUUMK.js.map} +0 -0
  212. /package/dist/{metrics-Y245ST63.js.map → metrics-4WF2GEPA.js.map} +0 -0
  213. /package/dist/{monitor-BUDWSC3S.js.map → monitor-WJXH54H2.js.map} +0 -0
  214. /package/dist/{product-CXL3YTMD.js.map → product-CYEQ7HOB.js.map} +0 -0
  215. /package/dist/{product-map-656BTU74.js.map → product-map-MSDLIFZY.js.map} +0 -0
  216. /package/dist/{prompts-3SZ7O5E6.js.map → prompts-ME53WPN4.js.map} +0 -0
  217. /package/dist/{proofs-4CSCCSNL.js.map → proofs-D3G6ZUFP.js.map} +0 -0
  218. /package/dist/{psql-M4CM6TSI.js.map → psql-EXAUDU3W.js.map} +0 -0
  219. /package/dist/{record-QP4TYZBS.js.map → record-EE7NGNKY.js.map} +0 -0
  220. /package/dist/{redis-KG47IGJV.js.map → redis-M4EYWKOJ.js.map} +0 -0
  221. /package/dist/{release-QHPSQULK.js.map → release-4KUWVWWC.js.map} +0 -0
  222. /package/dist/{runbook-YVBG2QK3.js.map → runbook-AHU77ANQ.js.map} +0 -0
  223. /package/dist/{scenarios-2SX2NUCD.js.map → scenarios-TU656T2B.js.map} +0 -0
  224. /package/dist/{schedule-P6F7MLXX.js.map → schedule-DXJI6SYO.js.map} +0 -0
  225. /package/dist/{session-SZMCZTPE.js.map → session-RUFTJHF4.js.map} +0 -0
  226. /package/dist/{slack-UDXTPOSW.js.map → slack-M3OKDVMY.js.map} +0 -0
  227. /package/dist/{spec-YCBBIV27.js.map → spec-5MXEBLYR.js.map} +0 -0
  228. /package/dist/{src-DEGBASCD.js.map → src-2X5OQRPJ.js.map} +0 -0
  229. /package/dist/{src-DY7GLRO2.js.map → src-72EFBA2R.js.map} +0 -0
  230. /package/dist/{src-NG7DRE5A.js.map → src-KH6WSBJV.js.map} +0 -0
  231. /package/dist/{start-ZYZSORPY.js.map → start-PDYBRB5T.js.map} +0 -0
  232. /package/dist/{status-ESIG6A7W.js.map → status-VMVPVCDA.js.map} +0 -0
  233. /package/dist/{tag-3K5Y25AR.js.map → tag-7KTDLPVU.js.map} +0 -0
  234. /package/dist/{task-JLNX7O3M.js.map → task-QLFYZNKR.js.map} +0 -0
  235. /package/dist/{teams-F4IU3EXE.js.map → teams-Y7BXCY6P.js.map} +0 -0
  236. /package/dist/{tickets-NBL5WACL.js.map → tickets-CQDJDNPY.js.map} +0 -0
  237. /package/dist/{trace-ZKYAI5JF.js.map → trace-QDORWXYX.js.map} +0 -0
  238. /package/dist/{triage-5NWCREUM.js.map → triage-GY7M3UGG.js.map} +0 -0
  239. /package/dist/{trigger-KXYYY5IR.js.map → trigger-DLFAORX4.js.map} +0 -0
  240. /package/dist/{update-check-Q2URVJLC.js.map → update-check-ES6XQ7OF.js.map} +0 -0
  241. /package/dist/{upgrade-4XSASC7U.js.map → upgrade-WKEOW5GS.js.map} +0 -0
  242. /package/dist/{usage-UY7VZYVY.js.map → usage-E6FDTR3W.js.map} +0 -0
  243. /package/dist/{utilities-53NXNLA7.js.map → utilities-4Z6YX3L5.js.map} +0 -0
  244. /package/dist/{visual-IJAT7X7D.js.map → visual-TA6LPVDJ.js.map} +0 -0
  245. /package/dist/{workflow-cache-FP6MKLVR.js.map → workflow-cache-MNLEMRMM.js.map} +0 -0
  246. /package/dist/{workflow-cache-convergence-resolve-HS5KO7US.js.map → workflow-cache-convergence-resolve-G5HJM6MF.js.map} +0 -0
  247. /package/dist/{workflow-debug-EM55H2QE.js.map → workflow-debug-TNBUGSBK.js.map} +0 -0
  248. /package/dist/{workflow-feedback-GNWNWVHH.js.map → workflow-feedback-G2ZXAUGI.js.map} +0 -0
  249. /package/dist/{workflow-fork-M5AQWST7.js.map → workflow-fork-6HHOMC2D.js.map} +0 -0
  250. /package/dist/{workflow-promotion-HYULZDYL.js.map → workflow-promotion-RG7L6JZT.js.map} +0 -0
  251. /package/dist/{workflow-proposals-EN327FSE.js.map → workflow-proposals-ZNF2F3UE.js.map} +0 -0
  252. /package/dist/{workflow-revisions-ZMTVNWRW.js.map → workflow-revisions-TP64Z6FO.js.map} +0 -0
  253. /package/dist/{workflow-status-FNAJG34W.js.map → workflow-status-5RG3JX56.js.map} +0 -0
@@ -1,23 +1,23 @@
1
1
  import { createRequire as __cr } from "module"; import { fileURLToPath as __fup } from "url"; import { dirname as __dn } from "path"; const require = __cr(import.meta.url); const __filename = __fup(import.meta.url); const __dirname = __dn(__filename);
2
2
  import {
3
3
  parseDurationSeconds
4
- } from "./chunk-74TU6PGO.js";
4
+ } from "./chunk-IV4HRGKX.js";
5
5
  import {
6
6
  resolveApplicationId,
7
7
  resolveOrCreateSandboxApplication
8
- } from "./chunk-HGEJHS45.js";
9
- import "./chunk-QYHJUHL7.js";
8
+ } from "./chunk-6ILWJFKY.js";
9
+ import "./chunk-VMJ7TBVM.js";
10
10
  import {
11
11
  SANDBOX_V2_GUIDE_SECTIONS,
12
12
  printServedGuide
13
- } from "./chunk-O7MBDOMO.js";
13
+ } from "./chunk-O7B2IYCZ.js";
14
14
  import {
15
15
  findOperation,
16
16
  getAllPlugins,
17
17
  getPlugin,
18
18
  getPluginIds,
19
19
  pluginOperations
20
- } from "./chunk-33U56YGD.js";
20
+ } from "./chunk-CWICLBLU.js";
21
21
  import {
22
22
  UUID_RE,
23
23
  apiRequest,
@@ -27,9 +27,10 @@ import {
27
27
  getTargetOrg,
28
28
  selectProperty,
29
29
  stripOneTrailingNewline
30
- } from "./chunk-TRC63RSK.js";
30
+ } from "./chunk-EWK44F6V.js";
31
31
  import "./chunk-S3Q7MDCS.js";
32
32
  import {
33
+ activeConfigDir,
33
34
  apiFetch,
34
35
  decodeJwtPayload,
35
36
  exitOnFailure,
@@ -37,33 +38,35 @@ import {
37
38
  hasFlag,
38
39
  isTerminalFailure,
39
40
  resolveConfig,
41
+ resolveOrgAppUrl,
40
42
  splitAtSeparator
41
- } from "./chunk-ZFN5FWN2.js";
43
+ } from "./chunk-4MFMPC5P.js";
42
44
  import {
43
45
  SANDBOX_GUIDE_SECTIONS,
44
46
  writeRawOutput
45
- } from "./chunk-KNWKOKM7.js";
47
+ } from "./chunk-2OHYMT3A.js";
46
48
  import {
47
49
  INSTANCE_SIZES,
48
50
  INSTANCE_SIZE_SPEC,
49
51
  SANDBOX_EGRESS_MODES,
50
- parseInstanceSize
51
- } from "./chunk-R2CVV7QT.js";
52
+ parseInstanceSize,
53
+ sandboxPreviewPagePath
54
+ } from "./chunk-RHWPZKRV.js";
52
55
  import "./chunk-TOWZAG3I.js";
53
56
 
54
57
  // src/sandbox.ts
55
- import process10 from "process";
56
- import { readFileSync as readFileSync6 } from "fs";
58
+ import process11 from "process";
59
+ import { readFileSync as readFileSync7 } from "fs";
57
60
 
58
61
  // src/sandbox-emulation.ts
59
- import process from "process";
62
+ import process2 from "process";
60
63
  import { readFileSync } from "fs";
61
64
  var pad = (value, width) => value.padEnd(width);
62
65
  async function fetchEmulation(apiUrl, token, templateId) {
63
66
  const res = await apiRequest(apiUrl, token, "GET", `/org/environments/templates/${templateId}/emulation`);
64
67
  if (!res.ok) {
65
68
  console.error(`Error: ${res.error}`);
66
- process.exit(1);
69
+ process2.exit(1);
67
70
  }
68
71
  return { config: res.config ?? { enabled: false, plugins: [] }, catalog: res.catalog ?? [] };
69
72
  }
@@ -78,14 +81,14 @@ async function patchEmulation(apiUrl, token, templateId, config) {
78
81
  );
79
82
  if (!res.ok) {
80
83
  console.error(`Error: ${res.error}`);
81
- process.exit(1);
84
+ process2.exit(1);
82
85
  }
83
86
  }
84
87
  function assertKnownPlugin(pluginId) {
85
88
  const ids = getPluginIds();
86
89
  if (!ids.includes(pluginId)) {
87
90
  console.error(`Error: unknown plugin '${pluginId}'. Available: ${ids.join(", ")}`);
88
- process.exit(1);
91
+ process2.exit(1);
89
92
  }
90
93
  }
91
94
  function readJsonFileOrExit(path, flag) {
@@ -96,7 +99,7 @@ function readJsonFileOrExit(path, flag) {
96
99
  console.error(
97
100
  `Error reading ${flag} file '${path}': ${err instanceof Error ? err.message : String(err)}`
98
101
  );
99
- process.exit(1);
102
+ process2.exit(1);
100
103
  }
101
104
  try {
102
105
  return JSON.parse(raw);
@@ -104,7 +107,7 @@ function readJsonFileOrExit(path, flag) {
104
107
  console.error(
105
108
  `Error parsing ${flag} file '${path}' as JSON: ${err instanceof Error ? err.message : String(err)}`
106
109
  );
107
- process.exit(1);
110
+ process2.exit(1);
108
111
  }
109
112
  }
110
113
  function readJsonObjectOrExit(path, flag) {
@@ -113,7 +116,7 @@ function readJsonObjectOrExit(path, flag) {
113
116
  console.error(
114
117
  `Error: ${flag} file '${path}' must contain a JSON object, not ${Array.isArray(parsed) ? "an array" : typeof parsed}.`
115
118
  );
116
- process.exit(1);
119
+ process2.exit(1);
117
120
  }
118
121
  return parsed;
119
122
  }
@@ -159,7 +162,7 @@ async function handleEmulationGet(argv, apiUrl, token) {
159
162
  const json = hasFlag(argv, "--json");
160
163
  if (!templateId) {
161
164
  console.error("Error: template ID required. Usage: canary sandbox emulation get <template-id>");
162
- process.exit(1);
165
+ process2.exit(1);
163
166
  }
164
167
  const { config, catalog } = await fetchEmulation(apiUrl, token, templateId);
165
168
  const available = getAllPlugins().map((plugin) => ({ id: plugin.id, name: plugin.name }));
@@ -208,7 +211,7 @@ async function setEmulationEnabled(argv, apiUrl, token, enable) {
208
211
  console.error(
209
212
  `Error: template ID required. Usage: canary sandbox emulation ${verb} <template-id> [pluginId]`
210
213
  );
211
- process.exit(1);
214
+ process2.exit(1);
212
215
  }
213
216
  const { config } = await fetchEmulation(apiUrl, token, templateId);
214
217
  let next;
@@ -219,7 +222,7 @@ async function setEmulationEnabled(argv, apiUrl, token, enable) {
219
222
  const exists = (config.plugins ?? []).some((plugin) => plugin.pluginId === pluginId);
220
223
  if (!enable && !exists) {
221
224
  console.error(`Error: plugin '${pluginId}' is not configured on this template.`);
222
- process.exit(1);
225
+ process2.exit(1);
223
226
  }
224
227
  next = withPluginEnabled(config, pluginId, enable);
225
228
  }
@@ -235,7 +238,7 @@ async function handleEmulationSet(argv, apiUrl, token) {
235
238
  console.error(
236
239
  "Usage: canary sandbox emulation set <template-id> <pluginId> [--config <file>] [--fixture <file>] [--env-mapping <file>]"
237
240
  );
238
- process.exit(1);
241
+ process2.exit(1);
239
242
  }
240
243
  assertKnownPlugin(pluginId);
241
244
  const configPath = getArgValue(argv, "--config");
@@ -243,7 +246,7 @@ async function handleEmulationSet(argv, apiUrl, token) {
243
246
  const envMappingPath = getArgValue(argv, "--env-mapping");
244
247
  if (!configPath && !fixturePath && !envMappingPath) {
245
248
  console.error("Error: provide at least one of --config, --fixture, --env-mapping");
246
- process.exit(1);
249
+ process2.exit(1);
247
250
  }
248
251
  const pluginConfig = configPath ? readJsonObjectOrExit(configPath, "--config") : void 0;
249
252
  const fixture = fixturePath ? readJsonFileOrExit(fixturePath, "--fixture") : void 0;
@@ -268,12 +271,12 @@ async function handleEmulationRemove(argv, apiUrl, token) {
268
271
  console.error(
269
272
  "Error: template ID and pluginId required. Usage: canary sandbox emulation remove <template-id> <pluginId>"
270
273
  );
271
- process.exit(1);
274
+ process2.exit(1);
272
275
  }
273
276
  const { config } = await fetchEmulation(apiUrl, token, templateId);
274
277
  if (!(config.plugins ?? []).some((plugin) => plugin.pluginId === pluginId)) {
275
278
  console.error(`Error: plugin '${pluginId}' is not configured on this template.`);
276
- process.exit(1);
279
+ process2.exit(1);
277
280
  }
278
281
  const next = withoutPlugin(config, pluginId);
279
282
  await patchEmulation(apiUrl, token, templateId, next);
@@ -302,12 +305,12 @@ async function handleEmulation(argv, apiUrl, token) {
302
305
  console.error(
303
306
  `Unknown emulation action: ${action2 ?? "(none)"}. Use get/enable/disable/set/remove.`
304
307
  );
305
- process.exit(1);
308
+ process2.exit(1);
306
309
  }
307
310
  }
308
311
 
309
312
  // src/sandbox-logs.ts
310
- import process2 from "process";
313
+ import process3 from "process";
311
314
  var requestFailureLogsUrl = (apiUrl, token, instanceId) => apiRequest(
312
315
  apiUrl,
313
316
  token,
@@ -342,7 +345,7 @@ async function handleLogs(argv, apiUrl, token) {
342
345
  const json = hasFlag(argv, "--json");
343
346
  if (!instanceId) {
344
347
  console.error("Error: instance ID required. Usage: canary sandbox logs <instance-id>");
345
- process2.exit(1);
348
+ process3.exit(1);
346
349
  }
347
350
  const service2 = getArgValue(argv, "--service");
348
351
  const tail = getArgValue(argv, "--tail") ?? "200";
@@ -352,13 +355,13 @@ async function handleLogs(argv, apiUrl, token) {
352
355
  const bundle = await fetchFailureLogsBundle(apiUrl, token, instanceId);
353
356
  if ("error" in bundle) {
354
357
  console.error(`Error: ${bundle.error}`);
355
- process2.exit(1);
358
+ process3.exit(1);
356
359
  }
357
360
  if (json) {
358
361
  writeRawOutput(JSON.stringify(bundle, null, 2));
359
362
  return;
360
363
  }
361
- process2.stdout.write(bundle.text || "(empty bundle)");
364
+ process3.stdout.write(bundle.text || "(empty bundle)");
362
365
  return;
363
366
  }
364
367
  const res = await apiRequest(
@@ -380,33 +383,33 @@ async function handleLogs(argv, apiUrl, token) {
380
383
  }
381
384
  console.error(`(live logs unavailable: ${res.error}; showing captured failure snapshot)
382
385
  `);
383
- process2.stdout.write(bundle.text || "(empty bundle)");
386
+ process3.stdout.write(bundle.text || "(empty bundle)");
384
387
  return;
385
388
  }
386
389
  console.error(`Error: ${res.error}`);
387
- process2.exit(1);
390
+ process3.exit(1);
388
391
  }
389
392
  if (json) {
390
393
  writeRawOutput(JSON.stringify(res.data, null, 2));
391
394
  return;
392
395
  }
393
- process2.stdout.write(res.data.logs ?? "(no logs)");
396
+ process3.stdout.write(res.data.logs ?? "(no logs)");
394
397
  }
395
398
 
396
399
  // src/sandbox-preview.ts
397
- import process3 from "process";
398
- async function handlePreview(argv, apiUrl, token) {
400
+ import process4 from "process";
401
+ async function handlePreview(argv, apiUrl, token, appSubdomain) {
399
402
  const pr = getArgValue(argv, "--pr");
400
403
  if (!pr) {
401
404
  console.error("Usage: cofactor sandbox preview --application <name|id> --pr <number> [--json]");
402
- process3.exit(1);
405
+ process4.exit(1);
403
406
  }
404
407
  const application = await resolveApplicationId(argv, apiUrl, token);
405
408
  const query = new URLSearchParams({ application, pr });
406
409
  const res = await apiRequest(apiUrl, token, "GET", `/org/sandboxes-v2/previews?${query}`);
407
410
  if (!res.ok) {
408
411
  console.error(`Error: ${res.error}`);
409
- process3.exit(1);
412
+ process4.exit(1);
410
413
  }
411
414
  const preview = res.data;
412
415
  if (hasFlag(argv, "--json")) {
@@ -415,6 +418,7 @@ async function handlePreview(argv, apiUrl, token) {
415
418
  }
416
419
  const lines = [
417
420
  `#${preview.prNumber}: ${preview.status}`,
421
+ ` page ${resolveOrgAppUrl(apiUrl, appSubdomain)}${sandboxPreviewPagePath(preview.applicationId, preview.prNumber)}`,
418
422
  ` commit ${(preview.appliedSha ?? preview.headSha).slice(0, 12)}${preview.appliedSha && preview.appliedSha !== preview.headSha ? ` (building ${preview.headSha.slice(0, 12)})` : ""}`,
419
423
  ...Object.entries(preview.portUrls).map(([name, url]) => ` ${name.padEnd(8)} ${url}`),
420
424
  ...preview.personas.map((p) => ` ${p.name.padEnd(8)} ${p.email} ${p.password}`)
@@ -425,8 +429,8 @@ async function handlePreview(argv, apiUrl, token) {
425
429
  }
426
430
 
427
431
  // src/sandbox-v2.ts
428
- import process4 from "process";
429
- import { readFileSync as readFileSync2 } from "fs";
432
+ import process6 from "process";
433
+ import { readFileSync as readFileSync4 } from "fs";
430
434
 
431
435
  // ../sandbox-helper/src/manifest.ts
432
436
  import { createHash as createHash2, randomBytes } from "crypto";
@@ -1267,1616 +1271,1889 @@ var prepareInputSchema = z2.strictObject({
1267
1271
  */
1268
1272
  sync: z2.strictObject({ key: syncKeySchema })
1269
1273
  });
1274
+ function definitionFile(dir) {
1275
+ if (dir === ".") return DEFINITION_FILE;
1276
+ if (dir === "" || dir.startsWith("/") || dir.endsWith("/") || dir.split("/").includes("..")) {
1277
+ throw new Error(`invalid definition directory "${dir}"`);
1278
+ }
1279
+ return `${dir}/${DEFINITION_FILE}`;
1280
+ }
1270
1281
 
1271
- // src/sandbox-v2.ts
1272
- function pad2(s, n) {
1273
- return s.padEnd(n);
1282
+ // src/sandbox-from-local.ts
1283
+ import { spawnSync as spawnSync2 } from "child_process";
1284
+ import { createHash as createHash3, randomUUID } from "crypto";
1285
+ import {
1286
+ existsSync as existsSync2,
1287
+ mkdirSync,
1288
+ readFileSync as readFileSync3,
1289
+ readdirSync,
1290
+ renameSync,
1291
+ rmSync as rmSync2,
1292
+ writeFileSync
1293
+ } from "fs";
1294
+ import { relative as relative2, resolve as resolve2, join as join2 } from "path";
1295
+ import { z as z4 } from "zod";
1296
+
1297
+ // src/sandbox-sync.ts
1298
+ import { spawnSync } from "child_process";
1299
+ import process5 from "process";
1300
+ import { copyFileSync, existsSync, lstatSync, mkdtempSync, readFileSync as readFileSync2, realpathSync, rmSync } from "fs";
1301
+ import { tmpdir } from "os";
1302
+ import { join, relative, resolve, sep } from "path";
1303
+
1304
+ // ../sandbox-helper/src/image-archive.ts
1305
+ import { z as z3 } from "zod";
1306
+ var MAX_BLOB_BYTES = 5 * 1024 ** 3;
1307
+ var ImageBlob = z3.object({
1308
+ digest: z3.string().regex(/^sha256:[0-9a-f]{64}$/),
1309
+ size: z3.number().int().nonnegative()
1310
+ });
1311
+ var ImageBlobs = z3.object({
1312
+ config: ImageBlob,
1313
+ // Docker allows at most 127 layers; each costs a storage lookup per push.
1314
+ layers: z3.array(ImageBlob).min(1).max(128)
1315
+ });
1316
+ function withSandboxLayers(team, added, created) {
1317
+ return {
1318
+ ...team,
1319
+ rootfs: { ...team.rootfs, diff_ids: [...team.rootfs.diff_ids, ...added.map((layer) => layer.digest)] },
1320
+ history: [...team.history ?? [], ...added.map(() => ({ created, created_by: "cofactor sandbox push" }))],
1321
+ config: { ...team.config, Entrypoint: ["/usr/local/bin/cofactor-sandbox", "boot"], Cmd: [] }
1322
+ };
1274
1323
  }
1275
- function sleep(ms) {
1276
- return new Promise((resolve3) => setTimeout(resolve3, ms));
1324
+ var BLOCK = 512;
1325
+ var encoder = new TextEncoder();
1326
+ var decoder = new TextDecoder();
1327
+ function tarHeader(name, size, mode = 420) {
1328
+ if (encoder.encode(name).length > 100) throw new Error(`tar entry name too long: ${name}`);
1329
+ const header = new Uint8Array(BLOCK);
1330
+ const put = (offset, length, value) => header.set(encoder.encode(value).slice(0, length), offset);
1331
+ const octal = (offset, length, value) => put(offset, length, `${value.toString(8).padStart(length - 1, "0")}\0`);
1332
+ put(0, 100, name);
1333
+ octal(100, 8, mode);
1334
+ octal(108, 8, 0);
1335
+ octal(116, 8, 0);
1336
+ octal(124, 12, size);
1337
+ octal(136, 12, 0);
1338
+ put(148, 8, " ");
1339
+ put(156, 1, "0");
1340
+ put(257, 6, "ustar\0");
1341
+ put(263, 2, "00");
1342
+ const checksum = header.reduce((sum, byte) => sum + byte, 0);
1343
+ put(148, 8, `${checksum.toString(8).padStart(6, "0")}\0 `);
1344
+ return header;
1277
1345
  }
1278
- function indentLines(text, indent) {
1279
- return text.split("\n").map((line) => `${indent}${line}`).join("\n");
1346
+ function tarPadding(size) {
1347
+ return new Uint8Array((BLOCK - size % BLOCK) % BLOCK);
1280
1348
  }
1281
- function formatActionSignature(a) {
1282
- const inputs = Object.keys(a.input).join(", ");
1283
- const outputs = Object.keys(a.output).join(", ");
1284
- return `${a.slug}(${inputs})${outputs ? ` \u2192 ${outputs}` : ""}`;
1349
+ var TAR_END = new Uint8Array(BLOCK * 2);
1350
+ function tarOf(files) {
1351
+ const parts = files.flatMap((file) => [tarHeader(file.name, file.data.length, file.mode), file.data, tarPadding(file.data.length)]);
1352
+ parts.push(TAR_END);
1353
+ const out = new Uint8Array(parts.reduce((total, part) => total + part.length, 0));
1354
+ let at = 0;
1355
+ for (const part of parts) {
1356
+ out.set(part, at);
1357
+ at += part.length;
1358
+ }
1359
+ return out;
1285
1360
  }
1286
- function jsonSchemaPropTypes(schema) {
1287
- const properties = schema.properties ?? {};
1288
- return Object.fromEntries(
1289
- Object.entries(properties).map(([key, prop]) => [key, typeof prop.type === "string" ? prop.type : "string"])
1290
- );
1361
+ var field = (block, offset, length) => decoder.decode(block.subarray(offset, offset + length)).replace(/\0.*$/s, "");
1362
+ async function tarEntries(read) {
1363
+ const entries = [];
1364
+ let longName = null;
1365
+ for (let offset = 0; ; ) {
1366
+ const block = await read(offset, BLOCK);
1367
+ if (block.length < BLOCK) throw new Error(`the tar ends at byte ${offset + block.length}, before its end-of-archive block; it is truncated`);
1368
+ if (block.every((byte) => byte === 0)) return entries;
1369
+ const size = Number.parseInt(field(block, 124, 12).trim() || "0", 8);
1370
+ if (!Number.isSafeInteger(size) || size < 0) throw new Error(`tar entry at ${offset} has an invalid size`);
1371
+ const type = field(block, 156, 1) || "0";
1372
+ const prefix = field(block, 345, 155);
1373
+ const name = longName ?? (prefix ? `${prefix}/${field(block, 0, 100)}` : field(block, 0, 100));
1374
+ const data = offset + BLOCK;
1375
+ longName = null;
1376
+ if (type === "x" || type === "L") {
1377
+ const text = decoder.decode(await read(data, size)).replace(/\0+$/, "");
1378
+ longName = type === "L" ? text : /(?:^|\n)\d+ path=([^\n]*)\n/.exec(text)?.[1] ?? null;
1379
+ } else if (type === "0") {
1380
+ entries.push({ name, offset: data, size });
1381
+ }
1382
+ offset = data + size + tarPadding(size).length;
1383
+ }
1291
1384
  }
1292
- function formatOperationSignature(op) {
1293
- const inputs = Object.keys(jsonSchemaPropTypes(op.inputSchema)).join(", ");
1294
- const outputs = Object.keys(jsonSchemaPropTypes(op.outputSchema)).join(", ");
1295
- return `${op.name}(${inputs})${outputs ? ` \u2192 ${outputs}` : ""}`;
1385
+
1386
+ // src/sandbox-sync.ts
1387
+ var SyncError = class extends Error {
1388
+ };
1389
+ function git(args, options) {
1390
+ const result = spawnSync("git", args, {
1391
+ cwd: options.cwd,
1392
+ env: options.env ?? process5.env,
1393
+ input: options.input,
1394
+ encoding: "utf8",
1395
+ maxBuffer: 1 << 30
1396
+ // Node defaults to 1 MiB; this repo's `ls-files -s -z` alone is 1.2 MB
1397
+ });
1398
+ if (result.error) throw new SyncError(`git ${args[0]} failed: ${result.error.message}`);
1399
+ if (!(options.okCodes ?? [0]).includes(result.status ?? -1)) {
1400
+ throw new SyncError(`git ${args[0]} failed (${result.signal ?? result.status}): ${result.stderr.trim()}`);
1401
+ }
1402
+ return result.stdout;
1296
1403
  }
1297
- function exitOnRefusal(res) {
1298
- if (res.ok) return;
1299
- console.error(`Error: ${res.error}`);
1300
- if (res.code === "SANDBOX_V2_DEFINITION_INVALID") printIssues(res.issues, " ");
1301
- process4.exit(1);
1404
+ var toPosix = (path) => path.split(sep).join("/");
1405
+ function repositoryOf(dir) {
1406
+ const top = git(["rev-parse", "--show-toplevel"], { cwd: dir }).trim();
1407
+ const gitDir = git(["rev-parse", "--absolute-git-dir"], { cwd: dir }).trim();
1408
+ return { top: realpathSync(top), gitDir };
1302
1409
  }
1303
- function printIssues(issues, indent) {
1304
- for (const issue of issues ?? []) {
1305
- console.log(`${indent}${issue.path || "(root)"}: ${issue.message}`);
1410
+ function indexEntries(output2, strip, top) {
1411
+ const files = [];
1412
+ const skipped = [];
1413
+ for (const entry of output2.split("\0")) {
1414
+ if (entry === "") continue;
1415
+ const match = /^(\d{6}) ([0-9a-f]+) \d\t(.*)$/s.exec(entry);
1416
+ if (!match) throw new SyncError(`unexpected git ls-files line: ${entry}`);
1417
+ const [, mode, blob, repoPath] = match;
1418
+ const path = strip === "" ? repoPath : repoPath.slice(strip.length + 1);
1419
+ const onDisk = mode === "100644" || mode === "100755" ? lstatOrNull(join(top, repoPath)) : null;
1420
+ if (onDisk?.isFile()) files.push({ path, blob, mode: mode === "100755" ? 493 : 420, size: onDisk.size });
1421
+ else skipped.push(path);
1306
1422
  }
1423
+ return { files, skipped };
1307
1424
  }
1308
- function shortVersion(version) {
1309
- return /^[0-9a-f]{40}$/.test(version) ? version.slice(0, 12) : version;
1425
+ function workingTree(root) {
1426
+ const { top, gitDir } = repositoryOf(root);
1427
+ const prefix = toPosix(relative(top, realpathSync(root)));
1428
+ const temp = mkdtempSync(join(tmpdir(), "cofactor-sync-"));
1429
+ const cleanup = () => rmSync(temp, { recursive: true, force: true });
1430
+ try {
1431
+ const index = join(temp, "index");
1432
+ const env = { ...process5.env, GIT_INDEX_FILE: index };
1433
+ const userIndex = join(gitDir, "index");
1434
+ if (existsSync(userIndex)) copyFileSync(userIndex, index);
1435
+ else if (git(["rev-parse", "--verify", "-q", "HEAD"], { cwd: top, okCodes: [0, 1] }).trim() !== "") git(["read-tree", "HEAD"], { cwd: top, env });
1436
+ const pathspec = prefix === "" ? "." : `:(literal)${prefix}`;
1437
+ git(["add", "-A", "--", pathspec], { cwd: top, env });
1438
+ const entries = indexEntries(git(["ls-files", "-s", "-z", "--", pathspec], { cwd: top, env }), prefix, top);
1439
+ const files = withRawBlobs(entries.files, top, prefix);
1440
+ const skipped = entries.skipped;
1441
+ const tree = git(prefix === "" ? ["write-tree"] : ["write-tree", `--prefix=${prefix}/`], { cwd: top, env }).trim();
1442
+ const untracked = new Set(
1443
+ git(["ls-files", "--others", "--exclude-standard", "-z", "--", pathspec], { cwd: top }).split("\0").filter(Boolean).map((path) => prefix === "" ? path : path.slice(prefix.length + 1))
1444
+ );
1445
+ return { files, skipped, untracked, tree, repo: top, prefix, env, cleanup };
1446
+ } catch (error) {
1447
+ cleanup();
1448
+ throw error;
1449
+ }
1310
1450
  }
1311
- function printApplicationHuman(app) {
1312
- console.log(
1313
- app.delivery === "github" ? `Delivered from GitHub: ${app.repo}${app.definitionDir && app.definitionDir !== "." ? `:${app.definitionDir}` : ""}` : "Delivered as pushed images"
1314
- );
1315
- console.log(` version ${app.version ? shortVersion(app.version) : "(none yet: run `cofactor sandbox push`)"}`);
1316
- if (app.pushes && app.pushes.length > 0) {
1317
- console.log(" pushes:");
1318
- for (const push2 of app.pushes) {
1319
- const tags = push2.tags.length > 0 ? ` [${push2.tags.join(", ")}]` : "";
1320
- console.log(` ${push2.id} ${pad2(push2.status, 9)} ${push2.createdAt}${tags}${push2.error ? `
1321
- ${push2.error}` : ""}`);
1322
- }
1451
+ function buildOutput(dir, to) {
1452
+ const { gitDir } = repositoryOf(dir);
1453
+ const temp = mkdtempSync(join(tmpdir(), "cofactor-sync-"));
1454
+ try {
1455
+ const env = { ...process5.env, GIT_INDEX_FILE: join(temp, "index"), GIT_DIR: gitDir, GIT_WORK_TREE: realpathSync(dir) };
1456
+ git(["add", "-A", "-f", "--", "."], { cwd: dir, env });
1457
+ const entries = indexEntries(git(["ls-files", "-s", "-z"], { cwd: dir, env }), "", realpathSync(dir));
1458
+ const files = withRawBlobs(entries.files, realpathSync(dir), "", env);
1459
+ const skipped = entries.skipped;
1460
+ const tree = git(["write-tree"], { cwd: dir, env }).trim();
1461
+ return { files: files.map((file) => ({ ...file, path: `${to}/${file.path}` })), skipped: skipped.map((path) => `${to}/${path}`), tree };
1462
+ } finally {
1463
+ rmSync(temp, { recursive: true, force: true });
1323
1464
  }
1324
- if (app.sandboxEnvironments.length > 0) {
1325
- console.log(" sandbox environments:");
1326
- for (const env of app.sandboxEnvironments) console.log(` ${pad2(env.environment, 16)} ${env.name}`);
1465
+ }
1466
+ var FILTER_ATTRIBUTES = ["filter", "text", "eol", "ident", "working-tree-encoding"];
1467
+ function withRawBlobs(files, top, prefix, env) {
1468
+ if (files.length === 0) return files;
1469
+ const options = { cwd: top, ...env ? { env } : {} };
1470
+ const repoPaths = files.map((file) => prefix === "" ? file.path : `${prefix}/${file.path}`);
1471
+ const autocrlf = git(["config", "--get", "core.autocrlf"], { ...options, okCodes: [0, 1] }).trim();
1472
+ const converts = autocrlf === "input" || autocrlf !== "" && git(["config", "--get", "--type=bool", "core.autocrlf"], options).trim() === "true";
1473
+ let filtered;
1474
+ if (converts) {
1475
+ filtered = new Set(repoPaths);
1476
+ } else {
1477
+ const out = git(["check-attr", "-z", "--stdin", ...FILTER_ATTRIBUTES], { ...options, input: repoPaths.map((path) => `${path}\0`).join("") }).split("\0");
1478
+ filtered = /* @__PURE__ */ new Set();
1479
+ for (let i = 0; i + 2 < out.length; i += 3) if (out[i + 2] !== "unspecified") filtered.add(out[i]);
1327
1480
  }
1328
- if (app.heads && app.heads.length > 0) {
1329
- console.log(" heads (what flow runs and unpinned claims use; a warm environment switches after its warm-up; update with `cofactor sandbox refresh`):");
1330
- for (const head of app.heads) {
1331
- const version = head.version ? shortVersion(head.version) : "(none)";
1332
- const built = head.builtAt ? `, built ${head.builtAt}` : "";
1333
- const behind = head.behindMain && head.mainHead ? ` main has moved: now ${shortVersion(head.mainHead)}` : "";
1334
- console.log(` ${pad2(head.environment, 16)} ${version} (${head.status}${built})${behind}`);
1335
- if (head.error) console.log(` ${head.error}`);
1336
- }
1481
+ return files.map(
1482
+ (file, i) => filtered.has(repoPaths[i]) ? { ...file, blob: gitBlobId(readFileSync2(join(top, repoPaths[i]))) } : file
1483
+ );
1484
+ }
1485
+ function lstatOrNull(path) {
1486
+ try {
1487
+ return lstatSync(path);
1488
+ } catch (error) {
1489
+ if (error.code === "ENOENT") return null;
1490
+ throw error;
1337
1491
  }
1338
- const def = app.definition;
1339
- if (!def) return;
1340
- console.log(" Environments:");
1341
- for (const env of def.environments) {
1342
- console.log(` ${env.name}${env.extends ? ` (extends ${env.extends})` : ""}`);
1343
- console.log(indentLines(env.description, " "));
1344
- if (env.seeds.length > 0) {
1345
- console.log(" seeds:");
1346
- for (const step of env.seeds) console.log(` ${step.id}: ${step.action}`);
1492
+ }
1493
+ function chunked(items, fn) {
1494
+ for (let i = 0; i < items.length; i += 200) fn(items.slice(i, i + 200));
1495
+ }
1496
+ function ignoredPaths(repo, env, paths) {
1497
+ if (paths.length === 0) return /* @__PURE__ */ new Set();
1498
+ const out = git(["check-ignore", "-z", "--stdin"], { cwd: repo, env, input: paths.map((p) => `${p}\0`).join(""), okCodes: [0, 1] });
1499
+ return new Set(out.split("\0").filter(Boolean));
1500
+ }
1501
+ function everTracked(repo, pathspecs) {
1502
+ const seen = /* @__PURE__ */ new Set();
1503
+ if (pathspecs.length === 0) return seen;
1504
+ if (git(["rev-parse", "--verify", "-q", "HEAD"], { cwd: repo, okCodes: [0, 1] }).trim() === "") return seen;
1505
+ chunked(pathspecs, (chunk) => {
1506
+ const out = git(["log", "--all", "--format=", "--name-only", "-z", "--", ...chunk.map((p) => `:(literal)${p}`)], { cwd: repo });
1507
+ for (const path of out.split("\0")) {
1508
+ const trimmed = path.replace(/^\n+/, "");
1509
+ if (trimmed) seen.add(trimmed);
1347
1510
  }
1348
- if (env.envKeys.length > 0) console.log(` env: ${env.envKeys.join(", ")}`);
1349
- }
1350
- if (def.actions.length > 0) {
1351
- console.log(" Actions:");
1352
- for (const a of def.actions) {
1353
- console.log(` ${formatActionSignature(a)}`);
1354
- console.log(` ${a.description}`);
1511
+ });
1512
+ return seen;
1513
+ }
1514
+ async function chooseDeletions(params) {
1515
+ const repoPath = (path) => params.prefix === "" ? path : `${params.prefix}/${path}`;
1516
+ const ignored = ignoredPaths(params.repo, params.env, params.copyOnly.map((entry) => repoPath(entry.path)));
1517
+ const candidates = params.copyOnly.filter((entry) => !ignored.has(repoPath(entry.path)));
1518
+ const history = everTracked(params.repo, candidates.map((entry) => repoPath(entry.path)));
1519
+ const kept = params.copyOnly.filter((entry) => ignored.has(repoPath(entry.path))).map((entry) => ({ ...entry, why: "ignored here" }));
1520
+ const remove = [];
1521
+ for (const entry of candidates) {
1522
+ if (!entry.path.endsWith("/")) {
1523
+ if (history.has(repoPath(entry.path))) remove.push(entry.path);
1524
+ else kept.push({ ...entry, why: "never tracked here" });
1525
+ continue;
1355
1526
  }
1527
+ const tracked = [...history].some((path) => path.startsWith(repoPath(entry.path)));
1528
+ const listed = tracked ? await params.listDir(entry.path.slice(0, -1)) : [];
1529
+ const ignoredInside = ignoredPaths(params.repo, params.env, listed.map(repoPath));
1530
+ const files = listed.filter((path) => !ignoredInside.has(repoPath(path)) && history.has(repoPath(path)));
1531
+ remove.push(...files);
1532
+ if (files.length < entry.files) kept.push({ path: entry.path, files: entry.files - files.length, why: "never tracked here" });
1356
1533
  }
1357
- if (def.operations.length > 0) {
1358
- console.log(" Operations:");
1359
- for (const op of def.operations) {
1360
- console.log(` ${formatOperationSignature(op)}`);
1361
- console.log(` ${op.description}`);
1534
+ return { delete: remove.sort(), kept: kept.sort((a, b) => a.path.localeCompare(b.path)) };
1535
+ }
1536
+ function applyBatches(files, remove) {
1537
+ const budget = SYNC_MAX_APPLY_BYTES - 1024 * 1024;
1538
+ const cost = (file) => 512 + Math.ceil(file.bytes.length / 512) * 512 + 100 + file.path.length;
1539
+ const batches = [[]];
1540
+ let size = 0;
1541
+ for (const file of files) {
1542
+ if (cost(file) > budget) throw new SyncError(`${file.path} is ${Math.ceil(file.bytes.length / 1024 / 1024)} MiB; a sync sends files of at most ${budget / 1024 / 1024} MiB`);
1543
+ if (size + cost(file) > budget && batches.at(-1).length > 0) {
1544
+ batches.push([]);
1545
+ size = 0;
1362
1546
  }
1547
+ batches.at(-1).push(file);
1548
+ size += cost(file);
1363
1549
  }
1364
- console.log(` Personas: ${def.personas.join(", ") || "(none)"}`);
1365
- console.log(` Ports: ${def.ports.map((p) => `${p.name} (${p.access})`).join(", ") || "(none)"}`);
1550
+ return batches.map((batch, index) => {
1551
+ const manifest = {
1552
+ files: batch.map(({ path, blob, mode }) => ({ path, blob, mode })),
1553
+ delete: index === 0 ? remove : []
1554
+ };
1555
+ return tarOf([
1556
+ { name: "manifest.json", data: new TextEncoder().encode(JSON.stringify(manifest)) },
1557
+ ...batch.map((file, i) => ({ name: syncApplyEntryName(i), data: file.bytes }))
1558
+ ]);
1559
+ });
1366
1560
  }
1367
- function printSandbox(data) {
1368
- console.log(`Sandbox ${data.id}: ${data.status}${data.expired ? " (expired)" : ""}`);
1369
- console.log(` environment ${data.environment}`);
1370
- console.log(` version ${shortVersion(data.version)}`);
1371
- if (data.error) console.log(` error ${data.error}`);
1372
- if (data.readyMs !== null) {
1373
- const seconds = (ms) => `${(ms / 1e3).toFixed(1)}s`;
1374
- const phases = data.phases ? ` (snapshot ${seconds(data.phases.snapshotMs)}, restore ${seconds(data.phases.restoreMs)}, prepare ${seconds(data.phases.prepareMs)})` : "";
1375
- console.log(` ready in ${seconds(data.readyMs)}${phases}`);
1561
+ async function copyRequest(session, path, init) {
1562
+ let response;
1563
+ try {
1564
+ response = await fetch(`${session.url}${path}`, {
1565
+ method: "POST",
1566
+ headers: { authorization: `Bearer ${session.token}`, "content-type": init.contentType },
1567
+ body: init.body
1568
+ });
1569
+ } catch (error) {
1570
+ throw new SyncError(`could not reach the sandbox for ${path}: ${error instanceof Error ? error.message : String(error)}`);
1376
1571
  }
1377
- if (data.snapshot?.buildPhases) {
1378
- const { buildMs, prepareMs, seeds } = data.snapshot.buildPhases;
1379
- const seedsMs = seeds.reduce((total, step) => total + step.ms, 0);
1380
- console.log(
1381
- ` snapshot built ${data.snapshot.builtAt} (build ${(buildMs / 1e3).toFixed(1)}s, prepare ${(prepareMs / 1e3).toFixed(1)}s, ${seeds.length} seeds ${(seedsMs / 1e3).toFixed(1)}s)`
1382
- );
1383
- for (const step of seeds) console.log(` ${step.id} ${step.action} ${(step.ms / 1e3).toFixed(1)}s`);
1384
- const ran = data.snapshot.buildPhases.build?.steps.filter((step) => step.ran) ?? [];
1385
- if (ran.length > 0) {
1386
- console.log(` build steps: ${ran.map((step) => `${step.name} ${(step.ms / 1e3).toFixed(1)}s`).join(", ")}`);
1572
+ if (!response.ok) {
1573
+ const text = await response.text();
1574
+ let message = text;
1575
+ try {
1576
+ message = JSON.parse(text).error ?? text;
1577
+ } catch {
1387
1578
  }
1579
+ throw new SyncError(`the sandbox refused ${path} (${response.status}): ${message.slice(0, 2e3)}`);
1388
1580
  }
1389
- if (data.leaseExpiresAt) console.log(` expires ${data.leaseExpiresAt}`);
1390
- if (data.synced) {
1391
- writeRawOutput(` synced ${data.synced.buildOutput ? "build output" : "tree"} ${data.synced.tree} at ${data.synced.at}`);
1581
+ return response;
1582
+ }
1583
+ async function plan(session, local, to) {
1584
+ const body = JSON.stringify(to === void 0 ? { local } : { local, to });
1585
+ if (Buffer.byteLength(body) > SYNC_MAX_APPLY_BYTES) {
1586
+ throw new SyncError(
1587
+ `the file list (${local.length} files) is over the ${SYNC_MAX_APPLY_BYTES / 1024 / 1024} MiB a sync can plan in one request; ignore what the sandbox doesn't need (.gitignore)`
1588
+ );
1392
1589
  }
1393
- if (Object.keys(data.portUrls).length > 0) {
1394
- console.log("");
1395
- console.log(" URLs:");
1396
- for (const [name, url] of Object.entries(data.portUrls)) writeRawOutput(` ${pad2(name, 10)} ${url}`);
1590
+ const response = await copyRequest(session, "/sync/plan", { body, contentType: "application/json" });
1591
+ return parseAnswer(syncPlanResponseSchema, await response.text(), "/sync/plan");
1592
+ }
1593
+ function parseAnswer(schema, text, path) {
1594
+ let value;
1595
+ try {
1596
+ value = JSON.parse(text);
1597
+ } catch {
1598
+ throw new SyncError(`the sandbox answered ${path} with something other than JSON: ${text.slice(0, 300)}`);
1397
1599
  }
1398
- if (data.personas.length > 0) {
1399
- console.log("");
1400
- console.log(" Logins:");
1401
- for (const p of data.personas) writeRawOutput(` ${pad2(p.name, 14)} ${p.email} ${p.password}`);
1402
- if (data.nativeLogin !== void 0) {
1403
- console.log(
1404
- data.nativeLogin ? " (native Login nodes sign these in via authentication.run)" : " (no authentication.run: native Login nodes fail with script_not_declared)"
1405
- );
1600
+ const parsed = schema.safeParse(value);
1601
+ if (!parsed.success) throw new SyncError(`the sandbox answered ${path} unexpectedly: ${text.slice(0, 300)}`);
1602
+ return parsed.data;
1603
+ }
1604
+ async function apply(session, batches, build, json) {
1605
+ let written = 0;
1606
+ let deleted = 0;
1607
+ let restarted = [];
1608
+ const steps = [];
1609
+ for (const [index, body] of batches.entries()) {
1610
+ const last = index === batches.length - 1;
1611
+ const response = await copyRequest(session, `/sync/apply${build && last ? "?build=1" : ""}`, { body, contentType: "application/x-tar" });
1612
+ if (!(build && last)) {
1613
+ const result = parseAnswer(syncApplyResultSchema, await response.text(), "/sync/apply");
1614
+ written += result.written;
1615
+ deleted += result.deleted;
1616
+ continue;
1406
1617
  }
1407
- }
1408
- if (data.signInUrl) {
1409
- console.log("");
1410
- writeRawOutput(` Open sign-in: ${data.signInUrl}`);
1411
- }
1412
- const seedIds = Object.keys(data.seedOutputs);
1413
- if (seedIds.length > 0) {
1414
- console.log("");
1415
- console.log(" Seed outputs:");
1416
- for (const id of seedIds) {
1417
- for (const [key, value] of Object.entries(data.seedOutputs[id])) {
1418
- writeRawOutput(` ${id}.${key}: ${JSON.stringify(value)}`);
1618
+ let buffer = "";
1619
+ const decoder2 = new TextDecoder();
1620
+ let finished = false;
1621
+ for await (const chunk of response.body) {
1622
+ buffer += decoder2.decode(chunk, { stream: true });
1623
+ let newline = buffer.indexOf("\n");
1624
+ while (newline >= 0) {
1625
+ const event = parseAnswer(syncBuildEventSchema, buffer.slice(0, newline), "/sync/apply?build=1");
1626
+ buffer = buffer.slice(newline + 1);
1627
+ newline = buffer.indexOf("\n");
1628
+ if (event.type === "applied") {
1629
+ written += event.result.written;
1630
+ deleted += event.result.deleted;
1631
+ } else if (event.type === "output") {
1632
+ if (!json) process5.stderr.write(event.text);
1633
+ } else if (event.type === "step") {
1634
+ steps.push({ name: event.name, ran: event.ran, ms: event.ms });
1635
+ if (!json) process5.stderr.write(` build step ${event.name}: ${event.ran ? `ran in ${(event.ms / 1e3).toFixed(1)} s` : "unchanged"}
1636
+ `);
1637
+ } else if (event.type === "done") {
1638
+ restarted = event.restarted;
1639
+ finished = true;
1640
+ } else if (event.type === "error") {
1641
+ throw new SyncError(`the build failed after the files were synced: ${event.message}`);
1642
+ }
1419
1643
  }
1420
1644
  }
1645
+ if (!finished) throw new SyncError("the build stream ended early; check the copy with `cofactor sandbox exec <id> -- cofactor-sandbox status`");
1421
1646
  }
1422
- if (data.actions.length > 0) {
1423
- console.log("");
1424
- console.log(" Actions:");
1425
- for (const a of data.actions) console.log(` ${formatActionSignature(a)}`);
1426
- }
1427
- if (data.operations.length > 0) {
1428
- console.log("");
1429
- console.log(" Operations:");
1430
- for (const op of data.operations) console.log(` ${formatOperationSignature(op)}`);
1431
- }
1647
+ return { written, deleted, restarted, steps };
1432
1648
  }
1433
- function handleSandboxValidate(argv) {
1434
- const file = argv.find((a) => !a.startsWith("-")) ?? "./cofactor.sandbox.yaml";
1649
+ var SYNC_USAGE = "Usage: cofactor sandbox sync <id> [--build] [--json]\n cofactor sandbox sync <id> <local-dir> --to <path-in-sandbox> [--json]";
1650
+ var LIST_LIMIT = 20;
1651
+ async function handleSandboxSync(positionals, argv, apiUrl, token) {
1652
+ const [id, localDir] = positionals;
1653
+ const to = getArgValue(argv, "--to");
1654
+ const build = hasFlag(argv, "--build");
1435
1655
  const json = hasFlag(argv, "--json");
1436
- const delivery = hasFlag(argv, "--upload") ? "upload" : "github";
1437
- let yamlText;
1438
- try {
1439
- yamlText = readFileSync2(file, "utf-8");
1440
- } catch (error) {
1441
- const message = error instanceof Error ? error.message : String(error);
1442
- if (json) {
1443
- writeRawOutput(JSON.stringify({ ok: false, issues: [{ path: "", message }] }, null, 2));
1444
- } else {
1445
- console.error(`Error reading "${file}": ${message}`);
1446
- }
1447
- process4.exit(1);
1448
- }
1449
- const { definition, issues } = validateDefinition(yamlText, delivery);
1450
- if (json) {
1451
- writeRawOutput(JSON.stringify({ ok: issues.length === 0, issues }, null, 2));
1452
- process4.exit(issues.length === 0 ? 0 : 1);
1656
+ const fail4 = (message) => {
1657
+ console.error(`Error: ${message}`);
1658
+ process5.exit(1);
1659
+ };
1660
+ if (!id) return fail4(`sandbox id required.
1661
+ ${SYNC_USAGE}`);
1662
+ if (localDir === void 0 !== (to === void 0)) return fail4(`a local directory and --to go together.
1663
+ ${SYNC_USAGE}`);
1664
+ if (to !== void 0 && build) return fail4("--build reruns the build of a working-tree sync; build output is already built (`sync <id> <dir> --to <path>`).");
1665
+ if (to !== void 0 && (to === "" || to.startsWith("/") || to.split("/").some((part) => part === "" || part === "." || part === ".."))) {
1666
+ return fail4("--to is a path under the sandbox's sync root, like `static` or `frontend/dist` (no leading `/`, no `..`).");
1453
1667
  }
1454
- if (issues.length > 0) {
1455
- for (const issue of issues) console.error(`${issue.path || "(root)"}: ${issue.message}`);
1456
- process4.exit(1);
1668
+ if (localDir !== void 0 && (!existsSync(localDir) || !lstatSync(localDir).isDirectory())) return fail4(`${localDir} is not a directory`);
1669
+ if (!isInsideRepo(to === void 0 ? process5.cwd() : resolve(localDir))) {
1670
+ return fail4("run this inside a git repository: the files to sync and the synced tree come from git.");
1457
1671
  }
1458
- const environmentCount = Object.keys(definition.environments).length;
1459
- const actionCount = Object.keys(definition.actions).length;
1460
- const operationCount = Object.keys(definition.emulation).reduce(
1461
- (sum, pluginId) => sum + pluginOperations(pluginId).length,
1462
- 0
1672
+ const started = Date.now();
1673
+ const sessionRes = await apiRequest(
1674
+ apiUrl,
1675
+ token,
1676
+ "POST",
1677
+ `/org/sandboxes-v2/sandboxes/${id}/sync-session`
1463
1678
  );
1464
- console.log(`\u2713 ${file} is valid`);
1465
- console.log(` ${environmentCount} environment(s), ${actionCount} action(s), ${operationCount} operation(s)`);
1466
- }
1467
- async function fetchApplicationSandbox(apiUrl, token, applicationId) {
1468
- const res = await apiRequest(apiUrl, token, "GET", `/org/applications/${applicationId}/sandbox`);
1469
- exitOnRefusal(res);
1470
- return res.data;
1471
- }
1472
- var V2_ARGS = {
1473
- envs: { values: ["--application", "--property"], flags: ["--json"], positionals: 0 },
1474
- refresh: { values: ["--application", "--property", "--environment"], flags: ["--wait", "--json"], positionals: 0 },
1475
- connect: { values: ["--application", "--property", "--repo", "--dir"], flags: ["--json"], positionals: 0 },
1476
- push: { values: ["--application", "--property", "--definition", "--tag"], flags: ["--wait", "--json"], positionals: 1 },
1477
- claim: { values: ["--application", "--property", "--environment", "--at", "--timeout"], flags: ["--wait", "--json"], positionals: 0 },
1478
- show: { values: [], flags: ["--json"], positionals: 1 },
1479
- claims: { values: ["--application", "--property"], flags: ["--json"], positionals: 0 },
1480
- run: { values: ["--input", "--json-input"], flags: ["--json"], positionals: 2 },
1481
- exec: { values: ["--timeout"], flags: ["--json"], positionals: 1 },
1482
- sync: { values: ["--to"], flags: ["--build", "--json"], positionals: 2 },
1483
- "verify-env": { values: ["--application", "--property", "--environment"], flags: ["--json"], positionals: 0 },
1484
- destroy: { values: [], flags: [], positionals: 1 },
1485
- secret: { values: ["--application", "--property", "--environment"], flags: ["--json"], positionals: 2 }
1486
- };
1487
- function rejectUnknownSandboxV2Args(command, argv) {
1488
- const spec = V2_ARGS[command];
1489
- if (!spec) return;
1490
- let positionals = 0;
1491
- for (let i = 0; i < argv.length; i++) {
1492
- const arg = argv[i];
1493
- if (spec.values.includes(arg)) {
1494
- i++;
1495
- continue;
1679
+ if (!sessionRes.ok) return fail4(sessionRes.error ?? "could not start a sync");
1680
+ const session = sessionRes.data;
1681
+ try {
1682
+ let local;
1683
+ let decide;
1684
+ let cleanup = () => {
1685
+ };
1686
+ if (to !== void 0) {
1687
+ const dir = resolve(localDir);
1688
+ const output2 = buildOutput(dir, to);
1689
+ local = { ...output2, untracked: /* @__PURE__ */ new Set(), root: dir };
1690
+ decide = async (planned) => ({ delete: planned.copyOnly.map((entry) => entry.path), kept: [] });
1691
+ } else {
1692
+ const root = session.delivery === "github" ? repositoryOf(process5.cwd()).top : process5.cwd();
1693
+ if (session.delivery === "upload" && !existsSync(join(root, "cofactor.sandbox.yaml"))) {
1694
+ return fail4("run this from the directory holding cofactor.sandbox.yaml: a pushed image's WORKDIR mirrors it.");
1695
+ }
1696
+ const tree = workingTree(root);
1697
+ cleanup = tree.cleanup;
1698
+ local = { ...tree, root };
1699
+ decide = (planned) => chooseDeletions({
1700
+ copyOnly: planned.copyOnly,
1701
+ repo: tree.repo,
1702
+ prefix: tree.prefix,
1703
+ env: tree.env,
1704
+ listDir: async (dir) => (await plan(session, [], dir)).copyOnly.map((entry) => entry.path)
1705
+ });
1496
1706
  }
1497
- if (["--input", "--tag"].some((value) => spec.values.includes(value) && arg.startsWith(`${value}=`))) continue;
1498
- if (spec.flags.includes(arg) || arg === "--debug") continue;
1499
- if (!arg.startsWith("-") && ++positionals <= spec.positionals) continue;
1500
- if (command === "secret" && !arg.startsWith("-")) {
1501
- console.error(
1502
- "Error: `cofactor sandbox secret` takes no value argument: pipe the value on stdin. If you typed a real key on the command line, rotate it."
1707
+ try {
1708
+ const planStarted = Date.now();
1709
+ const planned = await plan(session, local.files, to);
1710
+ const skipped = new Set(local.skipped.flatMap((path) => [path, `${path}/`]));
1711
+ const { delete: remove, kept } = await decide({ ...planned, copyOnly: planned.copyOnly.filter((entry) => !skipped.has(entry.path)) });
1712
+ const planMs = Date.now() - planStarted;
1713
+ const need = new Set(planned.need);
1714
+ const sending = local.files.filter((file) => need.has(file.path));
1715
+ if (!json) {
1716
+ writeRawOutput(`Syncing ${local.root} into sandbox ${id} (${session.delivery === "github" ? "GitHub" : "pushed image"}, version ${session.version.slice(0, 12)})`);
1717
+ writeRawOutput(` plan: ${sending.length} to write, ${remove.length} to delete, ${local.files.length - sending.length} unchanged (${(planMs / 1e3).toFixed(1)} s)`);
1718
+ const untracked = sending.filter((file) => local.untracked.has(file.path));
1719
+ const tracked = sending.filter((file) => !local.untracked.has(file.path));
1720
+ if (sending.length > 0) writeRawOutput(" write:");
1721
+ for (const file of tracked.slice(0, LIST_LIMIT)) writeRawOutput(` ${file.path}`);
1722
+ if (tracked.length > LIST_LIMIT) writeRawOutput(` ...and ${tracked.length - LIST_LIMIT} more`);
1723
+ for (const file of untracked) writeRawOutput(` ${file.path} (untracked)`);
1724
+ if (remove.length > 0) writeRawOutput(" delete:");
1725
+ for (const path of remove) writeRawOutput(` ${path}`);
1726
+ if (kept.length > 0) {
1727
+ writeRawOutput(" kept (only in the sandbox):");
1728
+ for (const entry of kept.slice(0, LIST_LIMIT)) {
1729
+ writeRawOutput(` ${entry.path}${entry.files > 1 ? ` (${entry.files} files)` : ""}, ${entry.why}`);
1730
+ }
1731
+ if (kept.length > LIST_LIMIT) writeRawOutput(` ...and ${kept.length - LIST_LIMIT} more`);
1732
+ }
1733
+ if (local.skipped.length > 0) writeRawOutput(` not synced (symlinks and submodules): ${local.skipped.join(", ")}`);
1734
+ }
1735
+ const applyStarted = Date.now();
1736
+ const batches = sending.length === 0 && remove.length === 0 && !build ? [] : applyBatches(
1737
+ sending.map((file) => ({ ...file, bytes: readFileSync2(join(local.root, to === void 0 ? file.path : file.path.slice(to.length + 1))) })),
1738
+ remove
1503
1739
  );
1504
- process4.exit(1);
1740
+ const applied = await apply(session, batches, build, json);
1741
+ const applyMs = Date.now() - applyStarted;
1742
+ const ms = Date.now() - started;
1743
+ const recorded = await apiRequest(apiUrl, token, "POST", `/org/sandboxes-v2/sandboxes/${id}/syncs`, {
1744
+ tree: local.tree,
1745
+ buildOutput: to !== void 0,
1746
+ written: applied.written,
1747
+ deleted: applied.deleted,
1748
+ ms
1749
+ });
1750
+ if (!recorded.ok) throw new SyncError(`the files were synced, but recording the sync failed: ${recorded.error}`);
1751
+ if (json) {
1752
+ writeRawOutput(
1753
+ JSON.stringify(
1754
+ {
1755
+ id,
1756
+ tree: local.tree,
1757
+ buildOutput: to !== void 0,
1758
+ written: sending.map((file) => file.path),
1759
+ deleted: remove,
1760
+ kept,
1761
+ build: build ? { steps: applied.steps, restarted: applied.restarted } : null,
1762
+ ms: { total: ms, plan: planMs, apply: applyMs }
1763
+ },
1764
+ null,
1765
+ 2
1766
+ )
1767
+ );
1768
+ return;
1769
+ }
1770
+ writeRawOutput(` applied: ${applied.written} written, ${applied.deleted} deleted (${(applyMs / 1e3).toFixed(1)} s)`);
1771
+ if (build) writeRawOutput(` restarted: ${applied.restarted.join(", ") || "nothing"}`);
1772
+ writeRawOutput(`Synced ${to === void 0 ? "tree" : "build output"} ${local.tree} in ${(ms / 1e3).toFixed(1)} s.`);
1773
+ if (!build) {
1774
+ writeRawOutput(` Restart what runs it: cofactor sandbox exec ${id} -- cofactor-sandbox restart <service>`);
1775
+ }
1776
+ const shown = await apiRequest(apiUrl, token, "GET", `/org/sandboxes-v2/sandboxes/${id}`);
1777
+ for (const [name, url] of Object.entries(shown.data?.portUrls ?? {})) writeRawOutput(` ${name.padEnd(10)} ${url}`);
1778
+ } finally {
1779
+ cleanup();
1505
1780
  }
1506
- console.error(`Error: \`cofactor sandbox ${command}\` does not take ${JSON.stringify(arg)}. See \`cofactor sandbox --help\`.`);
1507
- process4.exit(1);
1781
+ } catch (error) {
1782
+ if (error instanceof SyncError) return fail4(error.message);
1783
+ throw error;
1508
1784
  }
1509
1785
  }
1510
- function v2Positionals(command, argv) {
1511
- const values = V2_ARGS[command]?.values ?? [];
1512
- const result = [];
1513
- for (let i = 0; i < argv.length; i++) {
1514
- const arg = argv[i];
1515
- if (values.includes(arg)) i++;
1516
- else if (!arg.startsWith("-")) result.push(arg);
1517
- }
1518
- return result;
1786
+ function isInsideRepo(dir) {
1787
+ return git(["rev-parse", "--is-inside-work-tree"], { cwd: dir, okCodes: [0, 128] }).trim() === "true";
1519
1788
  }
1520
- async function handleSandboxEnvs(argv, apiUrl, token) {
1521
- const applicationId = await resolveApplicationId(argv, apiUrl, token);
1522
- const app = await fetchApplicationSandbox(apiUrl, token, applicationId);
1523
- if (hasFlag(argv, "--json")) {
1524
- writeRawOutput(JSON.stringify(app, null, 2));
1525
- return;
1526
- }
1527
- printApplicationHuman(app);
1789
+
1790
+ // src/sandbox-from-local.ts
1791
+ var TTL_MS = 7 * 24 * 3600 * 1e3;
1792
+ var RecordSchema = z4.strictObject({
1793
+ repo: z4.string(),
1794
+ repoName: z4.string(),
1795
+ remoteIdentity: z4.string(),
1796
+ application: z4.string(),
1797
+ applicationId: z4.string().nullable(),
1798
+ claims: z4.array(z4.string()),
1799
+ apiUrl: z4.string(),
1800
+ version: z4.string().regex(/^[0-9a-f]{40}$/),
1801
+ ref: z4.string().regex(/^refs\/cofactor\/wip\/[0-9a-f]{40}$/),
1802
+ updatedAt: z4.number()
1803
+ });
1804
+ function git2(repo, args, env = process.env) {
1805
+ const result = spawnSync2("git", args, {
1806
+ cwd: repo,
1807
+ env,
1808
+ encoding: "utf8",
1809
+ maxBuffer: 64 * 1024 * 1024
1810
+ });
1811
+ if (result.error || result.status !== 0) {
1812
+ const reason = (result.error?.message ?? result.stderr).replace(
1813
+ /https?:\/\/[^\s/@]+(?::[^\s/@]*)?@/g,
1814
+ "https://***@"
1815
+ );
1816
+ throw new Error(`git ${args[0]} failed: ${reason.trim()}`);
1817
+ }
1818
+ return result.stdout.trim();
1819
+ }
1820
+ function remoteIdentity(remote) {
1821
+ if (!/^https?:\/\//.test(remote)) return remote;
1822
+ const url = new URL(remote);
1823
+ url.username = "";
1824
+ url.password = "";
1825
+ return url.href;
1826
+ }
1827
+ var stateDir = () => join2(activeConfigDir(), "sandbox-wip");
1828
+ function save(file, record) {
1829
+ mkdirSync(stateDir(), { recursive: true, mode: 448 });
1830
+ const temp = `${file}.${randomUUID()}.tmp`;
1831
+ writeFileSync(temp, JSON.stringify(record), { mode: 384 });
1832
+ renameSync(temp, file);
1833
+ }
1834
+ function records() {
1835
+ if (!existsSync2(stateDir())) return [];
1836
+ return readdirSync(stateDir()).filter((name) => name.endsWith(".json")).map((name) => {
1837
+ const file = join2(stateDir(), name);
1838
+ return { file, record: RecordSchema.parse(JSON.parse(readFileSync3(file, "utf8"))) };
1839
+ });
1528
1840
  }
1529
- async function handleSandboxConnect(argv, apiUrl, token) {
1530
- const repo = getArgValue(argv, "--repo");
1531
- if (!repo) {
1532
- console.error("Usage: cofactor sandbox connect --application <name|id> --repo <owner/name> [--dir <path>] [--json]");
1533
- process4.exit(1);
1534
- }
1535
- const body = { repo, definitionDir: getArgValue(argv, "--dir") ?? "." };
1536
- const checkCanConnect = async () => {
1537
- exitOnRefusal(await apiRequest(apiUrl, token, "POST", "/org/sandboxes-v2/connect-check", body));
1538
- };
1539
- const applicationId = await resolveOrCreateSandboxApplication(argv, apiUrl, token, checkCanConnect);
1540
- const res = await apiRequest(apiUrl, token, "PUT", `/org/applications/${applicationId}/sandbox`, body);
1541
- exitOnRefusal(res);
1542
- if (hasFlag(argv, "--json")) {
1543
- writeRawOutput(JSON.stringify(res.data, null, 2));
1544
- return;
1841
+ function publishLocalSource(argv, apiUrl) {
1842
+ const application = getArgValue(argv, "--application") ?? getArgValue(argv, "--property");
1843
+ if (!application) throw new Error("--from-local requires --application <name|id>.");
1844
+ const { top } = repositoryOf(process.cwd());
1845
+ const remote = git2(top, ["remote", "get-url", "--push", "origin"]);
1846
+ const inferred = /^(?:git@github\.com:|ssh:\/\/git@github\.com\/|https:\/\/github\.com\/)([^/]+\/[^/]+?)(?:\.git)?$/.exec(
1847
+ remote
1848
+ )?.[1];
1849
+ const repoName = getArgValue(argv, "--repo") ?? inferred;
1850
+ if (!repoName)
1851
+ throw new Error("Cannot infer a GitHub repository from origin; pass --repo <owner/name>.");
1852
+ if (inferred && inferred.toLowerCase() !== repoName.toLowerCase())
1853
+ throw new Error("--repo must match the origin push repository.");
1854
+ const directory = getArgValue(argv, "--dir") ?? relative2(top, process.cwd());
1855
+ const definitionDir = relative2(top, resolve2(top, directory)).split("\\").join("/") || ".";
1856
+ if (definitionDir === ".." || definitionDir.startsWith("../"))
1857
+ throw new Error("--dir must be inside the repository.");
1858
+ const tree = workingTree(top);
1859
+ try {
1860
+ const yaml = git2(top, ["show", `${tree.tree}:${definitionFile(definitionDir)}`], tree.env);
1861
+ const { definition, issues } = validateDefinition(yaml, "github");
1862
+ if (!definition || issues.length)
1863
+ throw new Error(
1864
+ `Invalid sandbox definition:
1865
+ ${issues.map((issue) => `${issue.path}: ${issue.message}`).join("\n")}`
1866
+ );
1867
+ const env = {
1868
+ ...tree.env,
1869
+ GIT_AUTHOR_NAME: "Cofactor",
1870
+ GIT_AUTHOR_EMAIL: "sandbox@example.com",
1871
+ GIT_COMMITTER_NAME: "Cofactor",
1872
+ GIT_COMMITTER_EMAIL: "sandbox@example.com",
1873
+ GIT_AUTHOR_DATE: "2000-01-01T00:00:00Z",
1874
+ GIT_COMMITTER_DATE: "2000-01-01T00:00:00Z"
1875
+ };
1876
+ const version = git2(
1877
+ top,
1878
+ ["commit-tree", "--no-gpg-sign", tree.tree, "-m", "Cofactor working tree"],
1879
+ env
1880
+ );
1881
+ const key = createHash3("sha256").update(JSON.stringify([top, repoName, application, apiUrl, version])).digest("hex");
1882
+ const file = join2(stateDir(), `${key}.json`);
1883
+ const previous = existsSync2(file) ? RecordSchema.parse(JSON.parse(readFileSync3(file, "utf8"))) : null;
1884
+ const record = {
1885
+ repo: top,
1886
+ repoName,
1887
+ remoteIdentity: remoteIdentity(remote),
1888
+ application,
1889
+ applicationId: previous?.applicationId ?? null,
1890
+ claims: previous?.claims ?? [],
1891
+ apiUrl,
1892
+ version,
1893
+ ref: `refs/cofactor/wip/${tree.tree}`,
1894
+ updatedAt: Date.now()
1895
+ };
1896
+ save(file, record);
1897
+ git2(top, ["push", "--porcelain", "origin", `${version}:${record.ref}`]);
1898
+ return { record, file, definitionDir };
1899
+ } finally {
1900
+ tree.cleanup();
1545
1901
  }
1546
- printApplicationHuman(res.data);
1547
1902
  }
1548
- var CLAIM_POLL_INTERVAL_MS = 3e3;
1549
- var CLAIM_TIMEOUT_SECONDS = 2 * 3600;
1550
- var CLAIM_POLL_GRACE_MS = 2 * 60 * 1e3;
1551
- async function pollSandboxUntilReady(apiUrl, token, id, timeoutSeconds) {
1552
- const deadline = Date.now() + timeoutSeconds * 1e3 + CLAIM_POLL_GRACE_MS;
1553
- let toldUpgrading = false;
1554
- while (Date.now() < deadline) {
1555
- const res = await apiRequest(apiUrl, token, "GET", `/org/sandboxes-v2/sandboxes/${id}`);
1556
- if (!res.ok) return { error: res.error ?? "unknown error" };
1557
- if (res.data.status === "ready" || res.data.status === "failed") return { data: res.data };
1558
- if (res.data.upgrading && !toldUpgrading) {
1559
- toldUpgrading = true;
1560
- console.error(" updating this push to the current sandbox platform (once; the claim continues after)...");
1561
- }
1562
- await sleep(CLAIM_POLL_INTERVAL_MS);
1563
- }
1564
- return { error: `timed out waiting for sandbox ${id} to become ready (${Math.round(timeoutSeconds / 60)} min)` };
1903
+ function rememberLocalApplication(source, applicationId) {
1904
+ source.record.applicationId = applicationId;
1905
+ save(source.file, source.record);
1565
1906
  }
1566
- async function handleSandboxClaim(argv, apiUrl, token) {
1567
- const applicationId = await resolveApplicationId(argv, apiUrl, token);
1568
- const environment2 = getArgValue(argv, "--environment") ?? "default";
1569
- const version = getArgValue(argv, "--at");
1570
- const timeout = parseDurationSeconds(getArgValue(argv, "--timeout"), "--timeout");
1571
- const timeoutSeconds = timeout ?? CLAIM_TIMEOUT_SECONDS;
1572
- const wait = hasFlag(argv, "--wait");
1573
- const json = hasFlag(argv, "--json");
1574
- const body = { applicationId, environment: environment2 };
1575
- if (version) body.version = version;
1576
- if (timeout !== void 0) body.timeoutSeconds = timeout;
1577
- if (wait) body.wait = true;
1578
- const res = await apiRequest(apiUrl, token, "POST", "/org/sandboxes-v2/sandboxes", body);
1579
- if (!res.ok) {
1580
- console.error(`Error: ${res.error}`);
1581
- if (res.code === "SANDBOX_V2_DEFINITION_INVALID") printIssues(res.issues, " ");
1582
- process4.exit(1);
1583
- }
1584
- const id = res.data.id;
1585
- if (!wait) {
1586
- if (json) {
1587
- writeRawOutput(JSON.stringify({ id }, null, 2));
1588
- } else {
1589
- console.log(`Sandbox claiming: ${id}`);
1590
- console.log(` Poll with: cofactor sandbox show ${id}`);
1907
+ function rememberLocalClaim(source, id) {
1908
+ const latest = RecordSchema.parse(JSON.parse(readFileSync3(source.file, "utf8")));
1909
+ source.record.claims = [.../* @__PURE__ */ new Set([...latest.claims, id])];
1910
+ save(source.file, source.record);
1911
+ }
1912
+ function trackedLocalSandbox(id, apiUrl) {
1913
+ const entry = records().find(
1914
+ ({ record }) => record.apiUrl === apiUrl && record.claims.includes(id)
1915
+ );
1916
+ if (!entry || entry.record.applicationId === null) return null;
1917
+ return { applicationId: entry.record.applicationId, version: entry.record.version };
1918
+ }
1919
+ async function cleanupLocalRefs(apiUrl, token, applicationId, application, destroyed) {
1920
+ const candidates = records().filter(
1921
+ ({ record }) => record.apiUrl === apiUrl && (record.applicationId === applicationId || record.applicationId === null && record.application === application) && (destroyed ? record.version === destroyed.version : Date.now() - record.updatedAt >= TTL_MS)
1922
+ );
1923
+ if (!candidates.length) return;
1924
+ const live = /* @__PURE__ */ new Set();
1925
+ let cursor = null;
1926
+ do {
1927
+ const query = new URLSearchParams({ status: "claiming,ready", limit: "100" });
1928
+ if (cursor) query.set("cursor", cursor);
1929
+ const page = await apiRequest(apiUrl, token, "GET", `/org/sandboxes-v2/sandboxes?${query}`);
1930
+ if (!page.ok)
1931
+ throw new Error(page.error ?? "Could not check live claims; wip refs were retained.");
1932
+ for (const row of page.data) if (row.id !== destroyed?.id) live.add(row.version);
1933
+ cursor = page.nextCursor ?? null;
1934
+ } while (cursor);
1935
+ for (const { file, record } of candidates) {
1936
+ if (live.has(record.version)) continue;
1937
+ if (remoteIdentity(git2(record.repo, ["remote", "get-url", "--push", "origin"])) !== record.remoteIdentity) {
1938
+ throw new Error("The origin push repository changed; wip cleanup left it intact.");
1591
1939
  }
1592
- return;
1593
- }
1594
- const settled = "status" in res.data && (res.data.status === "ready" || res.data.status === "failed") ? res.data : null;
1595
- if (!json && !settled) console.log(`Claiming sandbox ${id}...`);
1596
- const waited = settled ? { data: settled } : await pollSandboxUntilReady(apiUrl, token, id, timeoutSeconds);
1597
- if ("error" in waited) {
1598
- console.error(`Error: ${waited.error}`);
1599
- process4.exit(1);
1600
- }
1601
- const { data } = waited;
1602
- if (json) {
1603
- writeRawOutput(JSON.stringify(data, null, 2));
1604
- } else {
1605
- printSandbox(data);
1940
+ const remoteSha = git2(record.repo, ["ls-remote", "--refs", "origin", record.ref]).split(
1941
+ /\s/
1942
+ )[0];
1943
+ if (remoteSha) {
1944
+ if (remoteSha !== record.version)
1945
+ throw new Error("Wip ref changed remotely; cleanup left it intact.");
1946
+ git2(record.repo, [
1947
+ "push",
1948
+ `--force-with-lease=${record.ref}:${record.version}`,
1949
+ "origin",
1950
+ `:${record.ref}`
1951
+ ]);
1952
+ }
1953
+ rmSync2(file);
1606
1954
  }
1607
- if (data.status === "failed") process4.exit(1);
1608
1955
  }
1609
- async function handleSandboxShow(argv, apiUrl, token) {
1610
- const [id] = argv.filter((a) => !a.startsWith("-"));
1611
- if (!id) {
1612
- console.error("Usage: cofactor sandbox show <id> [--json]");
1613
- process4.exit(1);
1614
- }
1615
- const json = hasFlag(argv, "--json");
1616
- const res = await apiRequest(apiUrl, token, "GET", `/org/sandboxes-v2/sandboxes/${id}`);
1617
- if (!res.ok) {
1618
- console.error(`Error: ${res.error}`);
1619
- process4.exit(1);
1620
- }
1621
- if (json) {
1622
- writeRawOutput(JSON.stringify(res.data, null, 2));
1623
- return;
1956
+
1957
+ // src/sandbox-v2.ts
1958
+ function pad2(s, n) {
1959
+ return s.padEnd(n);
1960
+ }
1961
+ function sleep(ms) {
1962
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
1963
+ }
1964
+ function indentLines(text, indent) {
1965
+ return text.split("\n").map((line) => `${indent}${line}`).join("\n");
1966
+ }
1967
+ function formatActionSignature(a) {
1968
+ const inputs = Object.keys(a.input).join(", ");
1969
+ const outputs = Object.keys(a.output).join(", ");
1970
+ return `${a.slug}(${inputs})${outputs ? ` \u2192 ${outputs}` : ""}`;
1971
+ }
1972
+ function jsonSchemaPropTypes(schema) {
1973
+ const properties = schema.properties ?? {};
1974
+ return Object.fromEntries(
1975
+ Object.entries(properties).map(([key, prop]) => [key, typeof prop.type === "string" ? prop.type : "string"])
1976
+ );
1977
+ }
1978
+ function formatOperationSignature(op) {
1979
+ const inputs = Object.keys(jsonSchemaPropTypes(op.inputSchema)).join(", ");
1980
+ const outputs = Object.keys(jsonSchemaPropTypes(op.outputSchema)).join(", ");
1981
+ return `${op.name}(${inputs})${outputs ? ` \u2192 ${outputs}` : ""}`;
1982
+ }
1983
+ function exitOnRefusal(res) {
1984
+ if (res.ok) return;
1985
+ console.error(`Error: ${res.error}`);
1986
+ if (res.code === "SANDBOX_V2_DEFINITION_INVALID") printIssues(res.issues, " ");
1987
+ process6.exit(1);
1988
+ }
1989
+ function printIssues(issues, indent) {
1990
+ for (const issue of issues ?? []) {
1991
+ console.log(`${indent}${issue.path || "(root)"}: ${issue.message}`);
1624
1992
  }
1625
- printSandbox(res.data);
1626
1993
  }
1627
- async function handleSandboxClaims(argv, apiUrl, token) {
1628
- const json = hasFlag(argv, "--json");
1629
- const applicationArg = getArgValue(argv, "--application") ?? getArgValue(argv, "--property");
1630
- const applicationId = applicationArg ? await resolveApplicationId(argv, apiUrl, token) : void 0;
1631
- const claims = [];
1632
- let cursor = null;
1633
- do {
1634
- const query = new URLSearchParams({ mine: "true", status: "claiming,ready", limit: "100" });
1635
- if (applicationId) query.set("applicationId", applicationId);
1636
- if (cursor) query.set("cursor", cursor);
1637
- const res = await apiRequest(
1638
- apiUrl,
1639
- token,
1640
- "GET",
1641
- `/org/sandboxes-v2/sandboxes?${query}`
1642
- );
1643
- if (!res.ok) {
1644
- console.error(`Error: ${res.error ?? "could not list sandboxes"}`);
1645
- process4.exit(1);
1994
+ function shortVersion(version) {
1995
+ return /^[0-9a-f]{40}$/.test(version) ? version.slice(0, 12) : version;
1996
+ }
1997
+ function printApplicationHuman(app) {
1998
+ console.log(
1999
+ app.delivery === "github" ? `Delivered from GitHub: ${app.repo}${app.definitionDir && app.definitionDir !== "." ? `:${app.definitionDir}` : ""}` : "Delivered as pushed images"
2000
+ );
2001
+ console.log(
2002
+ ` version ${app.version ? shortVersion(app.version) : app.delivery === "github" ? "(no definition on the default branch yet; use --at <sha>)" : "(none yet: run `cofactor sandbox push`)"}`
2003
+ );
2004
+ if (app.pushes && app.pushes.length > 0) {
2005
+ console.log(" pushes:");
2006
+ for (const push2 of app.pushes) {
2007
+ const tags = push2.tags.length > 0 ? ` [${push2.tags.join(", ")}]` : "";
2008
+ console.log(` ${push2.id} ${pad2(push2.status, 9)} ${push2.createdAt}${tags}${push2.error ? `
2009
+ ${push2.error}` : ""}`);
1646
2010
  }
1647
- claims.push(...res.data);
1648
- cursor = res.nextCursor ?? null;
1649
- } while (cursor);
1650
- if (json) {
1651
- writeRawOutput(JSON.stringify(claims, null, 2));
1652
- return;
1653
- }
1654
- if (claims.length === 0) {
1655
- console.log("No live claims.");
1656
- return;
1657
2011
  }
1658
- const names = new Map((await fetchProperties(apiUrl, token)).map((p) => [p.id, p.name]));
1659
- console.log(`${pad2("ID", 38)} ${pad2("Application", 24)} ${pad2("Environment", 14)} ${pad2("Status", 10)} ${pad2("Claimed", 26)} Expires`);
1660
- for (const c of claims) {
1661
- console.log(
1662
- `${pad2(c.id, 38)} ${pad2(names.get(c.applicationId) ?? c.applicationId, 24)} ${pad2(c.environment, 14)} ${pad2(c.status, 10)} ${pad2(c.createdAt, 26)} ${c.leaseExpiresAt ?? "-"}`
1663
- );
2012
+ if (app.sandboxEnvironments.length > 0) {
2013
+ console.log(" sandbox environments:");
2014
+ for (const env of app.sandboxEnvironments) console.log(` ${pad2(env.environment, 16)} ${env.name}`);
1664
2015
  }
1665
- console.log("\nTear one down with: cofactor sandbox destroy <id>");
1666
- }
1667
- function coerceInputValue(raw, type) {
1668
- switch (type) {
1669
- case "integer": {
1670
- if (!/^-?\d+$/.test(raw)) throw new Error(`must be an integer, got "${raw}"`);
1671
- return Number(raw);
1672
- }
1673
- case "number": {
1674
- const value = Number(raw);
1675
- if (Number.isNaN(value)) throw new Error(`must be a number, got "${raw}"`);
1676
- return value;
1677
- }
1678
- case "boolean": {
1679
- if (raw === "true") return true;
1680
- if (raw === "false") return false;
1681
- throw new Error(`must be "true" or "false", got "${raw}"`);
2016
+ if (app.heads && app.heads.length > 0) {
2017
+ console.log(" heads (what flow runs and unpinned claims use; a warm environment switches after its warm-up; update with `cofactor sandbox refresh`):");
2018
+ for (const head of app.heads) {
2019
+ const version = head.version ? shortVersion(head.version) : "(none)";
2020
+ const built = head.builtAt ? `, built ${head.builtAt}` : "";
2021
+ const behind = head.behindMain && head.mainHead ? ` main has moved: now ${shortVersion(head.mainHead)}` : "";
2022
+ console.log(` ${pad2(head.environment, 16)} ${version} (${head.status}${built})${behind}`);
2023
+ if (head.error) console.log(` ${head.error}`);
1682
2024
  }
1683
- default:
1684
- return raw;
1685
2025
  }
1686
- }
1687
- function buildRunInputFromFlags(argv, propTypes) {
1688
- const pairs = getArgValues(argv, "--input");
1689
- const input = {};
1690
- for (const pair of pairs) {
1691
- const eq = pair.indexOf("=");
1692
- if (eq <= 0) {
1693
- console.error(`Error: invalid --input "${pair}". Use key=value.`);
1694
- process4.exit(1);
2026
+ const def = app.definition;
2027
+ if (!def) return;
2028
+ console.log(" Environments:");
2029
+ for (const env of def.environments) {
2030
+ console.log(` ${env.name}${env.extends ? ` (extends ${env.extends})` : ""}`);
2031
+ console.log(indentLines(env.description, " "));
2032
+ if (env.seeds.length > 0) {
2033
+ console.log(" seeds:");
2034
+ for (const step of env.seeds) console.log(` ${step.id}: ${step.action}`);
1695
2035
  }
1696
- const key = pair.slice(0, eq);
1697
- const raw = pair.slice(eq + 1);
1698
- const type = propTypes[key];
1699
- if (!type) {
1700
- console.error(`Error: unknown input "${key}". Known: ${Object.keys(propTypes).join(", ") || "(none)"}`);
1701
- process4.exit(1);
2036
+ if (env.envKeys.length > 0) console.log(` env: ${env.envKeys.join(", ")}`);
2037
+ }
2038
+ if (def.actions.length > 0) {
2039
+ console.log(" Actions:");
2040
+ for (const a of def.actions) {
2041
+ console.log(` ${formatActionSignature(a)}`);
2042
+ console.log(` ${a.description}`);
1702
2043
  }
1703
- try {
1704
- input[key] = coerceInputValue(raw, type);
1705
- } catch (error) {
1706
- console.error(`Error: --input ${key}: ${error instanceof Error ? error.message : String(error)}`);
1707
- process4.exit(1);
2044
+ }
2045
+ if (def.operations.length > 0) {
2046
+ console.log(" Operations:");
2047
+ for (const op of def.operations) {
2048
+ console.log(` ${formatOperationSignature(op)}`);
2049
+ console.log(` ${op.description}`);
1708
2050
  }
1709
2051
  }
1710
- return input;
2052
+ console.log(` Personas: ${def.personas.join(", ") || "(none)"}`);
2053
+ console.log(` Ports: ${def.ports.map((p) => `${p.name} (${p.access})`).join(", ") || "(none)"}`);
1711
2054
  }
1712
- async function pollRunUntilDone(apiUrl, token, runId, timeoutMs, timeoutNote = "") {
1713
- const deadline = Date.now() + timeoutMs;
1714
- while (Date.now() < deadline) {
1715
- const pollRes = await apiRequest(apiUrl, token, "GET", `/org/sandboxes-v2/runs/${runId}`);
1716
- if (!pollRes.ok) {
1717
- console.error(`Error: ${pollRes.error}`);
1718
- process4.exit(1);
2055
+ function printSandbox(data) {
2056
+ console.log(`Sandbox ${data.id}: ${data.status}${data.expired ? " (expired)" : ""}`);
2057
+ console.log(` environment ${data.environment}`);
2058
+ console.log(` version ${shortVersion(data.version)}`);
2059
+ if (data.error) console.log(` error ${data.error}`);
2060
+ if (data.readyMs !== null) {
2061
+ const seconds = (ms) => `${(ms / 1e3).toFixed(1)}s`;
2062
+ const phases = data.phases ? ` (snapshot ${seconds(data.phases.snapshotMs)}, restore ${seconds(data.phases.restoreMs)}, prepare ${seconds(data.phases.prepareMs)})` : "";
2063
+ console.log(` ready in ${seconds(data.readyMs)}${phases}`);
2064
+ }
2065
+ if (data.snapshot?.buildPhases) {
2066
+ const { buildMs, prepareMs, seeds } = data.snapshot.buildPhases;
2067
+ const seedsMs = seeds.reduce((total, step) => total + step.ms, 0);
2068
+ console.log(
2069
+ ` snapshot built ${data.snapshot.builtAt} (build ${(buildMs / 1e3).toFixed(1)}s, prepare ${(prepareMs / 1e3).toFixed(1)}s, ${seeds.length} seeds ${(seedsMs / 1e3).toFixed(1)}s)`
2070
+ );
2071
+ for (const step of seeds) console.log(` ${step.id} ${step.action} ${(step.ms / 1e3).toFixed(1)}s`);
2072
+ const ran = data.snapshot.buildPhases.build?.steps.filter((step) => step.ran) ?? [];
2073
+ if (ran.length > 0) {
2074
+ console.log(` build steps: ${ran.map((step) => `${step.name} ${(step.ms / 1e3).toFixed(1)}s`).join(", ")}`);
1719
2075
  }
1720
- if (pollRes.data.status !== "running") return pollRes.data;
1721
- await sleep(2e3);
1722
2076
  }
1723
- console.error(`Timed out waiting for run ${runId}${timeoutNote}.`);
1724
- process4.exit(1);
1725
- }
1726
- async function handleSandboxRun(argv, apiUrl, token) {
1727
- const positional = argv.filter((a) => !a.startsWith("-"));
1728
- const [id, actionRef] = positional;
1729
- if (!id || !actionRef) {
1730
- console.error("Usage: cofactor sandbox run <id> <action|plugin.op> [--input k=v ...] [--json-input '{...}'] [--json]");
1731
- process4.exit(1);
2077
+ if (data.leaseExpiresAt) console.log(` expires ${data.leaseExpiresAt}`);
2078
+ if (data.synced) {
2079
+ writeRawOutput(` synced ${data.synced.buildOutput ? "build output" : "tree"} ${data.synced.tree} at ${data.synced.at}`);
1732
2080
  }
1733
- const json = hasFlag(argv, "--json");
1734
- const jsonInput = getArgValue(argv, "--json-input");
1735
- const showRes = await apiRequest(apiUrl, token, "GET", `/org/sandboxes-v2/sandboxes/${id}`);
1736
- if (!showRes.ok) {
1737
- console.error(`Error: ${showRes.error}`);
1738
- process4.exit(1);
2081
+ if (Object.keys(data.portUrls).length > 0) {
2082
+ console.log("");
2083
+ console.log(" URLs:");
2084
+ for (const [name, url] of Object.entries(data.portUrls)) writeRawOutput(` ${pad2(name, 10)} ${url}`);
1739
2085
  }
1740
- const sandbox = showRes.data;
1741
- const repoAction = sandbox.actions.find((a) => a.slug === actionRef);
1742
- const operation = sandbox.operations.find((op) => op.name === actionRef);
1743
- if (!repoAction && !operation) {
1744
- console.error(`Error: unknown action or operation "${actionRef}".`);
1745
- console.error(` Actions: ${sandbox.actions.map((a) => a.slug).join(", ") || "(none)"}`);
1746
- console.error(` Operations: ${sandbox.operations.map((op) => op.name).join(", ") || "(none)"}`);
1747
- process4.exit(1);
2086
+ if (data.personas.length > 0) {
2087
+ console.log("");
2088
+ console.log(" Logins:");
2089
+ for (const p of data.personas) writeRawOutput(` ${pad2(p.name, 14)} ${p.email} ${p.password}`);
2090
+ if (data.nativeLogin !== void 0) {
2091
+ console.log(
2092
+ data.nativeLogin ? " (native Login nodes sign these in via authentication.run)" : " (no authentication.run: native Login nodes fail with script_not_declared)"
2093
+ );
2094
+ }
1748
2095
  }
1749
- let input;
1750
- if (jsonInput !== void 0) {
1751
- try {
1752
- input = JSON.parse(jsonInput);
1753
- } catch (error) {
1754
- console.error(`Error: invalid --json-input: ${error instanceof Error ? error.message : String(error)}`);
1755
- process4.exit(1);
2096
+ if (data.signInUrl) {
2097
+ console.log("");
2098
+ writeRawOutput(` Open sign-in: ${data.signInUrl}`);
2099
+ }
2100
+ const seedIds = Object.keys(data.seedOutputs);
2101
+ if (seedIds.length > 0) {
2102
+ console.log("");
2103
+ console.log(" Seed outputs:");
2104
+ for (const id of seedIds) {
2105
+ for (const [key, value] of Object.entries(data.seedOutputs[id])) {
2106
+ writeRawOutput(` ${id}.${key}: ${JSON.stringify(value)}`);
2107
+ }
1756
2108
  }
1757
- } else if (repoAction) {
1758
- const propTypes = Object.fromEntries(Object.entries(repoAction.input).map(([key, prop]) => [key, prop.type]));
1759
- input = buildRunInputFromFlags(argv, propTypes);
1760
- } else {
1761
- input = buildRunInputFromFlags(argv, jsonSchemaPropTypes(operation.inputSchema));
1762
2109
  }
1763
- const enqueueRes = await apiRequest(
2110
+ if (data.actions.length > 0) {
2111
+ console.log("");
2112
+ console.log(" Actions:");
2113
+ for (const a of data.actions) console.log(` ${formatActionSignature(a)}`);
2114
+ }
2115
+ if (data.operations.length > 0) {
2116
+ console.log("");
2117
+ console.log(" Operations:");
2118
+ for (const op of data.operations) console.log(` ${formatOperationSignature(op)}`);
2119
+ }
2120
+ }
2121
+ function handleSandboxValidate(argv) {
2122
+ const file = argv.find((a) => !a.startsWith("-")) ?? "./cofactor.sandbox.yaml";
2123
+ const json = hasFlag(argv, "--json");
2124
+ const delivery = hasFlag(argv, "--upload") ? "upload" : "github";
2125
+ let yamlText;
2126
+ try {
2127
+ yamlText = readFileSync4(file, "utf-8");
2128
+ } catch (error) {
2129
+ const message = error instanceof Error ? error.message : String(error);
2130
+ if (json) {
2131
+ writeRawOutput(JSON.stringify({ ok: false, issues: [{ path: "", message }] }, null, 2));
2132
+ } else {
2133
+ console.error(`Error reading "${file}": ${message}`);
2134
+ }
2135
+ process6.exit(1);
2136
+ }
2137
+ const { definition, issues } = validateDefinition(yamlText, delivery);
2138
+ if (json) {
2139
+ writeRawOutput(JSON.stringify({ ok: issues.length === 0, issues }, null, 2));
2140
+ process6.exit(issues.length === 0 ? 0 : 1);
2141
+ }
2142
+ if (issues.length > 0) {
2143
+ for (const issue of issues) console.error(`${issue.path || "(root)"}: ${issue.message}`);
2144
+ process6.exit(1);
2145
+ }
2146
+ const environmentCount = Object.keys(definition.environments).length;
2147
+ const actionCount = Object.keys(definition.actions).length;
2148
+ const operationCount = Object.keys(definition.emulation).reduce(
2149
+ (sum, pluginId) => sum + pluginOperations(pluginId).length,
2150
+ 0
2151
+ );
2152
+ console.log(`\u2713 ${file} is valid`);
2153
+ console.log(` ${environmentCount} environment(s), ${actionCount} action(s), ${operationCount} operation(s)`);
2154
+ }
2155
+ async function fetchApplicationSandbox(apiUrl, token, applicationId, at) {
2156
+ const res = await apiRequest(
1764
2157
  apiUrl,
1765
2158
  token,
1766
- "POST",
1767
- `/org/sandboxes-v2/sandboxes/${id}/runs`,
1768
- { action: actionRef, input }
2159
+ "GET",
2160
+ `/org/applications/${applicationId}/sandbox${at ? `?at=${encodeURIComponent(at)}` : ""}`
1769
2161
  );
1770
- if (!enqueueRes.ok) {
1771
- if (enqueueRes.code === "SANDBOX_V2_ACTION_INVALID_INPUT") {
1772
- console.error(`Error: invalid input for "${actionRef}":`);
1773
- for (const issue of enqueueRes.issues ?? []) {
1774
- if (typeof issue === "string") console.error(` ${issue}`);
1775
- else console.error(` ${(issue.path ?? []).join(".")}: ${issue.message ?? ""}`);
2162
+ exitOnRefusal(res);
2163
+ return res.data;
2164
+ }
2165
+ var V2_ARGS = {
2166
+ envs: { values: ["--application", "--property"], flags: ["--json"], positionals: 0 },
2167
+ refresh: {
2168
+ values: ["--application", "--property", "--environment"],
2169
+ flags: ["--wait", "--json"],
2170
+ positionals: 0
2171
+ },
2172
+ connect: {
2173
+ values: ["--application", "--property", "--repo", "--dir", "--at"],
2174
+ flags: ["--json"],
2175
+ positionals: 0
2176
+ },
2177
+ push: {
2178
+ values: ["--application", "--property", "--definition", "--tag"],
2179
+ flags: ["--wait", "--json"],
2180
+ positionals: 1
2181
+ },
2182
+ claim: {
2183
+ values: ["--application", "--property", "--environment", "--at", "--timeout", "--repo", "--dir"],
2184
+ flags: ["--wait", "--json", "--from-local"],
2185
+ positionals: 0
2186
+ },
2187
+ show: { values: [], flags: ["--json"], positionals: 1 },
2188
+ claims: { values: ["--application", "--property"], flags: ["--json"], positionals: 0 },
2189
+ run: { values: ["--input", "--json-input"], flags: ["--json"], positionals: 2 },
2190
+ exec: { values: ["--timeout"], flags: ["--json"], positionals: 1 },
2191
+ sync: { values: ["--to"], flags: ["--build", "--json"], positionals: 2 },
2192
+ "auth-check": { values: ["--persona"], flags: ["--json"], positionals: 1 },
2193
+ "verify-env": {
2194
+ values: ["--application", "--property", "--environment", "--at"],
2195
+ flags: ["--json"],
2196
+ positionals: 0
2197
+ },
2198
+ destroy: { values: [], flags: [], positionals: 1 },
2199
+ secret: { values: ["--application", "--property", "--environment"], flags: ["--json"], positionals: 2 }
2200
+ };
2201
+ function rejectUnknownSandboxV2Args(command, argv) {
2202
+ const spec = V2_ARGS[command];
2203
+ if (!spec) return;
2204
+ let positionals = 0;
2205
+ for (let i = 0; i < argv.length; i++) {
2206
+ const arg = argv[i];
2207
+ if (spec.values.includes(arg)) {
2208
+ const value = argv[i + 1];
2209
+ if (!value || value.startsWith("--")) {
2210
+ console.error(`Error: ${arg} requires a value. See \`cofactor sandbox --help\`.`);
2211
+ process6.exit(1);
1776
2212
  }
1777
- console.error("");
1778
- console.error("Input schema:");
1779
- writeRawOutput(JSON.stringify(repoAction ? repoAction.input : operation.inputSchema, null, 2));
1780
- } else {
1781
- console.error(`Error: ${enqueueRes.error}`);
2213
+ i++;
2214
+ continue;
1782
2215
  }
1783
- process4.exit(1);
2216
+ if (["--input", "--tag"].some((value) => spec.values.includes(value) && arg.startsWith(`${value}=`))) continue;
2217
+ if (spec.flags.includes(arg) || arg === "--debug") continue;
2218
+ if (!arg.startsWith("-") && ++positionals <= spec.positionals) continue;
2219
+ if (command === "secret" && !arg.startsWith("-")) {
2220
+ console.error(
2221
+ "Error: `cofactor sandbox secret` takes no value argument: pipe the value on stdin. If you typed a real key on the command line, rotate it."
2222
+ );
2223
+ process6.exit(1);
2224
+ }
2225
+ console.error(`Error: \`cofactor sandbox ${command}\` does not take ${JSON.stringify(arg)}. See \`cofactor sandbox --help\`.`);
2226
+ process6.exit(1);
1784
2227
  }
1785
- const runId = enqueueRes.data.id;
1786
- const run = await pollRunUntilDone(apiUrl, token, runId, ((repoAction?.timeout ?? 60) + 60) * 1e3);
1787
- if (json) {
1788
- writeRawOutput(JSON.stringify(run, null, 2));
1789
- if (run.status === "failed") process4.exit(1);
2228
+ }
2229
+ function v2Positionals(command, argv) {
2230
+ const values = V2_ARGS[command]?.values ?? [];
2231
+ const result = [];
2232
+ for (let i = 0; i < argv.length; i++) {
2233
+ const arg = argv[i];
2234
+ if (values.includes(arg)) i++;
2235
+ else if (!arg.startsWith("-")) result.push(arg);
2236
+ }
2237
+ return result;
2238
+ }
2239
+ async function handleSandboxEnvs(argv, apiUrl, token) {
2240
+ const applicationId = await resolveApplicationId(argv, apiUrl, token);
2241
+ const app = await fetchApplicationSandbox(apiUrl, token, applicationId);
2242
+ if (hasFlag(argv, "--json")) {
2243
+ writeRawOutput(JSON.stringify(app, null, 2));
1790
2244
  return;
1791
2245
  }
1792
- if (run.status === "failed") {
1793
- console.error(`Run failed: ${run.error ?? "unknown error"}`);
1794
- if (run.stderrTail) {
1795
- console.error("");
1796
- console.error("stderr:");
1797
- console.error(run.stderrTail);
1798
- }
1799
- process4.exit(1);
2246
+ printApplicationHuman(app);
2247
+ }
2248
+ async function connectSandboxApplication(argv, apiUrl, token) {
2249
+ const repo = getArgValue(argv, "--repo");
2250
+ if (!repo) {
2251
+ console.error("Usage: cofactor sandbox connect --application <name|id> --repo <owner/name> [--dir <path>] [--json]");
2252
+ process6.exit(1);
1800
2253
  }
1801
- writeRawOutput(JSON.stringify(run.output ?? {}, null, 2));
2254
+ const body = {
2255
+ repo,
2256
+ definitionDir: getArgValue(argv, "--dir") ?? ".",
2257
+ at: getArgValue(argv, "--at")
2258
+ };
2259
+ const checkCanConnect = async () => {
2260
+ exitOnRefusal(await apiRequest(apiUrl, token, "POST", "/org/sandboxes-v2/connect-check", body));
2261
+ };
2262
+ const applicationId = await resolveOrCreateSandboxApplication(argv, apiUrl, token, checkCanConnect);
2263
+ const res = await apiRequest(apiUrl, token, "PUT", `/org/applications/${applicationId}/sandbox`, body);
2264
+ exitOnRefusal(res);
2265
+ return res.data;
1802
2266
  }
1803
- var EXEC_USAGE = "Usage: cofactor sandbox exec <id> [--timeout <seconds>] [--json] -- <command...>";
1804
- function parseExecArgs(options, commandWords) {
1805
- const command = commandWords.join(" ");
1806
- if (command.trim() === "") return { ok: false, error: `a command is required after \`--\`. ${EXEC_USAGE}` };
1807
- const id = v2Positionals("exec", options)[0];
1808
- if (!id) return { ok: false, error: `sandbox id required. ${EXEC_USAGE}` };
1809
- const timeout = getArgValue(options, "--timeout");
1810
- let timeoutSeconds;
1811
- if (timeout !== void 0) {
1812
- timeoutSeconds = /^\d+$/.test(timeout) ? Number(timeout) : NaN;
1813
- if (!Number.isInteger(timeoutSeconds) || timeoutSeconds < 1 || timeoutSeconds > EXEC_MAX_TIMEOUT_SECONDS) {
1814
- return { ok: false, error: `--timeout must be a whole number of seconds from 1 to ${EXEC_MAX_TIMEOUT_SECONDS}.` };
2267
+ async function handleSandboxConnect(argv, apiUrl, token) {
2268
+ const app = await connectSandboxApplication(argv, apiUrl, token);
2269
+ if (hasFlag(argv, "--json")) writeRawOutput(JSON.stringify(app, null, 2));
2270
+ else printApplicationHuman(app);
2271
+ }
2272
+ var CLAIM_POLL_INTERVAL_MS = 3e3;
2273
+ var CLAIM_TIMEOUT_SECONDS = 2 * 3600;
2274
+ var CLAIM_POLL_GRACE_MS = 2 * 60 * 1e3;
2275
+ async function pollSandboxUntilReady(apiUrl, token, id, timeoutSeconds) {
2276
+ const deadline = Date.now() + timeoutSeconds * 1e3 + CLAIM_POLL_GRACE_MS;
2277
+ let toldUpgrading = false;
2278
+ while (Date.now() < deadline) {
2279
+ const res = await apiRequest(apiUrl, token, "GET", `/org/sandboxes-v2/sandboxes/${id}`);
2280
+ if (!res.ok) return { error: res.error ?? "unknown error" };
2281
+ if (res.data.status === "ready" || res.data.status === "failed") return { data: res.data };
2282
+ if (res.data.upgrading && !toldUpgrading) {
2283
+ toldUpgrading = true;
2284
+ console.error(" updating this push to the current sandbox platform (once; the claim continues after)...");
1815
2285
  }
2286
+ await sleep(CLAIM_POLL_INTERVAL_MS);
1816
2287
  }
1817
- return { ok: true, args: { id, timeoutSeconds, json: hasFlag(options, "--json"), command } };
1818
- }
1819
- function execExitCode(run) {
1820
- if (run.status === "succeeded") return 0;
1821
- return run.exitCode ?? 1;
2288
+ return { error: `timed out waiting for sandbox ${id} to become ready (${Math.round(timeoutSeconds / 60)} min)` };
1822
2289
  }
1823
- async function handleSandboxExec(options, commandWords, apiUrl, token) {
1824
- const parsed = parseExecArgs(options, commandWords);
1825
- if (!parsed.ok) {
1826
- console.error(`Error: ${parsed.error}`);
1827
- process4.exit(1);
2290
+ async function handleSandboxClaim(argv, apiUrl, token) {
2291
+ const timeout = parseDurationSeconds(getArgValue(argv, "--timeout"), "--timeout");
2292
+ const fromLocal = hasFlag(argv, "--from-local");
2293
+ if (fromLocal && hasFlag(argv, "--at")) throw new Error("--from-local and --at are mutually exclusive.");
2294
+ if (!fromLocal && (hasFlag(argv, "--repo") || hasFlag(argv, "--dir"))) throw new Error("--repo and --dir on claim require --from-local.");
2295
+ const source = fromLocal ? publishLocalSource(argv, apiUrl) : null;
2296
+ const applicationId = source ? (await connectSandboxApplication(["--application", source.record.application, "--repo", source.record.repoName, "--dir", source.definitionDir, "--at", source.record.version], apiUrl, token)).applicationId : await resolveApplicationId(argv, apiUrl, token);
2297
+ if (source) {
2298
+ rememberLocalApplication(source, applicationId);
2299
+ await cleanupLocalRefs(apiUrl, token, applicationId, source.record.application);
1828
2300
  }
1829
- const { id, timeoutSeconds, json, command } = parsed.args;
1830
- const enqueueRes = await apiRequest(
1831
- apiUrl,
1832
- token,
1833
- "POST",
1834
- `/org/sandboxes-v2/sandboxes/${id}/exec`,
1835
- timeoutSeconds === void 0 ? { command } : { command, timeoutSeconds }
1836
- );
1837
- if (!enqueueRes.ok) {
1838
- console.error(`Error: ${enqueueRes.error}`);
1839
- process4.exit(1);
2301
+ const environment2 = getArgValue(argv, "--environment") ?? "default";
2302
+ const version = source?.record.version ?? getArgValue(argv, "--at");
2303
+ const timeoutSeconds = timeout ?? CLAIM_TIMEOUT_SECONDS;
2304
+ const wait = hasFlag(argv, "--wait");
2305
+ const json = hasFlag(argv, "--json");
2306
+ const body = { applicationId, environment: environment2 };
2307
+ if (version) body.version = version;
2308
+ if (timeout !== void 0) body.timeoutSeconds = timeout;
2309
+ if (wait) body.wait = true;
2310
+ const res = await apiRequest(apiUrl, token, "POST", "/org/sandboxes-v2/sandboxes", body);
2311
+ if (!res.ok) {
2312
+ console.error(`Error: ${res.error}`);
2313
+ if (res.code === "SANDBOX_V2_DEFINITION_INVALID") printIssues(res.issues, " ");
2314
+ process6.exit(1);
1840
2315
  }
1841
- const runId = enqueueRes.data.id;
1842
- const run = await pollRunUntilDone(
1843
- apiUrl,
1844
- token,
1845
- runId,
1846
- ((timeoutSeconds ?? EXEC_DEFAULT_TIMEOUT_SECONDS) + 60) * 1e3,
1847
- "; the command may still be running in the sandbox"
1848
- );
1849
- const code = execExitCode(run);
1850
- if (json) {
1851
- writeRawOutput(JSON.stringify(run, null, 2));
1852
- process4.exit(code);
2316
+ const id = res.data.id;
2317
+ if (source) rememberLocalClaim(source, id);
2318
+ if (source && wait && !json) console.log(`Source commit: ${version}`);
2319
+ if (!wait) {
2320
+ if (json) {
2321
+ writeRawOutput(JSON.stringify(source ? { id, version } : { id }, null, 2));
2322
+ } else {
2323
+ console.log(`Sandbox claiming: ${id}`);
2324
+ if (source) console.log(` Source commit: ${version}`);
2325
+ console.log(` Poll with: cofactor sandbox show ${id}`);
2326
+ }
2327
+ return;
1853
2328
  }
1854
- if (run.status === "failed" && run.exitCode === null) {
1855
- process4.stderr.write(`Error: ${run.error ?? "unknown error"}
1856
- `);
1857
- if (run.stdoutTail) process4.stderr.write(run.stdoutTail);
1858
- if (run.stdoutTail && !run.stdoutTail.endsWith("\n") && run.stderrTail) process4.stderr.write("\n");
1859
- if (run.stderrTail) process4.stderr.write(run.stderrTail);
1860
- process4.exit(1);
2329
+ const settled = "status" in res.data && (res.data.status === "ready" || res.data.status === "failed") ? res.data : null;
2330
+ if (!json && !settled) console.log(`Claiming sandbox ${id}...`);
2331
+ const waited = settled ? { data: settled } : await pollSandboxUntilReady(apiUrl, token, id, timeoutSeconds);
2332
+ if ("error" in waited) {
2333
+ console.error(`Error: ${waited.error}`);
2334
+ process6.exit(1);
1861
2335
  }
1862
- if (run.stdoutTail) process4.stdout.write(run.stdoutTail);
1863
- if (run.stderrTail) process4.stderr.write(run.stderrTail);
1864
- if (code === 124 && run.error) process4.stderr.write(`
1865
- ${run.error}
1866
- `);
1867
- process4.exit(code);
2336
+ const { data } = waited;
2337
+ if (json) {
2338
+ writeRawOutput(JSON.stringify(data, null, 2));
2339
+ } else {
2340
+ printSandbox(data);
2341
+ }
2342
+ if (data.status === "failed") process6.exit(1);
1868
2343
  }
1869
- async function handleSandboxDestroy(argv, apiUrl, token) {
2344
+ async function handleSandboxShow(argv, apiUrl, token) {
1870
2345
  const [id] = argv.filter((a) => !a.startsWith("-"));
1871
2346
  if (!id) {
1872
- console.error("Usage: cofactor sandbox destroy <id>");
1873
- process4.exit(1);
2347
+ console.error("Usage: cofactor sandbox show <id> [--json]");
2348
+ process6.exit(1);
1874
2349
  }
1875
- const res = await apiRequest(
1876
- apiUrl,
1877
- token,
1878
- "DELETE",
1879
- `/org/sandboxes-v2/sandboxes/${id}`
1880
- );
2350
+ const json = hasFlag(argv, "--json");
2351
+ const res = await apiRequest(apiUrl, token, "GET", `/org/sandboxes-v2/sandboxes/${id}`);
1881
2352
  if (!res.ok) {
1882
2353
  console.error(`Error: ${res.error}`);
1883
- process4.exit(1);
2354
+ process6.exit(1);
1884
2355
  }
1885
- console.log(`Sandbox ${id} destroying.`);
1886
- }
1887
- async function handleSandboxVerifyEnv(argv, apiUrl, token) {
1888
- const applicationId = await resolveApplicationId(argv, apiUrl, token);
1889
- const onlyEnvironment = getArgValue(argv, "--environment");
1890
- const json = hasFlag(argv, "--json");
1891
- const log = (line) => json ? console.error(line) : console.log(line);
1892
- const app = await fetchApplicationSandbox(apiUrl, token, applicationId);
1893
- if (!app.version || !app.definition) {
1894
- console.error("Error: this application has no version to verify yet: run `cofactor sandbox push` first.");
1895
- process4.exit(1);
1896
- }
1897
- const version = app.version;
1898
- const allEnvironments = app.definition.environments.map((e) => e.name);
1899
- if (onlyEnvironment && !allEnvironments.includes(onlyEnvironment)) {
1900
- console.error(`Error: environment "${onlyEnvironment}" not found. Known: ${allEnvironments.join(", ")}`);
1901
- process4.exit(1);
2356
+ if (json) {
2357
+ writeRawOutput(JSON.stringify(res.data, null, 2));
2358
+ return;
1902
2359
  }
1903
- const environments = onlyEnvironment ? [onlyEnvironment] : allEnvironments;
1904
- const results = [];
1905
- for (const environment2 of environments) {
1906
- log(`
1907
- === ${environment2} ===`);
1908
- const started = Date.now();
1909
- const claimRes = await apiRequest(
2360
+ printSandbox(res.data);
2361
+ }
2362
+ async function handleSandboxClaims(argv, apiUrl, token) {
2363
+ const json = hasFlag(argv, "--json");
2364
+ const applicationArg = getArgValue(argv, "--application") ?? getArgValue(argv, "--property");
2365
+ const applicationId = applicationArg ? await resolveApplicationId(argv, apiUrl, token) : void 0;
2366
+ const claims = [];
2367
+ let cursor = null;
2368
+ do {
2369
+ const query = new URLSearchParams({ mine: "true", status: "claiming,ready", limit: "100" });
2370
+ if (applicationId) query.set("applicationId", applicationId);
2371
+ if (cursor) query.set("cursor", cursor);
2372
+ const res = await apiRequest(
1910
2373
  apiUrl,
1911
2374
  token,
1912
- "POST",
1913
- "/org/sandboxes-v2/sandboxes",
1914
- { applicationId, environment: environment2, version }
2375
+ "GET",
2376
+ `/org/sandboxes-v2/sandboxes?${query}`
1915
2377
  );
1916
- if (!claimRes.ok) {
1917
- const seconds2 = (Date.now() - started) / 1e3;
1918
- console.error(` claim failed: ${claimRes.error}`);
1919
- results.push({ environment: environment2, ok: false, seconds: seconds2, error: claimRes.error });
1920
- continue;
1921
- }
1922
- const id = claimRes.data.id;
1923
- log(` claimed ${id}, waiting...`);
1924
- const waited = await pollSandboxUntilReady(apiUrl, token, id, CLAIM_TIMEOUT_SECONDS);
1925
- const seconds = (Date.now() - started) / 1e3;
1926
- if ("error" in waited || waited.data.status !== "ready") {
1927
- const error = "error" in waited ? waited.error : waited.data.error ?? "failed";
1928
- console.error(` failed: ${error}`);
1929
- results.push({ environment: environment2, ok: false, seconds, error });
1930
- } else {
1931
- const { data } = waited;
1932
- log(` ready in ${data.readyMs !== null ? (data.readyMs / 1e3).toFixed(1) : "?"}s`);
1933
- for (const [stepId, outputs] of Object.entries(data.seedOutputs)) {
1934
- for (const [key, value] of Object.entries(outputs)) {
1935
- log(` ${stepId}.${key}: ${JSON.stringify(value)}`);
1936
- }
1937
- }
1938
- const personas = [];
1939
- if (data.nativeLogin) {
1940
- for (const { name } of data.personas) {
1941
- const checked = await apiRequest(
1942
- apiUrl,
1943
- token,
1944
- "POST",
1945
- `/org/sandboxes-v2/sandboxes/${id}/auth-check`,
1946
- { persona: name },
1947
- // The API waits on the script (up to 60 s) and the sandbox connection.
1948
- { timeoutMs: 9e4 }
1949
- );
1950
- const check = checked.ok ? checked.data : { persona: name, ok: false, error: checked.error ?? "unknown error" };
1951
- personas.push(check);
1952
- if (check.ok) log(` persona ${name}: signed in`);
1953
- else console.error(` persona ${name}: FAILED
1954
- ${indentLines(check.error ?? "", " ")}`);
1955
- }
1956
- }
1957
- const failedPersonas = personas.filter((p) => !p.ok).map((p) => p.persona);
1958
- results.push({
1959
- environment: environment2,
1960
- ok: failedPersonas.length === 0,
1961
- seconds,
1962
- ...failedPersonas.length > 0 ? { error: `persona sign-in failed: ${failedPersonas.join(", ")}` } : {},
1963
- ...data.nativeLogin ? { personas } : {}
1964
- });
2378
+ if (!res.ok) {
2379
+ console.error(`Error: ${res.error ?? "could not list sandboxes"}`);
2380
+ process6.exit(1);
1965
2381
  }
1966
- log(` destroying ${id}...`);
1967
- await apiRequest(apiUrl, token, "DELETE", `/org/sandboxes-v2/sandboxes/${id}`);
2382
+ claims.push(...res.data);
2383
+ cursor = res.nextCursor ?? null;
2384
+ } while (cursor);
2385
+ if (json) {
2386
+ writeRawOutput(JSON.stringify(claims, null, 2));
2387
+ return;
1968
2388
  }
1969
- log("");
1970
- log("Summary:");
1971
- log(` ${pad2("Environment", 20)} ${pad2("Result", 6)} Seconds`);
1972
- for (const r of results) {
1973
- const errSuffix = r.error ? ` (${r.error})` : "";
1974
- log(` ${pad2(r.environment, 20)} ${pad2(r.ok ? "OK" : "FAIL", 6)} ${r.seconds.toFixed(1)}${errSuffix}`);
2389
+ if (claims.length === 0) {
2390
+ console.log("No live claims.");
2391
+ return;
1975
2392
  }
1976
- if (json) writeRawOutput(JSON.stringify(results, null, 2));
1977
- if (results.some((r) => !r.ok)) process4.exit(1);
1978
- }
1979
-
1980
- // src/sandbox-push.ts
1981
- import process5 from "process";
1982
- import { spawnSync as spawnSync2 } from "child_process";
1983
- import { createHash as createHash3 } from "crypto";
1984
- import { createReadStream, createWriteStream, mkdtempSync as mkdtempSync2, readFileSync as readFileSync4, rmSync as rmSync2 } from "fs";
1985
- import { open } from "fs/promises";
1986
- import { tmpdir as tmpdir2 } from "os";
1987
- import { dirname as dirname2, join as join2 } from "path";
1988
- import { pipeline } from "stream/promises";
1989
- import { createGunzip } from "zlib";
1990
-
1991
- // src/sandbox-push-syncable.ts
1992
- import { spawnSync } from "child_process";
1993
- import { existsSync, mkdtempSync, readFileSync as readFileSync3, rmSync, statSync } from "fs";
1994
- import { tmpdir } from "os";
1995
- import { dirname, join, resolve } from "path";
1996
- var realDocker = (args) => {
1997
- const result = spawnSync("docker", args, { encoding: "utf8", maxBuffer: 16 * 1024 * 1024 });
1998
- if (result.error) return { status: null, stdout: "", stderr: result.error.message };
1999
- return { status: result.status, stdout: result.stdout, stderr: result.stderr };
2000
- };
2001
- var SAMPLE = 20;
2002
- function sampleOf(paths) {
2003
- const sorted = [...paths].sort();
2004
- if (sorted.length <= SAMPLE) return sorted;
2005
- return Array.from({ length: SAMPLE }, (_, i) => sorted[Math.floor(i * sorted.length / SAMPLE)]);
2006
- }
2007
- var SYNCABLE_RULE = "a syncable image has your definition file at its WORKDIR, as `COPY . .` gives you";
2008
- function syncabilityLine(params) {
2009
- const docker2 = params.docker ?? realDocker;
2010
- const git2 = spawnSync("git", ["ls-files", "-s", "-z"], { cwd: params.definitionDir, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
2011
- if (git2.status !== 0) return "Sync: not checked (the definition is not in a git repository).";
2012
- const tracked = git2.stdout.split("\0").flatMap((entry) => /^(100644|100755) [0-9a-f]+ \d\t(.*)$/s.exec(entry)?.[2] ?? []).filter((path) => path !== "cofactor.sandbox.yaml").filter((path) => existsSync(join(params.definitionDir, path)));
2013
- const temp = mkdtempSync(join(tmpdir(), "cofactor-syncable-"));
2014
- const created = docker2(["create", params.image]);
2015
- if (created.status !== 0) {
2016
- rmSync(temp, { recursive: true, force: true });
2017
- return `Sync: not checked (docker create failed: ${created.stderr.trim()}).`;
2393
+ const names = new Map((await fetchProperties(apiUrl, token)).map((p) => [p.id, p.name]));
2394
+ console.log(`${pad2("ID", 38)} ${pad2("Application", 24)} ${pad2("Environment", 14)} ${pad2("Status", 10)} ${pad2("Claimed", 26)} Expires`);
2395
+ for (const c of claims) {
2396
+ console.log(
2397
+ `${pad2(c.id, 38)} ${pad2(names.get(c.applicationId) ?? c.applicationId, 24)} ${pad2(c.environment, 14)} ${pad2(c.status, 10)} ${pad2(c.createdAt, 26)} ${c.leaseExpiresAt ?? "-"}`
2398
+ );
2018
2399
  }
2019
- const container = created.stdout.trim();
2020
- let copies = 0;
2021
- try {
2022
- const copyOut = (path) => {
2023
- const target = join(temp, String(copies++));
2024
- if (docker2(["cp", "-L", `${container}:${params.workdir}/${path}`, target]).status !== 0) return null;
2025
- return statSync(target).isFile() ? readFileSync3(target) : null;
2026
- };
2027
- const anchor = copyOut("cofactor.sandbox.yaml");
2028
- if (!anchor) {
2029
- return `Sync: not syncable: cofactor.sandbox.yaml isn't at ${params.workdir}/cofactor.sandbox.yaml (${SYNCABLE_RULE}).`;
2400
+ console.log("\nTear one down with: cofactor sandbox destroy <id>");
2401
+ }
2402
+ function coerceInputValue(raw, type) {
2403
+ switch (type) {
2404
+ case "integer": {
2405
+ if (!/^-?\d+$/.test(raw)) throw new Error(`must be an integer, got "${raw}"`);
2406
+ return Number(raw);
2030
2407
  }
2031
- const sample = sampleOf(tracked);
2032
- let differ = 0;
2033
- let missing = 0;
2034
- for (const path of sample) {
2035
- const bytes = copyOut(path);
2036
- if (!bytes) missing += 1;
2037
- else if (gitBlobId(bytes) !== gitBlobId(readFileSync3(join(params.definitionDir, path)))) differ += 1;
2408
+ case "number": {
2409
+ const value = Number(raw);
2410
+ if (Number.isNaN(value)) throw new Error(`must be a number, got "${raw}"`);
2411
+ return value;
2038
2412
  }
2039
- const anchorOlder = gitBlobId(anchor) !== gitBlobId(readFileSync3(params.definitionFile));
2040
- const notes = [
2041
- ...anchorOlder || differ > 0 ? ["the image is older than your working tree, which is fine"] : [],
2042
- ...missing > 0 ? [`${missing} of ${sample.length} sampled files aren't in the image (fine for sources it doesn't ship)`] : []
2043
- ];
2044
- return `Sync: syncable${notes.length > 0 ? ` (${notes.join("; ")})` : ""}.`;
2045
- } finally {
2046
- docker2(["rm", container]);
2047
- rmSync(temp, { recursive: true, force: true });
2413
+ case "boolean": {
2414
+ if (raw === "true") return true;
2415
+ if (raw === "false") return false;
2416
+ throw new Error(`must be "true" or "false", got "${raw}"`);
2417
+ }
2418
+ default:
2419
+ return raw;
2048
2420
  }
2049
2421
  }
2050
- function syncabilityLineOrSkipped(params) {
2051
- try {
2052
- const definitionFile = resolve(params.definitionFile);
2053
- return syncabilityLine({ ...params, definitionFile, definitionDir: dirname(definitionFile) });
2054
- } catch (error) {
2055
- return `Sync: not checked (${error instanceof Error ? error.message : String(error)}).`;
2422
+ function buildRunInputFromFlags(argv, propTypes) {
2423
+ const pairs = getArgValues(argv, "--input");
2424
+ const input = {};
2425
+ for (const pair of pairs) {
2426
+ const eq = pair.indexOf("=");
2427
+ if (eq <= 0) {
2428
+ console.error(`Error: invalid --input "${pair}". Use key=value.`);
2429
+ process6.exit(1);
2430
+ }
2431
+ const key = pair.slice(0, eq);
2432
+ const raw = pair.slice(eq + 1);
2433
+ const type = propTypes[key];
2434
+ if (!type) {
2435
+ console.error(`Error: unknown input "${key}". Known: ${Object.keys(propTypes).join(", ") || "(none)"}`);
2436
+ process6.exit(1);
2437
+ }
2438
+ try {
2439
+ input[key] = coerceInputValue(raw, type);
2440
+ } catch (error) {
2441
+ console.error(`Error: --input ${key}: ${error instanceof Error ? error.message : String(error)}`);
2442
+ process6.exit(1);
2443
+ }
2056
2444
  }
2445
+ return input;
2057
2446
  }
2058
-
2059
- // ../sandbox-helper/src/image-archive.ts
2060
- import { z as z3 } from "zod";
2061
- var MAX_BLOB_BYTES = 5 * 1024 ** 3;
2062
- var ImageBlob = z3.object({
2063
- digest: z3.string().regex(/^sha256:[0-9a-f]{64}$/),
2064
- size: z3.number().int().nonnegative()
2065
- });
2066
- var ImageBlobs = z3.object({
2067
- config: ImageBlob,
2068
- // Docker allows at most 127 layers; each costs a storage lookup per push.
2069
- layers: z3.array(ImageBlob).min(1).max(128)
2070
- });
2071
- function withSandboxLayers(team, added, created) {
2072
- return {
2073
- ...team,
2074
- rootfs: { ...team.rootfs, diff_ids: [...team.rootfs.diff_ids, ...added.map((layer) => layer.digest)] },
2075
- history: [...team.history ?? [], ...added.map(() => ({ created, created_by: "cofactor sandbox push" }))],
2076
- config: { ...team.config, Entrypoint: ["/usr/local/bin/cofactor-sandbox", "boot"], Cmd: [] }
2077
- };
2447
+ async function pollRunUntilDone(apiUrl, token, runId, timeoutMs, timeoutNote = "") {
2448
+ const deadline = Date.now() + timeoutMs;
2449
+ while (Date.now() < deadline) {
2450
+ const pollRes = await apiRequest(apiUrl, token, "GET", `/org/sandboxes-v2/runs/${runId}`);
2451
+ if (!pollRes.ok) {
2452
+ console.error(`Error: ${pollRes.error}`);
2453
+ process6.exit(1);
2454
+ }
2455
+ if (pollRes.data.status !== "running") return pollRes.data;
2456
+ await sleep(2e3);
2457
+ }
2458
+ console.error(`Timed out waiting for run ${runId}${timeoutNote}.`);
2459
+ process6.exit(1);
2078
2460
  }
2079
- var BLOCK = 512;
2080
- var encoder = new TextEncoder();
2081
- var decoder = new TextDecoder();
2082
- function tarHeader(name, size, mode = 420) {
2083
- if (encoder.encode(name).length > 100) throw new Error(`tar entry name too long: ${name}`);
2084
- const header = new Uint8Array(BLOCK);
2085
- const put = (offset, length, value) => header.set(encoder.encode(value).slice(0, length), offset);
2086
- const octal = (offset, length, value) => put(offset, length, `${value.toString(8).padStart(length - 1, "0")}\0`);
2087
- put(0, 100, name);
2088
- octal(100, 8, mode);
2089
- octal(108, 8, 0);
2090
- octal(116, 8, 0);
2091
- octal(124, 12, size);
2092
- octal(136, 12, 0);
2093
- put(148, 8, " ");
2094
- put(156, 1, "0");
2095
- put(257, 6, "ustar\0");
2096
- put(263, 2, "00");
2097
- const checksum = header.reduce((sum, byte) => sum + byte, 0);
2098
- put(148, 8, `${checksum.toString(8).padStart(6, "0")}\0 `);
2099
- return header;
2100
- }
2101
- function tarPadding(size) {
2102
- return new Uint8Array((BLOCK - size % BLOCK) % BLOCK);
2103
- }
2104
- var TAR_END = new Uint8Array(BLOCK * 2);
2105
- function tarOf(files) {
2106
- const parts = files.flatMap((file) => [tarHeader(file.name, file.data.length, file.mode), file.data, tarPadding(file.data.length)]);
2107
- parts.push(TAR_END);
2108
- const out = new Uint8Array(parts.reduce((total, part) => total + part.length, 0));
2109
- let at = 0;
2110
- for (const part of parts) {
2111
- out.set(part, at);
2112
- at += part.length;
2461
+ async function handleSandboxRun(argv, apiUrl, token) {
2462
+ const positional = argv.filter((a) => !a.startsWith("-"));
2463
+ const [id, actionRef] = positional;
2464
+ if (!id || !actionRef) {
2465
+ console.error(
2466
+ "Usage: cofactor sandbox run <id> <action|plugin.op> [--input k=v ...] [--json-input '{...}'] [--json]"
2467
+ );
2468
+ process6.exit(1);
2113
2469
  }
2114
- return out;
2115
- }
2116
- var field = (block, offset, length) => decoder.decode(block.subarray(offset, offset + length)).replace(/\0.*$/s, "");
2117
- async function tarEntries(read) {
2118
- const entries = [];
2119
- let longName = null;
2120
- for (let offset = 0; ; ) {
2121
- const block = await read(offset, BLOCK);
2122
- if (block.length < BLOCK) throw new Error(`the tar ends at byte ${offset + block.length}, before its end-of-archive block; it is truncated`);
2123
- if (block.every((byte) => byte === 0)) return entries;
2124
- const size = Number.parseInt(field(block, 124, 12).trim() || "0", 8);
2125
- if (!Number.isSafeInteger(size) || size < 0) throw new Error(`tar entry at ${offset} has an invalid size`);
2126
- const type = field(block, 156, 1) || "0";
2127
- const prefix = field(block, 345, 155);
2128
- const name = longName ?? (prefix ? `${prefix}/${field(block, 0, 100)}` : field(block, 0, 100));
2129
- const data = offset + BLOCK;
2130
- longName = null;
2131
- if (type === "x" || type === "L") {
2132
- const text = decoder.decode(await read(data, size)).replace(/\0+$/, "");
2133
- longName = type === "L" ? text : /(?:^|\n)\d+ path=([^\n]*)\n/.exec(text)?.[1] ?? null;
2134
- } else if (type === "0") {
2135
- entries.push({ name, offset: data, size });
2470
+ const json = hasFlag(argv, "--json");
2471
+ const jsonInput = getArgValue(argv, "--json-input");
2472
+ const showRes = await apiRequest(apiUrl, token, "GET", `/org/sandboxes-v2/sandboxes/${id}`);
2473
+ if (!showRes.ok) {
2474
+ console.error(`Error: ${showRes.error}`);
2475
+ process6.exit(1);
2476
+ }
2477
+ const sandbox = showRes.data;
2478
+ const repoAction = sandbox.actions.find((a) => a.slug === actionRef);
2479
+ const operation = sandbox.operations.find((op) => op.name === actionRef);
2480
+ if (!repoAction && !operation) {
2481
+ console.error(`Error: unknown action or operation "${actionRef}".`);
2482
+ console.error(` Actions: ${sandbox.actions.map((a) => a.slug).join(", ") || "(none)"}`);
2483
+ console.error(` Operations: ${sandbox.operations.map((op) => op.name).join(", ") || "(none)"}`);
2484
+ process6.exit(1);
2485
+ }
2486
+ let input;
2487
+ if (jsonInput !== void 0) {
2488
+ try {
2489
+ input = JSON.parse(jsonInput);
2490
+ } catch (error) {
2491
+ console.error(`Error: invalid --json-input: ${error instanceof Error ? error.message : String(error)}`);
2492
+ process6.exit(1);
2136
2493
  }
2137
- offset = data + size + tarPadding(size).length;
2494
+ } else if (repoAction) {
2495
+ const propTypes = Object.fromEntries(Object.entries(repoAction.input).map(([key, prop]) => [key, prop.type]));
2496
+ input = buildRunInputFromFlags(argv, propTypes);
2497
+ } else {
2498
+ input = buildRunInputFromFlags(argv, jsonSchemaPropTypes(operation.inputSchema));
2499
+ }
2500
+ const enqueueRes = await apiRequest(
2501
+ apiUrl,
2502
+ token,
2503
+ "POST",
2504
+ `/org/sandboxes-v2/sandboxes/${id}/runs`,
2505
+ { action: actionRef, input }
2506
+ );
2507
+ if (!enqueueRes.ok) {
2508
+ if (enqueueRes.code === "SANDBOX_V2_ACTION_INVALID_INPUT") {
2509
+ console.error(`Error: invalid input for "${actionRef}":`);
2510
+ for (const issue of enqueueRes.issues ?? []) {
2511
+ if (typeof issue === "string") console.error(` ${issue}`);
2512
+ else console.error(` ${(issue.path ?? []).join(".")}: ${issue.message ?? ""}`);
2513
+ }
2514
+ console.error("");
2515
+ console.error("Input schema:");
2516
+ writeRawOutput(JSON.stringify(repoAction ? repoAction.input : operation.inputSchema, null, 2));
2517
+ } else {
2518
+ console.error(`Error: ${enqueueRes.error}`);
2519
+ }
2520
+ process6.exit(1);
2521
+ }
2522
+ const runId = enqueueRes.data.id;
2523
+ const run = await pollRunUntilDone(apiUrl, token, runId, ((repoAction?.timeout ?? 60) + 60) * 1e3);
2524
+ if (json) {
2525
+ writeRawOutput(JSON.stringify(run, null, 2));
2526
+ if (run.status === "failed") process6.exit(1);
2527
+ return;
2528
+ }
2529
+ if (run.status === "failed") {
2530
+ console.error(`Run failed: ${run.error ?? "unknown error"}`);
2531
+ if (run.stderrTail) {
2532
+ console.error("");
2533
+ console.error("stderr:");
2534
+ console.error(run.stderrTail);
2535
+ }
2536
+ process6.exit(1);
2138
2537
  }
2538
+ writeRawOutput(JSON.stringify(run.output ?? {}, null, 2));
2139
2539
  }
2140
-
2141
- // src/sandbox-push.ts
2142
- var USAGE = [
2143
- "Usage: cofactor sandbox push --application <name|id> <image> [--definition <file>] [--tag <tag>]... [--wait] [--json]",
2144
- "",
2145
- "Tag main builds `main` (or `nightly`): once an application has a push with either tag, its head is the",
2146
- "newest such push, and only those runs move the visual-review baseline. Other pushes are claimed with --at."
2147
- ].join("\n");
2148
- var UPLOAD_CONCURRENCY = 4;
2149
- var UPLOAD_ATTEMPTS = 3;
2150
- var HASH_CHUNK_BYTES = 8 * 1024 * 1024;
2151
- var IMPORT_TIMEOUT_MS = 50 * 6e4;
2152
- var IMPORT_POLL_MS = 5e3;
2153
- var PushError = class extends Error {
2154
- constructor(message, issues = []) {
2155
- super(message);
2156
- this.issues = issues;
2540
+ var EXEC_USAGE = "Usage: cofactor sandbox exec <id> [--timeout <seconds>] [--json] -- <command...>";
2541
+ function parseExecArgs(options, commandWords) {
2542
+ const command = commandWords.join(" ");
2543
+ if (command.trim() === "") return { ok: false, error: `a command is required after \`--\`. ${EXEC_USAGE}` };
2544
+ const id = v2Positionals("exec", options)[0];
2545
+ if (!id) return { ok: false, error: `sandbox id required. ${EXEC_USAGE}` };
2546
+ const timeout = getArgValue(options, "--timeout");
2547
+ let timeoutSeconds;
2548
+ if (timeout !== void 0) {
2549
+ timeoutSeconds = /^\d+$/.test(timeout) ? Number(timeout) : NaN;
2550
+ if (!Number.isInteger(timeoutSeconds) || timeoutSeconds < 1 || timeoutSeconds > EXEC_MAX_TIMEOUT_SECONDS) {
2551
+ return { ok: false, error: `--timeout must be a whole number of seconds from 1 to ${EXEC_MAX_TIMEOUT_SECONDS}.` };
2552
+ }
2157
2553
  }
2158
- issues;
2159
- };
2160
- function fail(message, issues) {
2161
- throw new PushError(message, issues);
2554
+ return { ok: true, args: { id, timeoutSeconds, json: hasFlag(options, "--json"), command } };
2162
2555
  }
2163
- function docker(args, options) {
2164
- const result = spawnSync2("docker", args, {
2165
- encoding: "utf8",
2166
- stdio: ["ignore", "pipe", options.quiet ? "pipe" : "inherit"],
2167
- maxBuffer: 64 * 1024 * 1024
2168
- });
2169
- if (result.error) fail(`could not run docker (${result.error.message}); is Docker installed and running?`);
2170
- if (result.status !== 0) fail(`docker ${args.slice(0, 2).join(" ")} failed:
2171
- ${result.stderr ?? ""}`.trimEnd());
2172
- return result.stdout;
2556
+ function execExitCode(run) {
2557
+ if (run.status === "succeeded") return 0;
2558
+ return run.exitCode ?? 1;
2173
2559
  }
2174
- function imageConfigOf(image) {
2175
- const result = spawnSync2("docker", ["image", "inspect", image], { encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
2176
- if (result.error) fail(`could not run docker (${result.error.message}); is Docker installed and running?`);
2177
- const [inspected] = result.status === 0 ? JSON.parse(result.stdout) : [];
2178
- if (!inspected) fail(`Docker has no local image ${image}: build or pull it first`);
2179
- if (inspected.Os !== "linux" || inspected.Architecture !== "amd64") {
2180
- fail(
2181
- `${image} is ${inspected.Os}/${inspected.Architecture}; sandboxes run linux/amd64. Rebuild it with
2182
- docker buildx build --platform linux/amd64 -t ${image} --load <context>`
2183
- );
2560
+ async function handleSandboxExec(options, commandWords, apiUrl, token) {
2561
+ const parsed = parseExecArgs(options, commandWords);
2562
+ if (!parsed.ok) {
2563
+ console.error(`Error: ${parsed.error}`);
2564
+ process6.exit(1);
2184
2565
  }
2185
- const user = inspected.Config.User ?? "";
2186
- if (!["", "0", "root"].includes(user.split(":")[0])) {
2187
- fail(
2188
- `${image} runs as USER ${user}; a sandbox image must leave USER as root (the sandbox drops to uid 1000 itself). Remove the final \`USER\` line from the Dockerfile, or end it with \`USER root\`.`
2189
- );
2566
+ const { id, timeoutSeconds, json, command } = parsed.args;
2567
+ const enqueueRes = await apiRequest(
2568
+ apiUrl,
2569
+ token,
2570
+ "POST",
2571
+ `/org/sandboxes-v2/sandboxes/${id}/exec`,
2572
+ timeoutSeconds === void 0 ? { command } : { command, timeoutSeconds }
2573
+ );
2574
+ if (!enqueueRes.ok) {
2575
+ console.error(`Error: ${enqueueRes.error}`);
2576
+ process6.exit(1);
2190
2577
  }
2191
- const env = Object.fromEntries(
2192
- (inspected.Config.Env ?? []).map((entry) => {
2193
- const separator = entry.indexOf("=");
2194
- return [entry.slice(0, separator), entry.slice(separator + 1)];
2195
- })
2578
+ const runId = enqueueRes.data.id;
2579
+ const run = await pollRunUntilDone(
2580
+ apiUrl,
2581
+ token,
2582
+ runId,
2583
+ ((timeoutSeconds ?? EXEC_DEFAULT_TIMEOUT_SECONDS) + 60) * 1e3,
2584
+ "; the command may still be running in the sandbox"
2196
2585
  );
2197
- const parsed = imageConfigSchema.safeParse({ workdir: inspected.Config.WorkingDir ?? "", env });
2198
- if (!parsed.success) {
2199
- const workdir = inspected.Config.WorkingDir || "(none)";
2200
- fail(
2201
- `${image}'s WORKDIR ${workdir} can't be used: ${parsed.error.issues[0]?.message ?? "invalid"}. Set one for your app in its Dockerfile (e.g. \`WORKDIR /app\`): services, prepare commands and actions run from it, and the sandbox makes it writable by the app.`
2202
- );
2586
+ const code = execExitCode(run);
2587
+ if (json) {
2588
+ writeRawOutput(JSON.stringify(run, null, 2));
2589
+ process6.exit(code);
2203
2590
  }
2204
- return parsed.data;
2205
- }
2206
- function shellWord(value) {
2207
- return /^[\w@%+=:,./-]+$/.test(value) ? value : `'${value.replaceAll("'", `'\\''`)}'`;
2208
- }
2209
- async function apiOrFail(result) {
2210
- const res = await result;
2211
- if (!res.ok) fail(res.error ?? "unknown error", res.issues);
2212
- return res.data;
2213
- }
2214
- var mib = (bytes) => `${(bytes / 1024 / 1024).toFixed(bytes < 10 * 1024 * 1024 ? 1 : 0)} MB`;
2215
- async function readAt(file, offset, size) {
2216
- const bytes = new Uint8Array(size);
2217
- for (let at = 0; at < size; ) {
2218
- const { bytesRead } = await file.read(bytes, at, size - at, offset + at);
2219
- if (bytesRead === 0) return bytes.subarray(0, at);
2220
- at += bytesRead;
2591
+ if (run.status === "failed" && run.exitCode === null) {
2592
+ process6.stderr.write(`Error: ${run.error ?? "unknown error"}
2593
+ `);
2594
+ if (run.stdoutTail) process6.stderr.write(run.stdoutTail);
2595
+ if (run.stdoutTail && !run.stdoutTail.endsWith("\n") && run.stderrTail) process6.stderr.write("\n");
2596
+ if (run.stderrTail) process6.stderr.write(run.stderrTail);
2597
+ process6.exit(1);
2221
2598
  }
2222
- return bytes;
2599
+ if (run.stdoutTail) process6.stdout.write(run.stdoutTail);
2600
+ if (run.stderrTail) process6.stderr.write(run.stderrTail);
2601
+ if (code === 124 && run.error) process6.stderr.write(`
2602
+ ${run.error}
2603
+ `);
2604
+ process6.exit(code);
2223
2605
  }
2224
- async function digestOf(file, offset, size) {
2225
- const hash = createHash3("sha256");
2226
- for (let at = 0; at < size; at += HASH_CHUNK_BYTES) {
2227
- hash.update(await readAt(file, offset + at, Math.min(HASH_CHUNK_BYTES, size - at)));
2606
+ async function handleSandboxDestroy(argv, apiUrl, token) {
2607
+ const [id] = argv.filter((a) => !a.startsWith("-"));
2608
+ if (!id) {
2609
+ console.error("Usage: cofactor sandbox destroy <id>");
2610
+ process6.exit(1);
2228
2611
  }
2229
- return `sha256:${hash.digest("hex")}`;
2612
+ const local = trackedLocalSandbox(id, apiUrl);
2613
+ const res = await apiRequest(
2614
+ apiUrl,
2615
+ token,
2616
+ "DELETE",
2617
+ `/org/sandboxes-v2/sandboxes/${id}`
2618
+ );
2619
+ if (!res.ok) {
2620
+ console.error(`Error: ${res.error}`);
2621
+ process6.exit(1);
2622
+ }
2623
+ console.log(`Sandbox ${id} destroying.`);
2624
+ if (local) await cleanupLocalRefs(apiUrl, token, local.applicationId, local.applicationId, { id, version: local.version });
2230
2625
  }
2231
- function partOf(bytes) {
2232
- return { digest: `sha256:${createHash3("sha256").update(bytes).digest("hex")}`, size: bytes.length, body: () => bytes };
2233
- }
2234
- async function putBlob(url, headers, part) {
2235
- const digest = part.digest;
2236
- for (let attempt = 1; ; attempt++) {
2237
- let response;
2238
- try {
2239
- const init = {
2240
- method: "PUT",
2241
- headers: { ...headers, "content-length": String(part.size) },
2242
- body: part.body(),
2243
- duplex: "half"
2244
- };
2245
- response = await fetch(url, init);
2246
- } catch (error) {
2247
- if (attempt >= UPLOAD_ATTEMPTS) throw error;
2248
- await new Promise((resolve3) => setTimeout(resolve3, 2e3 * attempt));
2249
- continue;
2250
- }
2251
- if (response.ok) return;
2252
- const detail = `${response.status}: ${(await response.text()).slice(0, 300)}`;
2253
- if (response.status < 500 || attempt >= UPLOAD_ATTEMPTS) {
2254
- fail(`uploading ${digest} was refused (HTTP ${detail}); run the push again to upload what is still missing`);
2255
- }
2256
- await new Promise((resolve3) => setTimeout(resolve3, 2e3 * attempt));
2257
- }
2258
- }
2259
- async function savedImage(archive) {
2260
- const file = await open(archive);
2261
- try {
2262
- const entries = await tarEntries((offset, length) => readAt(file, offset, length)).catch(
2263
- (error) => fail(`reading the saved image: ${error instanceof Error ? error.message : String(error)}`)
2264
- );
2265
- const byName = new Map(entries.map((entry) => [entry.name.replace(/^\.\//, ""), entry]));
2266
- const entryOf = (name) => {
2267
- const entry = byName.get(name);
2268
- if (!entry) fail(`the saved image has no ${name}; is this a \`docker save\` archive?`);
2269
- return entry;
2270
- };
2271
- const json = async (name) => {
2272
- const entry = entryOf(name);
2273
- return JSON.parse(new TextDecoder().decode(await readAt(file, entry.offset, entry.size)));
2274
- };
2275
- const layerOf = async (name) => {
2276
- const entry = entryOf(name);
2277
- const tooLarge = (bytes) => fail(`a layer of the image is ${(bytes / 1024 ** 3).toFixed(1)} GB; a push takes at most 5 GB per layer. Slim the image.`);
2278
- if (entry.size > MAX_BLOB_BYTES) tooLarge(entry.size);
2279
- const range = { start: entry.offset, end: entry.offset + entry.size - 1 };
2280
- const [first, second] = await readAt(file, entry.offset, 2);
2281
- if (first === 31 && second === 139) {
2282
- const unpacked = join2(dirname2(archive), `layer-${entry.offset}.tar`);
2283
- await pipeline(createReadStream(archive, range), createGunzip(), createWriteStream(unpacked)).catch(
2284
- (error) => fail(`unpacking a compressed layer of the image: ${error instanceof Error ? error.message : String(error)}`)
2285
- );
2286
- const plain = await open(unpacked);
2287
- try {
2288
- const { size } = await plain.stat();
2289
- if (size > MAX_BLOB_BYTES) tooLarge(size);
2290
- return { digest: await digestOf(plain, 0, size), size, body: () => createReadStream(unpacked) };
2291
- } finally {
2292
- await plain.close();
2293
- }
2294
- }
2295
- return { digest: await digestOf(file, entry.offset, entry.size), size: entry.size, body: () => createReadStream(archive, range) };
2296
- };
2297
- const [manifest] = await json("manifest.json");
2298
- if (!manifest) fail("the saved image lists no image");
2299
- const layers = [];
2300
- for (const name of manifest.Layers) layers.push(await layerOf(name));
2301
- return { config: await json(manifest.Config), layers };
2302
- } finally {
2303
- await file.close();
2304
- }
2305
- }
2306
- async function pushedImage(archive, cofactorLayers, definitionYaml, config) {
2307
- const saved = await savedImage(archive);
2308
- const own = partOf(
2309
- tarOf([
2310
- { name: IMAGE_DEFINITION_FILE.slice(1), data: new TextEncoder().encode(definitionYaml) },
2311
- { name: IMAGE_CONFIG_FILE.slice(1), data: new TextEncoder().encode(JSON.stringify(config)) }
2312
- ])
2313
- );
2314
- const added = [...cofactorLayers, own];
2315
- const pushed = withSandboxLayers(saved.config, added, (/* @__PURE__ */ new Date()).toISOString());
2316
- const imageConfig = partOf(new TextEncoder().encode(JSON.stringify(pushed)));
2317
- const blob = ({ digest, size }) => ({ digest, size });
2318
- return {
2319
- config: blob(imageConfig),
2320
- layers: [...saved.layers, ...added].map(blob),
2321
- blobs: new Map([imageConfig, ...saved.layers, own].map((part) => [part.digest, part]))
2322
- };
2323
- }
2324
- async function handleSandboxPush(argv, apiUrl, token) {
2325
- try {
2326
- await push(argv, apiUrl, token);
2327
- } catch (error) {
2328
- if (!(error instanceof PushError)) throw error;
2329
- console.error(`Error: ${error.message}`);
2330
- for (const issue of error.issues) console.error(` ${issue.path || "(root)"}: ${issue.message}`);
2331
- process5.exit(1);
2332
- }
2333
- }
2334
- async function push(argv, apiUrl, token) {
2335
- const valueFlags = /* @__PURE__ */ new Set(["--application", "--property", "--definition", "--tag", "--env", "--org", "--token", "--api-url", "--app-url"]);
2336
- const positional = argv.filter((arg, i) => !arg.startsWith("-") && !valueFlags.has(argv[i - 1] ?? ""));
2337
- const image = positional[0];
2338
- if (!image) fail(`an image is required
2339
- ${USAGE}`);
2340
- const definitionFile = getArgValue(argv, "--definition") ?? "./cofactor.sandbox.yaml";
2341
- const tags = getArgValues(argv, "--tag");
2342
- const badTag = tags.find((tag) => !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(tag));
2343
- if (badTag !== void 0) fail(`--tag ${badTag}: use letters, digits, '.', '_' and '-' (at most 64, starting with a letter or digit)`);
2344
- if (tags.length > 10) fail("a push takes at most 10 tags");
2345
- const wait = hasFlag(argv, "--wait");
2626
+ async function handleSandboxVerifyEnv(argv, apiUrl, token) {
2627
+ const applicationId = await resolveApplicationId(argv, apiUrl, token);
2628
+ const onlyEnvironment = getArgValue(argv, "--environment");
2346
2629
  const json = hasFlag(argv, "--json");
2347
2630
  const log = (line) => json ? console.error(line) : console.log(line);
2348
- let definitionYaml;
2349
- try {
2350
- definitionYaml = readFileSync4(definitionFile, "utf8");
2351
- } catch (error) {
2352
- fail(`reading ${definitionFile}: ${error instanceof Error ? error.message : String(error)}`);
2353
- }
2354
- const { definition, issues } = validateDefinition(definitionYaml, "upload");
2355
- if (!definition || issues.length > 0) fail(`${definitionFile} is not a valid definition for pushed images:`, issues);
2356
- const storageProvider = definition.object_storage?.provider;
2357
- const applicationId = await resolveOrCreateSandboxApplication(argv, apiUrl, token);
2358
- const config = imageConfigOf(image);
2359
- log(syncabilityLineOrSkipped({ image, workdir: config.workdir, definitionFile }));
2360
- const work = mkdtempSync2(join2(tmpdir2(), "cofactor-push-"));
2361
- const cleanUp = () => rmSync2(work, { recursive: true, force: true });
2362
- const interrupted = (signal) => {
2363
- cleanUp();
2364
- process5.exit(signal === "SIGINT" ? 130 : 143);
2365
- };
2366
- process5.once("SIGINT", interrupted);
2367
- process5.once("SIGTERM", interrupted);
2368
- process5.exitCode = 1;
2369
- try {
2370
- const ours = await apiOrFail(
2371
- apiRequest(
2372
- apiUrl,
2373
- token,
2374
- "GET",
2375
- "/org/sandboxes-v2/helper-layer"
2376
- )
2377
- );
2378
- if (!ours.helper) fail("this Cofactor takes pushes from an older cofactor CLI; use the version it ships with");
2379
- const cofactorLayers = [ours.helper, ...storageProvider ? [ours.objectStorage[storageProvider]] : []];
2380
- const archive = join2(work, "image.tar");
2381
- log("Saving the image...");
2382
- docker(["save", "--output", archive, image], { quiet: json });
2383
- const pushing = await pushedImage(archive, cofactorLayers, definitionYaml, config);
2384
- const { layers } = pushing;
2385
- const created = await apiOrFail(
2386
- apiRequest(
2387
- apiUrl,
2388
- token,
2389
- "POST",
2390
- `/org/applications/${applicationId}/sandbox/uploads`,
2391
- { definitionYaml, platformVersion: ours.platformVersion, tags, image: { config: pushing.config, layers } }
2392
- )
2393
- );
2394
- const partsByDigest = pushing.blobs;
2395
- const uploadBytes = created.uploads.reduce((total, upload) => total + (partsByDigest.get(upload.digest)?.size ?? 0), 0);
2396
- const imageBytes = layers.reduce((total, layer) => total + layer.size, 0);
2397
- log(`Pushing ${created.id}: uploading ${created.uploads.length} of ${layers.length + 1} blobs (${mib(uploadBytes)}); ${mib(imageBytes - uploadBytes)} already in Cofactor`);
2398
- const queue = [...created.uploads];
2399
- let sent = 0;
2400
- await Promise.all(
2401
- Array.from({ length: UPLOAD_CONCURRENCY }, async () => {
2402
- for (let next = queue.shift(); next; next = queue.shift()) {
2403
- const part = partsByDigest.get(next.digest);
2404
- if (!part) fail(`Cofactor asked for ${next.digest}, which this image doesn't have`);
2405
- await putBlob(next.url, next.headers, part);
2406
- sent += part.size;
2407
- if (part.size > 10 * 1024 * 1024) log(` ${mib(sent)} of ${mib(uploadBytes)}`);
2408
- }
2409
- })
2631
+ const app = await fetchApplicationSandbox(
2632
+ apiUrl,
2633
+ token,
2634
+ applicationId,
2635
+ getArgValue(argv, "--at")
2636
+ );
2637
+ if (!app.version || !app.definition) {
2638
+ console.error(
2639
+ "Error: no sandbox definition at the default version. For GitHub, push it and use --at <sha>; for images, run `cofactor sandbox push`."
2410
2640
  );
2411
- rmSync2(archive);
2412
- let pushed = await apiOrFail(
2413
- apiRequest(
2414
- apiUrl,
2415
- token,
2416
- "POST",
2417
- `/org/applications/${applicationId}/sandbox/uploads/${created.id}/complete`
2418
- )
2641
+ process6.exit(1);
2642
+ }
2643
+ const version = app.version;
2644
+ const allEnvironments = app.definition.environments.map((e) => e.name);
2645
+ if (onlyEnvironment && !allEnvironments.includes(onlyEnvironment)) {
2646
+ console.error(`Error: environment "${onlyEnvironment}" not found. Known: ${allEnvironments.join(", ")}`);
2647
+ process6.exit(1);
2648
+ }
2649
+ const environments = onlyEnvironment ? [onlyEnvironment] : allEnvironments;
2650
+ const results = [];
2651
+ for (const environment2 of environments) {
2652
+ log(`
2653
+ === ${environment2} ===`);
2654
+ const started = Date.now();
2655
+ const claimRes = await apiRequest(
2656
+ apiUrl,
2657
+ token,
2658
+ "POST",
2659
+ "/org/sandboxes-v2/sandboxes",
2660
+ { applicationId, environment: environment2, version }
2419
2661
  );
2420
- if (wait) {
2421
- log("Importing (Cofactor boots the image until its services are healthy)...");
2422
- const deadline = Date.now() + IMPORT_TIMEOUT_MS;
2423
- const importStarted = Date.now();
2424
- let reported = importStarted;
2425
- while ((pushed.status === "importing" || pushed.status === "uploading") && Date.now() < deadline) {
2426
- await new Promise((resolve3) => setTimeout(resolve3, IMPORT_POLL_MS));
2427
- if (Date.now() - reported >= 6e4) {
2428
- reported = Date.now();
2429
- log(` still importing (${Math.round((reported - importStarted) / 6e4)} min)`);
2430
- }
2431
- pushed = await apiOrFail(
2432
- apiRequest(apiUrl, token, "GET", `/org/applications/${applicationId}/sandbox/uploads/${created.id}`)
2433
- );
2434
- }
2662
+ if (!claimRes.ok) {
2663
+ const seconds2 = (Date.now() - started) / 1e3;
2664
+ console.error(` claim failed: ${claimRes.error}`);
2665
+ results.push({ environment: environment2, ok: false, seconds: seconds2, error: claimRes.error });
2666
+ continue;
2435
2667
  }
2436
- const uploaded = { blobs: created.uploads.length, of: layers.length + 1, bytes: uploadBytes, reusedBytes: imageBytes - uploadBytes };
2437
- const application = shellWord(getArgValue(argv, "--application") ?? getArgValue(argv, "--property") ?? applicationId);
2438
- const target = ["--env", "--org"].map((flag) => {
2439
- const value = getArgValue(process5.argv, flag);
2440
- return value ? ` ${flag} ${shellWord(value)}` : "";
2441
- }).join("");
2442
- if (json) {
2443
- writeRawOutput(JSON.stringify({ ...pushed, uploaded }, null, 2));
2668
+ const id = claimRes.data.id;
2669
+ log(` claimed ${id}, waiting...`);
2670
+ const waited = await pollSandboxUntilReady(apiUrl, token, id, CLAIM_TIMEOUT_SECONDS);
2671
+ const seconds = (Date.now() - started) / 1e3;
2672
+ if ("error" in waited || waited.data.status !== "ready") {
2673
+ const error = "error" in waited ? waited.error : waited.data.error ?? "failed";
2674
+ console.error(` failed: ${error}`);
2675
+ results.push({ environment: environment2, ok: false, seconds, error });
2444
2676
  } else {
2445
- console.log(`Push ${pushed.id}: ${pushed.status}${pushed.tags.length > 0 ? ` [${pushed.tags.join(", ")}]` : ""}`);
2446
- if (pushed.status === "ready") {
2447
- console.log(` Claim it: cofactor sandbox claim --application ${application} --environment default --wait${target}`);
2448
- } else if (pushed.status !== "failed") {
2449
- console.log(` Importing; follow it with: cofactor sandbox envs --application ${application}${target}`);
2677
+ const { data } = waited;
2678
+ log(` ready in ${data.readyMs !== null ? (data.readyMs / 1e3).toFixed(1) : "?"}s`);
2679
+ for (const [stepId, outputs] of Object.entries(data.seedOutputs)) {
2680
+ for (const [key, value] of Object.entries(outputs)) {
2681
+ log(` ${stepId}.${key}: ${JSON.stringify(value)}`);
2682
+ }
2450
2683
  }
2451
- }
2452
- if (pushed.status === "failed") fail(`push ${pushed.id} failed:
2453
- ${pushed.error ?? "no reason given"}`);
2454
- process5.exitCode = 0;
2455
- } finally {
2456
- process5.off("SIGINT", interrupted);
2457
- process5.off("SIGTERM", interrupted);
2458
- cleanUp();
2459
- }
2460
- }
2461
-
2462
- // src/sandbox-sync.ts
2463
- import { spawnSync as spawnSync3 } from "child_process";
2464
- import process6 from "process";
2465
- import { copyFileSync, existsSync as existsSync2, lstatSync, mkdtempSync as mkdtempSync3, readFileSync as readFileSync5, realpathSync, rmSync as rmSync3 } from "fs";
2466
- import { tmpdir as tmpdir3 } from "os";
2467
- import { join as join3, relative, resolve as resolve2, sep } from "path";
2468
- var SyncError = class extends Error {
2469
- };
2470
- function git(args, options) {
2471
- const result = spawnSync3("git", args, {
2472
- cwd: options.cwd,
2473
- env: options.env ?? process6.env,
2474
- input: options.input,
2475
- encoding: "utf8",
2476
- maxBuffer: 1 << 30
2477
- // Node defaults to 1 MiB; this repo's `ls-files -s -z` alone is 1.2 MB
2478
- });
2479
- if (result.error) throw new SyncError(`git ${args[0]} failed: ${result.error.message}`);
2480
- if (!(options.okCodes ?? [0]).includes(result.status ?? -1)) {
2481
- throw new SyncError(`git ${args[0]} failed (${result.signal ?? result.status}): ${result.stderr.trim()}`);
2482
- }
2483
- return result.stdout;
2484
- }
2485
- var toPosix = (path) => path.split(sep).join("/");
2486
- function repositoryOf(dir) {
2487
- const top = git(["rev-parse", "--show-toplevel"], { cwd: dir }).trim();
2488
- const gitDir = git(["rev-parse", "--absolute-git-dir"], { cwd: dir }).trim();
2489
- return { top: realpathSync(top), gitDir };
2490
- }
2491
- function indexEntries(output2, strip, top) {
2492
- const files = [];
2493
- const skipped = [];
2494
- for (const entry of output2.split("\0")) {
2495
- if (entry === "") continue;
2496
- const match = /^(\d{6}) ([0-9a-f]+) \d\t(.*)$/s.exec(entry);
2497
- if (!match) throw new SyncError(`unexpected git ls-files line: ${entry}`);
2498
- const [, mode, blob, repoPath] = match;
2499
- const path = strip === "" ? repoPath : repoPath.slice(strip.length + 1);
2500
- const onDisk = mode === "100644" || mode === "100755" ? lstatOrNull(join3(top, repoPath)) : null;
2501
- if (onDisk?.isFile()) files.push({ path, blob, mode: mode === "100755" ? 493 : 420, size: onDisk.size });
2502
- else skipped.push(path);
2684
+ const personas = [];
2685
+ if (data.nativeLogin) {
2686
+ for (const { name } of data.personas) {
2687
+ const checked = await apiRequest(
2688
+ apiUrl,
2689
+ token,
2690
+ "POST",
2691
+ `/org/sandboxes-v2/sandboxes/${id}/auth-check`,
2692
+ { persona: name },
2693
+ // The API waits on the script (up to 60 s) and the sandbox connection.
2694
+ { timeoutMs: 9e4 }
2695
+ );
2696
+ const check = checked.ok ? checked.data : { persona: name, ok: false, error: checked.error ?? "unknown error" };
2697
+ personas.push(check);
2698
+ if (check.ok) log(` persona ${name}: signed in`);
2699
+ else console.error(` persona ${name}: FAILED
2700
+ ${indentLines(check.error ?? "", " ")}`);
2701
+ }
2702
+ }
2703
+ const failedPersonas = personas.filter((p) => !p.ok).map((p) => p.persona);
2704
+ results.push({
2705
+ environment: environment2,
2706
+ ok: failedPersonas.length === 0,
2707
+ seconds,
2708
+ ...failedPersonas.length > 0 ? { error: `persona sign-in failed: ${failedPersonas.join(", ")}` } : {},
2709
+ ...data.nativeLogin ? { personas } : {}
2710
+ });
2711
+ }
2712
+ log(` destroying ${id}...`);
2713
+ await apiRequest(apiUrl, token, "DELETE", `/org/sandboxes-v2/sandboxes/${id}`);
2503
2714
  }
2504
- return { files, skipped };
2715
+ log("");
2716
+ log("Summary:");
2717
+ log(` ${pad2("Environment", 20)} ${pad2("Result", 6)} Seconds`);
2718
+ for (const r of results) {
2719
+ const errSuffix = r.error ? ` (${r.error})` : "";
2720
+ log(` ${pad2(r.environment, 20)} ${pad2(r.ok ? "OK" : "FAIL", 6)} ${r.seconds.toFixed(1)}${errSuffix}`);
2721
+ }
2722
+ if (json) writeRawOutput(JSON.stringify(results, null, 2));
2723
+ if (results.some((r) => !r.ok)) process6.exit(1);
2505
2724
  }
2506
- function workingTree(root) {
2507
- const { top, gitDir } = repositoryOf(root);
2508
- const prefix = toPosix(relative(top, realpathSync(root)));
2509
- const temp = mkdtempSync3(join3(tmpdir3(), "cofactor-sync-"));
2510
- const cleanup = () => rmSync3(temp, { recursive: true, force: true });
2511
- try {
2512
- const index = join3(temp, "index");
2513
- const env = { ...process6.env, GIT_INDEX_FILE: index };
2514
- const userIndex = join3(gitDir, "index");
2515
- if (existsSync2(userIndex)) copyFileSync(userIndex, index);
2516
- else if (git(["rev-parse", "--verify", "-q", "HEAD"], { cwd: top, okCodes: [0, 1] }).trim() !== "") git(["read-tree", "HEAD"], { cwd: top, env });
2517
- const pathspec = prefix === "" ? "." : `:(literal)${prefix}`;
2518
- git(["add", "-A", "--", pathspec], { cwd: top, env });
2519
- const entries = indexEntries(git(["ls-files", "-s", "-z", "--", pathspec], { cwd: top, env }), prefix, top);
2520
- const files = withRawBlobs(entries.files, top, prefix);
2521
- const skipped = entries.skipped;
2522
- const tree = git(prefix === "" ? ["write-tree"] : ["write-tree", `--prefix=${prefix}/`], { cwd: top, env }).trim();
2523
- const untracked = new Set(
2524
- git(["ls-files", "--others", "--exclude-standard", "-z", "--", pathspec], { cwd: top }).split("\0").filter(Boolean).map((path) => prefix === "" ? path : path.slice(prefix.length + 1))
2525
- );
2526
- return { files, skipped, untracked, tree, repo: top, prefix, env, cleanup };
2527
- } catch (error) {
2528
- cleanup();
2529
- throw error;
2725
+ async function handleSandboxAuthCheck(argv, apiUrl, token) {
2726
+ const id = v2Positionals("auth-check", argv)[0];
2727
+ const persona = getArgValue(argv, "--persona");
2728
+ if (!id || !persona) {
2729
+ console.error("Usage: cofactor sandbox auth-check <id> --persona <name> [--json]");
2730
+ process6.exit(1);
2530
2731
  }
2732
+ const checked = await apiRequest(
2733
+ apiUrl,
2734
+ token,
2735
+ "POST",
2736
+ `/org/sandboxes-v2/sandboxes/${encodeURIComponent(id)}/auth-check`,
2737
+ { persona },
2738
+ { timeoutMs: 9e4 }
2739
+ );
2740
+ exitOnRefusal(checked);
2741
+ if (hasFlag(argv, "--json")) writeRawOutput(JSON.stringify(checked.data, null, 2));
2742
+ else if (checked.data.ok) console.log(`Persona ${persona}: signed in`);
2743
+ else console.error(`Persona ${persona}: FAILED
2744
+ ${checked.data.error ?? "unknown error"}`);
2745
+ if (!checked.data.ok) process6.exit(1);
2531
2746
  }
2532
- function buildOutput(dir, to) {
2533
- const { gitDir } = repositoryOf(dir);
2534
- const temp = mkdtempSync3(join3(tmpdir3(), "cofactor-sync-"));
2747
+
2748
+ // src/sandbox-push.ts
2749
+ import process7 from "process";
2750
+ import { spawnSync as spawnSync4 } from "child_process";
2751
+ import { createHash as createHash4 } from "crypto";
2752
+ import { createReadStream, createWriteStream, mkdtempSync as mkdtempSync3, readFileSync as readFileSync6, rmSync as rmSync4 } from "fs";
2753
+ import { open } from "fs/promises";
2754
+ import { tmpdir as tmpdir3 } from "os";
2755
+ import { dirname as dirname2, join as join4 } from "path";
2756
+ import { pipeline } from "stream/promises";
2757
+ import { createGunzip } from "zlib";
2758
+
2759
+ // src/sandbox-push-syncable.ts
2760
+ import { spawnSync as spawnSync3 } from "child_process";
2761
+ import { existsSync as existsSync3, mkdtempSync as mkdtempSync2, readFileSync as readFileSync5, rmSync as rmSync3, statSync } from "fs";
2762
+ import { tmpdir as tmpdir2 } from "os";
2763
+ import { dirname, join as join3, resolve as resolve3 } from "path";
2764
+ var realDocker = (args) => {
2765
+ const result = spawnSync3("docker", args, { encoding: "utf8", maxBuffer: 16 * 1024 * 1024 });
2766
+ if (result.error) return { status: null, stdout: "", stderr: result.error.message };
2767
+ return { status: result.status, stdout: result.stdout, stderr: result.stderr };
2768
+ };
2769
+ var SAMPLE = 20;
2770
+ function sampleOf(paths) {
2771
+ const sorted = [...paths].sort();
2772
+ if (sorted.length <= SAMPLE) return sorted;
2773
+ return Array.from({ length: SAMPLE }, (_, i) => sorted[Math.floor(i * sorted.length / SAMPLE)]);
2774
+ }
2775
+ var SYNCABLE_RULE = "a syncable image has your definition file at its WORKDIR, as `COPY . .` gives you";
2776
+ function syncabilityLine(params) {
2777
+ const docker2 = params.docker ?? realDocker;
2778
+ const git3 = spawnSync3("git", ["ls-files", "-s", "-z"], { cwd: params.definitionDir, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
2779
+ if (git3.status !== 0) return "Sync: not checked (the definition is not in a git repository).";
2780
+ const tracked = git3.stdout.split("\0").flatMap((entry) => /^(100644|100755) [0-9a-f]+ \d\t(.*)$/s.exec(entry)?.[2] ?? []).filter((path) => path !== "cofactor.sandbox.yaml").filter((path) => existsSync3(join3(params.definitionDir, path)));
2781
+ const temp = mkdtempSync2(join3(tmpdir2(), "cofactor-syncable-"));
2782
+ const created = docker2(["create", params.image]);
2783
+ if (created.status !== 0) {
2784
+ rmSync3(temp, { recursive: true, force: true });
2785
+ return `Sync: not checked (docker create failed: ${created.stderr.trim()}).`;
2786
+ }
2787
+ const container = created.stdout.trim();
2788
+ let copies = 0;
2535
2789
  try {
2536
- const env = { ...process6.env, GIT_INDEX_FILE: join3(temp, "index"), GIT_DIR: gitDir, GIT_WORK_TREE: realpathSync(dir) };
2537
- git(["add", "-A", "-f", "--", "."], { cwd: dir, env });
2538
- const entries = indexEntries(git(["ls-files", "-s", "-z"], { cwd: dir, env }), "", realpathSync(dir));
2539
- const files = withRawBlobs(entries.files, realpathSync(dir), "", env);
2540
- const skipped = entries.skipped;
2541
- const tree = git(["write-tree"], { cwd: dir, env }).trim();
2542
- return { files: files.map((file) => ({ ...file, path: `${to}/${file.path}` })), skipped: skipped.map((path) => `${to}/${path}`), tree };
2790
+ const copyOut = (path) => {
2791
+ const target = join3(temp, String(copies++));
2792
+ if (docker2(["cp", "-L", `${container}:${params.workdir}/${path}`, target]).status !== 0) return null;
2793
+ return statSync(target).isFile() ? readFileSync5(target) : null;
2794
+ };
2795
+ const anchor = copyOut("cofactor.sandbox.yaml");
2796
+ if (!anchor) {
2797
+ return `Sync: not syncable: cofactor.sandbox.yaml isn't at ${params.workdir}/cofactor.sandbox.yaml (${SYNCABLE_RULE}).`;
2798
+ }
2799
+ const sample = sampleOf(tracked);
2800
+ let differ = 0;
2801
+ let missing = 0;
2802
+ for (const path of sample) {
2803
+ const bytes = copyOut(path);
2804
+ if (!bytes) missing += 1;
2805
+ else if (gitBlobId(bytes) !== gitBlobId(readFileSync5(join3(params.definitionDir, path)))) differ += 1;
2806
+ }
2807
+ const anchorOlder = gitBlobId(anchor) !== gitBlobId(readFileSync5(params.definitionFile));
2808
+ const notes = [
2809
+ ...anchorOlder || differ > 0 ? ["the image is older than your working tree, which is fine"] : [],
2810
+ ...missing > 0 ? [`${missing} of ${sample.length} sampled files aren't in the image (fine for sources it doesn't ship)`] : []
2811
+ ];
2812
+ return `Sync: syncable${notes.length > 0 ? ` (${notes.join("; ")})` : ""}.`;
2543
2813
  } finally {
2814
+ docker2(["rm", container]);
2544
2815
  rmSync3(temp, { recursive: true, force: true });
2545
2816
  }
2546
2817
  }
2547
- var FILTER_ATTRIBUTES = ["filter", "text", "eol", "ident", "working-tree-encoding"];
2548
- function withRawBlobs(files, top, prefix, env) {
2549
- if (files.length === 0) return files;
2550
- const options = { cwd: top, ...env ? { env } : {} };
2551
- const repoPaths = files.map((file) => prefix === "" ? file.path : `${prefix}/${file.path}`);
2552
- const autocrlf = git(["config", "--get", "core.autocrlf"], { ...options, okCodes: [0, 1] }).trim();
2553
- const converts = autocrlf === "input" || autocrlf !== "" && git(["config", "--get", "--type=bool", "core.autocrlf"], options).trim() === "true";
2554
- let filtered;
2555
- if (converts) {
2556
- filtered = new Set(repoPaths);
2557
- } else {
2558
- const out = git(["check-attr", "-z", "--stdin", ...FILTER_ATTRIBUTES], { ...options, input: repoPaths.map((path) => `${path}\0`).join("") }).split("\0");
2559
- filtered = /* @__PURE__ */ new Set();
2560
- for (let i = 0; i + 2 < out.length; i += 3) if (out[i + 2] !== "unspecified") filtered.add(out[i]);
2561
- }
2562
- return files.map(
2563
- (file, i) => filtered.has(repoPaths[i]) ? { ...file, blob: gitBlobId(readFileSync5(join3(top, repoPaths[i]))) } : file
2564
- );
2565
- }
2566
- function lstatOrNull(path) {
2818
+ function syncabilityLineOrSkipped(params) {
2567
2819
  try {
2568
- return lstatSync(path);
2820
+ const definitionFile2 = resolve3(params.definitionFile);
2821
+ return syncabilityLine({ ...params, definitionFile: definitionFile2, definitionDir: dirname(definitionFile2) });
2569
2822
  } catch (error) {
2570
- if (error.code === "ENOENT") return null;
2571
- throw error;
2823
+ return `Sync: not checked (${error instanceof Error ? error.message : String(error)}).`;
2572
2824
  }
2573
2825
  }
2574
- function chunked(items, fn) {
2575
- for (let i = 0; i < items.length; i += 200) fn(items.slice(i, i + 200));
2576
- }
2577
- function ignoredPaths(repo, env, paths) {
2578
- if (paths.length === 0) return /* @__PURE__ */ new Set();
2579
- const out = git(["check-ignore", "-z", "--stdin"], { cwd: repo, env, input: paths.map((p) => `${p}\0`).join(""), okCodes: [0, 1] });
2580
- return new Set(out.split("\0").filter(Boolean));
2581
- }
2582
- function everTracked(repo, pathspecs) {
2583
- const seen = /* @__PURE__ */ new Set();
2584
- if (pathspecs.length === 0) return seen;
2585
- if (git(["rev-parse", "--verify", "-q", "HEAD"], { cwd: repo, okCodes: [0, 1] }).trim() === "") return seen;
2586
- chunked(pathspecs, (chunk) => {
2587
- const out = git(["log", "--all", "--format=", "--name-only", "-z", "--", ...chunk.map((p) => `:(literal)${p}`)], { cwd: repo });
2588
- for (const path of out.split("\0")) {
2589
- const trimmed = path.replace(/^\n+/, "");
2590
- if (trimmed) seen.add(trimmed);
2591
- }
2592
- });
2593
- return seen;
2594
- }
2595
- async function chooseDeletions(params) {
2596
- const repoPath = (path) => params.prefix === "" ? path : `${params.prefix}/${path}`;
2597
- const ignored = ignoredPaths(params.repo, params.env, params.copyOnly.map((entry) => repoPath(entry.path)));
2598
- const candidates = params.copyOnly.filter((entry) => !ignored.has(repoPath(entry.path)));
2599
- const history = everTracked(params.repo, candidates.map((entry) => repoPath(entry.path)));
2600
- const kept = params.copyOnly.filter((entry) => ignored.has(repoPath(entry.path))).map((entry) => ({ ...entry, why: "ignored here" }));
2601
- const remove = [];
2602
- for (const entry of candidates) {
2603
- if (!entry.path.endsWith("/")) {
2604
- if (history.has(repoPath(entry.path))) remove.push(entry.path);
2605
- else kept.push({ ...entry, why: "never tracked here" });
2606
- continue;
2607
- }
2608
- const tracked = [...history].some((path) => path.startsWith(repoPath(entry.path)));
2609
- const listed = tracked ? await params.listDir(entry.path.slice(0, -1)) : [];
2610
- const ignoredInside = ignoredPaths(params.repo, params.env, listed.map(repoPath));
2611
- const files = listed.filter((path) => !ignoredInside.has(repoPath(path)) && history.has(repoPath(path)));
2612
- remove.push(...files);
2613
- if (files.length < entry.files) kept.push({ path: entry.path, files: entry.files - files.length, why: "never tracked here" });
2826
+
2827
+ // src/sandbox-push.ts
2828
+ var USAGE = [
2829
+ "Usage: cofactor sandbox push --application <name|id> <image> [--definition <file>] [--tag <tag>]... [--wait] [--json]",
2830
+ "",
2831
+ "Tag main builds `main` (or `nightly`): once an application has a push with either tag, its head is the",
2832
+ "newest such push, and only those runs move the visual-review baseline. Other pushes are claimed with --at."
2833
+ ].join("\n");
2834
+ var UPLOAD_CONCURRENCY = 4;
2835
+ var UPLOAD_ATTEMPTS = 3;
2836
+ var HASH_CHUNK_BYTES = 8 * 1024 * 1024;
2837
+ var IMPORT_TIMEOUT_MS = 50 * 6e4;
2838
+ var IMPORT_POLL_MS = 5e3;
2839
+ var PushError = class extends Error {
2840
+ constructor(message, issues = []) {
2841
+ super(message);
2842
+ this.issues = issues;
2614
2843
  }
2615
- return { delete: remove.sort(), kept: kept.sort((a, b) => a.path.localeCompare(b.path)) };
2844
+ issues;
2845
+ };
2846
+ function fail(message, issues) {
2847
+ throw new PushError(message, issues);
2616
2848
  }
2617
- function applyBatches(files, remove) {
2618
- const budget = SYNC_MAX_APPLY_BYTES - 1024 * 1024;
2619
- const cost = (file) => 512 + Math.ceil(file.bytes.length / 512) * 512 + 100 + file.path.length;
2620
- const batches = [[]];
2621
- let size = 0;
2622
- for (const file of files) {
2623
- if (cost(file) > budget) throw new SyncError(`${file.path} is ${Math.ceil(file.bytes.length / 1024 / 1024)} MiB; a sync sends files of at most ${budget / 1024 / 1024} MiB`);
2624
- if (size + cost(file) > budget && batches.at(-1).length > 0) {
2625
- batches.push([]);
2626
- size = 0;
2627
- }
2628
- batches.at(-1).push(file);
2629
- size += cost(file);
2630
- }
2631
- return batches.map((batch, index) => {
2632
- const manifest = {
2633
- files: batch.map(({ path, blob, mode }) => ({ path, blob, mode })),
2634
- delete: index === 0 ? remove : []
2635
- };
2636
- return tarOf([
2637
- { name: "manifest.json", data: new TextEncoder().encode(JSON.stringify(manifest)) },
2638
- ...batch.map((file, i) => ({ name: syncApplyEntryName(i), data: file.bytes }))
2639
- ]);
2849
+ function docker(args, options) {
2850
+ const result = spawnSync4("docker", args, {
2851
+ encoding: "utf8",
2852
+ stdio: ["ignore", "pipe", options.quiet ? "pipe" : "inherit"],
2853
+ maxBuffer: 64 * 1024 * 1024
2640
2854
  });
2855
+ if (result.error) fail(`could not run docker (${result.error.message}); is Docker installed and running?`);
2856
+ if (result.status !== 0) fail(`docker ${args.slice(0, 2).join(" ")} failed:
2857
+ ${result.stderr ?? ""}`.trimEnd());
2858
+ return result.stdout;
2641
2859
  }
2642
- async function copyRequest(session, path, init) {
2643
- let response;
2644
- try {
2645
- response = await fetch(`${session.url}${path}`, {
2646
- method: "POST",
2647
- headers: { authorization: `Bearer ${session.token}`, "content-type": init.contentType },
2648
- body: init.body
2649
- });
2650
- } catch (error) {
2651
- throw new SyncError(`could not reach the sandbox for ${path}: ${error instanceof Error ? error.message : String(error)}`);
2860
+ function imageConfigOf(image) {
2861
+ const result = spawnSync4("docker", ["image", "inspect", image], { encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
2862
+ if (result.error) fail(`could not run docker (${result.error.message}); is Docker installed and running?`);
2863
+ const [inspected] = result.status === 0 ? JSON.parse(result.stdout) : [];
2864
+ if (!inspected) fail(`Docker has no local image ${image}: build or pull it first`);
2865
+ if (inspected.Os !== "linux" || inspected.Architecture !== "amd64") {
2866
+ fail(
2867
+ `${image} is ${inspected.Os}/${inspected.Architecture}; sandboxes run linux/amd64. Rebuild it with
2868
+ docker buildx build --platform linux/amd64 -t ${image} --load <context>`
2869
+ );
2652
2870
  }
2653
- if (!response.ok) {
2654
- const text = await response.text();
2655
- let message = text;
2656
- try {
2657
- message = JSON.parse(text).error ?? text;
2658
- } catch {
2659
- }
2660
- throw new SyncError(`the sandbox refused ${path} (${response.status}): ${message.slice(0, 2e3)}`);
2871
+ const user = inspected.Config.User ?? "";
2872
+ if (!["", "0", "root"].includes(user.split(":")[0])) {
2873
+ fail(
2874
+ `${image} runs as USER ${user}; a sandbox image must leave USER as root (the sandbox drops to uid 1000 itself). Remove the final \`USER\` line from the Dockerfile, or end it with \`USER root\`.`
2875
+ );
2876
+ }
2877
+ const env = Object.fromEntries(
2878
+ (inspected.Config.Env ?? []).map((entry) => {
2879
+ const separator = entry.indexOf("=");
2880
+ return [entry.slice(0, separator), entry.slice(separator + 1)];
2881
+ })
2882
+ );
2883
+ const parsed = imageConfigSchema.safeParse({ workdir: inspected.Config.WorkingDir ?? "", env });
2884
+ if (!parsed.success) {
2885
+ const workdir = inspected.Config.WorkingDir || "(none)";
2886
+ fail(
2887
+ `${image}'s WORKDIR ${workdir} can't be used: ${parsed.error.issues[0]?.message ?? "invalid"}. Set one for your app in its Dockerfile (e.g. \`WORKDIR /app\`): services, prepare commands and actions run from it, and the sandbox makes it writable by the app.`
2888
+ );
2661
2889
  }
2662
- return response;
2890
+ return parsed.data;
2663
2891
  }
2664
- async function plan(session, local, to) {
2665
- const body = JSON.stringify(to === void 0 ? { local } : { local, to });
2666
- if (Buffer.byteLength(body) > SYNC_MAX_APPLY_BYTES) {
2667
- throw new SyncError(
2668
- `the file list (${local.length} files) is over the ${SYNC_MAX_APPLY_BYTES / 1024 / 1024} MiB a sync can plan in one request; ignore what the sandbox doesn't need (.gitignore)`
2669
- );
2892
+ function shellWord(value) {
2893
+ return /^[\w@%+=:,./-]+$/.test(value) ? value : `'${value.replaceAll("'", `'\\''`)}'`;
2894
+ }
2895
+ async function apiOrFail(result) {
2896
+ const res = await result;
2897
+ if (!res.ok) fail(res.error ?? "unknown error", res.issues);
2898
+ return res.data;
2899
+ }
2900
+ var mib = (bytes) => `${(bytes / 1024 / 1024).toFixed(bytes < 10 * 1024 * 1024 ? 1 : 0)} MB`;
2901
+ async function readAt(file, offset, size) {
2902
+ const bytes = new Uint8Array(size);
2903
+ for (let at = 0; at < size; ) {
2904
+ const { bytesRead } = await file.read(bytes, at, size - at, offset + at);
2905
+ if (bytesRead === 0) return bytes.subarray(0, at);
2906
+ at += bytesRead;
2670
2907
  }
2671
- const response = await copyRequest(session, "/sync/plan", { body, contentType: "application/json" });
2672
- return parseAnswer(syncPlanResponseSchema, await response.text(), "/sync/plan");
2908
+ return bytes;
2673
2909
  }
2674
- function parseAnswer(schema, text, path) {
2675
- let value;
2676
- try {
2677
- value = JSON.parse(text);
2678
- } catch {
2679
- throw new SyncError(`the sandbox answered ${path} with something other than JSON: ${text.slice(0, 300)}`);
2910
+ async function digestOf(file, offset, size) {
2911
+ const hash = createHash4("sha256");
2912
+ for (let at = 0; at < size; at += HASH_CHUNK_BYTES) {
2913
+ hash.update(await readAt(file, offset + at, Math.min(HASH_CHUNK_BYTES, size - at)));
2680
2914
  }
2681
- const parsed = schema.safeParse(value);
2682
- if (!parsed.success) throw new SyncError(`the sandbox answered ${path} unexpectedly: ${text.slice(0, 300)}`);
2683
- return parsed.data;
2915
+ return `sha256:${hash.digest("hex")}`;
2684
2916
  }
2685
- async function apply(session, batches, build, json) {
2686
- let written = 0;
2687
- let deleted = 0;
2688
- let restarted = [];
2689
- const steps = [];
2690
- for (const [index, body] of batches.entries()) {
2691
- const last = index === batches.length - 1;
2692
- const response = await copyRequest(session, `/sync/apply${build && last ? "?build=1" : ""}`, { body, contentType: "application/x-tar" });
2693
- if (!(build && last)) {
2694
- const result = parseAnswer(syncApplyResultSchema, await response.text(), "/sync/apply");
2695
- written += result.written;
2696
- deleted += result.deleted;
2917
+ function partOf(bytes) {
2918
+ return { digest: `sha256:${createHash4("sha256").update(bytes).digest("hex")}`, size: bytes.length, body: () => bytes };
2919
+ }
2920
+ async function putBlob(url, headers, part) {
2921
+ const digest = part.digest;
2922
+ for (let attempt = 1; ; attempt++) {
2923
+ let response;
2924
+ try {
2925
+ const init = {
2926
+ method: "PUT",
2927
+ headers: { ...headers, "content-length": String(part.size) },
2928
+ body: part.body(),
2929
+ duplex: "half"
2930
+ };
2931
+ response = await fetch(url, init);
2932
+ } catch (error) {
2933
+ if (attempt >= UPLOAD_ATTEMPTS) throw error;
2934
+ await new Promise((resolve4) => setTimeout(resolve4, 2e3 * attempt));
2697
2935
  continue;
2698
2936
  }
2699
- let buffer = "";
2700
- const decoder2 = new TextDecoder();
2701
- let finished = false;
2702
- for await (const chunk of response.body) {
2703
- buffer += decoder2.decode(chunk, { stream: true });
2704
- let newline = buffer.indexOf("\n");
2705
- while (newline >= 0) {
2706
- const event = parseAnswer(syncBuildEventSchema, buffer.slice(0, newline), "/sync/apply?build=1");
2707
- buffer = buffer.slice(newline + 1);
2708
- newline = buffer.indexOf("\n");
2709
- if (event.type === "applied") {
2710
- written += event.result.written;
2711
- deleted += event.result.deleted;
2712
- } else if (event.type === "output") {
2713
- if (!json) process6.stderr.write(event.text);
2714
- } else if (event.type === "step") {
2715
- steps.push({ name: event.name, ran: event.ran, ms: event.ms });
2716
- if (!json) process6.stderr.write(` build step ${event.name}: ${event.ran ? `ran in ${(event.ms / 1e3).toFixed(1)} s` : "unchanged"}
2717
- `);
2718
- } else if (event.type === "done") {
2719
- restarted = event.restarted;
2720
- finished = true;
2721
- } else if (event.type === "error") {
2722
- throw new SyncError(`the build failed after the files were synced: ${event.message}`);
2723
- }
2724
- }
2937
+ if (response.ok) return;
2938
+ const detail = `${response.status}: ${(await response.text()).slice(0, 300)}`;
2939
+ if (response.status < 500 || attempt >= UPLOAD_ATTEMPTS) {
2940
+ fail(`uploading ${digest} was refused (HTTP ${detail}); run the push again to upload what is still missing`);
2725
2941
  }
2726
- if (!finished) throw new SyncError("the build stream ended early; check the copy with `cofactor sandbox exec <id> -- cofactor-sandbox status`");
2942
+ await new Promise((resolve4) => setTimeout(resolve4, 2e3 * attempt));
2727
2943
  }
2728
- return { written, deleted, restarted, steps };
2729
2944
  }
2730
- var SYNC_USAGE = "Usage: cofactor sandbox sync <id> [--build] [--json]\n cofactor sandbox sync <id> <local-dir> --to <path-in-sandbox> [--json]";
2731
- var LIST_LIMIT = 20;
2732
- async function handleSandboxSync(positionals, argv, apiUrl, token) {
2733
- const [id, localDir] = positionals;
2734
- const to = getArgValue(argv, "--to");
2735
- const build = hasFlag(argv, "--build");
2736
- const json = hasFlag(argv, "--json");
2737
- const fail4 = (message) => {
2738
- console.error(`Error: ${message}`);
2739
- process6.exit(1);
2740
- };
2741
- if (!id) return fail4(`sandbox id required.
2742
- ${SYNC_USAGE}`);
2743
- if (localDir === void 0 !== (to === void 0)) return fail4(`a local directory and --to go together.
2744
- ${SYNC_USAGE}`);
2745
- if (to !== void 0 && build) return fail4("--build reruns the build of a working-tree sync; build output is already built (`sync <id> <dir> --to <path>`).");
2746
- if (to !== void 0 && (to === "" || to.startsWith("/") || to.split("/").some((part) => part === "" || part === "." || part === ".."))) {
2747
- return fail4("--to is a path under the sandbox's sync root, like `static` or `frontend/dist` (no leading `/`, no `..`).");
2748
- }
2749
- if (localDir !== void 0 && (!existsSync2(localDir) || !lstatSync(localDir).isDirectory())) return fail4(`${localDir} is not a directory`);
2750
- if (!isInsideRepo(to === void 0 ? process6.cwd() : resolve2(localDir))) {
2751
- return fail4("run this inside a git repository: the files to sync and the synced tree come from git.");
2752
- }
2753
- const started = Date.now();
2754
- const sessionRes = await apiRequest(
2755
- apiUrl,
2756
- token,
2757
- "POST",
2758
- `/org/sandboxes-v2/sandboxes/${id}/sync-session`
2759
- );
2760
- if (!sessionRes.ok) return fail4(sessionRes.error ?? "could not start a sync");
2761
- const session = sessionRes.data;
2945
+ async function savedImage(archive) {
2946
+ const file = await open(archive);
2762
2947
  try {
2763
- let local;
2764
- let decide;
2765
- let cleanup = () => {
2948
+ const entries = await tarEntries((offset, length) => readAt(file, offset, length)).catch(
2949
+ (error) => fail(`reading the saved image: ${error instanceof Error ? error.message : String(error)}`)
2950
+ );
2951
+ const byName = new Map(entries.map((entry) => [entry.name.replace(/^\.\//, ""), entry]));
2952
+ const entryOf = (name) => {
2953
+ const entry = byName.get(name);
2954
+ if (!entry) fail(`the saved image has no ${name}; is this a \`docker save\` archive?`);
2955
+ return entry;
2766
2956
  };
2767
- if (to !== void 0) {
2768
- const dir = resolve2(localDir);
2769
- const output2 = buildOutput(dir, to);
2770
- local = { ...output2, untracked: /* @__PURE__ */ new Set(), root: dir };
2771
- decide = async (planned) => ({ delete: planned.copyOnly.map((entry) => entry.path), kept: [] });
2772
- } else {
2773
- const root = session.delivery === "github" ? repositoryOf(process6.cwd()).top : process6.cwd();
2774
- if (session.delivery === "upload" && !existsSync2(join3(root, "cofactor.sandbox.yaml"))) {
2775
- return fail4("run this from the directory holding cofactor.sandbox.yaml: a pushed image's WORKDIR mirrors it.");
2957
+ const json = async (name) => {
2958
+ const entry = entryOf(name);
2959
+ return JSON.parse(new TextDecoder().decode(await readAt(file, entry.offset, entry.size)));
2960
+ };
2961
+ const layerOf = async (name) => {
2962
+ const entry = entryOf(name);
2963
+ const tooLarge = (bytes) => fail(`a layer of the image is ${(bytes / 1024 ** 3).toFixed(1)} GB; a push takes at most 5 GB per layer. Slim the image.`);
2964
+ if (entry.size > MAX_BLOB_BYTES) tooLarge(entry.size);
2965
+ const range = { start: entry.offset, end: entry.offset + entry.size - 1 };
2966
+ const [first, second] = await readAt(file, entry.offset, 2);
2967
+ if (first === 31 && second === 139) {
2968
+ const unpacked = join4(dirname2(archive), `layer-${entry.offset}.tar`);
2969
+ await pipeline(createReadStream(archive, range), createGunzip(), createWriteStream(unpacked)).catch(
2970
+ (error) => fail(`unpacking a compressed layer of the image: ${error instanceof Error ? error.message : String(error)}`)
2971
+ );
2972
+ const plain = await open(unpacked);
2973
+ try {
2974
+ const { size } = await plain.stat();
2975
+ if (size > MAX_BLOB_BYTES) tooLarge(size);
2976
+ return { digest: await digestOf(plain, 0, size), size, body: () => createReadStream(unpacked) };
2977
+ } finally {
2978
+ await plain.close();
2979
+ }
2776
2980
  }
2777
- const tree = workingTree(root);
2778
- cleanup = tree.cleanup;
2779
- local = { ...tree, root };
2780
- decide = (planned) => chooseDeletions({
2781
- copyOnly: planned.copyOnly,
2782
- repo: tree.repo,
2783
- prefix: tree.prefix,
2784
- env: tree.env,
2785
- listDir: async (dir) => (await plan(session, [], dir)).copyOnly.map((entry) => entry.path)
2786
- });
2787
- }
2788
- try {
2789
- const planStarted = Date.now();
2790
- const planned = await plan(session, local.files, to);
2791
- const skipped = new Set(local.skipped.flatMap((path) => [path, `${path}/`]));
2792
- const { delete: remove, kept } = await decide({ ...planned, copyOnly: planned.copyOnly.filter((entry) => !skipped.has(entry.path)) });
2793
- const planMs = Date.now() - planStarted;
2794
- const need = new Set(planned.need);
2795
- const sending = local.files.filter((file) => need.has(file.path));
2796
- if (!json) {
2797
- writeRawOutput(`Syncing ${local.root} into sandbox ${id} (${session.delivery === "github" ? "GitHub" : "pushed image"}, version ${session.version.slice(0, 12)})`);
2798
- writeRawOutput(` plan: ${sending.length} to write, ${remove.length} to delete, ${local.files.length - sending.length} unchanged (${(planMs / 1e3).toFixed(1)} s)`);
2799
- const untracked = sending.filter((file) => local.untracked.has(file.path));
2800
- const tracked = sending.filter((file) => !local.untracked.has(file.path));
2801
- if (sending.length > 0) writeRawOutput(" write:");
2802
- for (const file of tracked.slice(0, LIST_LIMIT)) writeRawOutput(` ${file.path}`);
2803
- if (tracked.length > LIST_LIMIT) writeRawOutput(` ...and ${tracked.length - LIST_LIMIT} more`);
2804
- for (const file of untracked) writeRawOutput(` ${file.path} (untracked)`);
2805
- if (remove.length > 0) writeRawOutput(" delete:");
2806
- for (const path of remove) writeRawOutput(` ${path}`);
2807
- if (kept.length > 0) {
2808
- writeRawOutput(" kept (only in the sandbox):");
2809
- for (const entry of kept.slice(0, LIST_LIMIT)) {
2810
- writeRawOutput(` ${entry.path}${entry.files > 1 ? ` (${entry.files} files)` : ""}, ${entry.why}`);
2811
- }
2812
- if (kept.length > LIST_LIMIT) writeRawOutput(` ...and ${kept.length - LIST_LIMIT} more`);
2981
+ return { digest: await digestOf(file, entry.offset, entry.size), size: entry.size, body: () => createReadStream(archive, range) };
2982
+ };
2983
+ const [manifest] = await json("manifest.json");
2984
+ if (!manifest) fail("the saved image lists no image");
2985
+ const layers = [];
2986
+ for (const name of manifest.Layers) layers.push(await layerOf(name));
2987
+ return { config: await json(manifest.Config), layers };
2988
+ } finally {
2989
+ await file.close();
2990
+ }
2991
+ }
2992
+ async function pushedImage(archive, cofactorLayers, definitionYaml, config) {
2993
+ const saved = await savedImage(archive);
2994
+ const own = partOf(
2995
+ tarOf([
2996
+ { name: IMAGE_DEFINITION_FILE.slice(1), data: new TextEncoder().encode(definitionYaml) },
2997
+ { name: IMAGE_CONFIG_FILE.slice(1), data: new TextEncoder().encode(JSON.stringify(config)) }
2998
+ ])
2999
+ );
3000
+ const added = [...cofactorLayers, own];
3001
+ const pushed = withSandboxLayers(saved.config, added, (/* @__PURE__ */ new Date()).toISOString());
3002
+ const imageConfig = partOf(new TextEncoder().encode(JSON.stringify(pushed)));
3003
+ const blob = ({ digest, size }) => ({ digest, size });
3004
+ return {
3005
+ config: blob(imageConfig),
3006
+ layers: [...saved.layers, ...added].map(blob),
3007
+ blobs: new Map([imageConfig, ...saved.layers, own].map((part) => [part.digest, part]))
3008
+ };
3009
+ }
3010
+ async function handleSandboxPush(argv, apiUrl, token) {
3011
+ try {
3012
+ await push(argv, apiUrl, token);
3013
+ } catch (error) {
3014
+ if (!(error instanceof PushError)) throw error;
3015
+ console.error(`Error: ${error.message}`);
3016
+ for (const issue of error.issues) console.error(` ${issue.path || "(root)"}: ${issue.message}`);
3017
+ process7.exit(1);
3018
+ }
3019
+ }
3020
+ async function push(argv, apiUrl, token) {
3021
+ const valueFlags = /* @__PURE__ */ new Set(["--application", "--property", "--definition", "--tag", "--env", "--org", "--token", "--api-url", "--app-url"]);
3022
+ const positional = argv.filter((arg, i) => !arg.startsWith("-") && !valueFlags.has(argv[i - 1] ?? ""));
3023
+ const image = positional[0];
3024
+ if (!image) fail(`an image is required
3025
+ ${USAGE}`);
3026
+ const definitionFile2 = getArgValue(argv, "--definition") ?? "./cofactor.sandbox.yaml";
3027
+ const tags = getArgValues(argv, "--tag");
3028
+ const badTag = tags.find((tag) => !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(tag));
3029
+ if (badTag !== void 0) fail(`--tag ${badTag}: use letters, digits, '.', '_' and '-' (at most 64, starting with a letter or digit)`);
3030
+ if (tags.length > 10) fail("a push takes at most 10 tags");
3031
+ const wait = hasFlag(argv, "--wait");
3032
+ const json = hasFlag(argv, "--json");
3033
+ const log = (line) => json ? console.error(line) : console.log(line);
3034
+ let definitionYaml;
3035
+ try {
3036
+ definitionYaml = readFileSync6(definitionFile2, "utf8");
3037
+ } catch (error) {
3038
+ fail(`reading ${definitionFile2}: ${error instanceof Error ? error.message : String(error)}`);
3039
+ }
3040
+ const { definition, issues } = validateDefinition(definitionYaml, "upload");
3041
+ if (!definition || issues.length > 0) fail(`${definitionFile2} is not a valid definition for pushed images:`, issues);
3042
+ const storageProvider = definition.object_storage?.provider;
3043
+ const applicationId = await resolveOrCreateSandboxApplication(argv, apiUrl, token);
3044
+ const config = imageConfigOf(image);
3045
+ log(syncabilityLineOrSkipped({ image, workdir: config.workdir, definitionFile: definitionFile2 }));
3046
+ const work = mkdtempSync3(join4(tmpdir3(), "cofactor-push-"));
3047
+ const cleanUp = () => rmSync4(work, { recursive: true, force: true });
3048
+ const interrupted = (signal) => {
3049
+ cleanUp();
3050
+ process7.exit(signal === "SIGINT" ? 130 : 143);
3051
+ };
3052
+ process7.once("SIGINT", interrupted);
3053
+ process7.once("SIGTERM", interrupted);
3054
+ process7.exitCode = 1;
3055
+ try {
3056
+ const ours = await apiOrFail(
3057
+ apiRequest(
3058
+ apiUrl,
3059
+ token,
3060
+ "GET",
3061
+ "/org/sandboxes-v2/helper-layer"
3062
+ )
3063
+ );
3064
+ if (!ours.helper) fail("this Cofactor takes pushes from an older cofactor CLI; use the version it ships with");
3065
+ const cofactorLayers = [ours.helper, ...storageProvider ? [ours.objectStorage[storageProvider]] : []];
3066
+ const archive = join4(work, "image.tar");
3067
+ log("Saving the image...");
3068
+ docker(["save", "--output", archive, image], { quiet: json });
3069
+ const pushing = await pushedImage(archive, cofactorLayers, definitionYaml, config);
3070
+ const { layers } = pushing;
3071
+ const created = await apiOrFail(
3072
+ apiRequest(
3073
+ apiUrl,
3074
+ token,
3075
+ "POST",
3076
+ `/org/applications/${applicationId}/sandbox/uploads`,
3077
+ { definitionYaml, platformVersion: ours.platformVersion, tags, image: { config: pushing.config, layers } }
3078
+ )
3079
+ );
3080
+ const partsByDigest = pushing.blobs;
3081
+ const uploadBytes = created.uploads.reduce((total, upload) => total + (partsByDigest.get(upload.digest)?.size ?? 0), 0);
3082
+ const imageBytes = layers.reduce((total, layer) => total + layer.size, 0);
3083
+ log(`Pushing ${created.id}: uploading ${created.uploads.length} of ${layers.length + 1} blobs (${mib(uploadBytes)}); ${mib(imageBytes - uploadBytes)} already in Cofactor`);
3084
+ const queue = [...created.uploads];
3085
+ let sent = 0;
3086
+ await Promise.all(
3087
+ Array.from({ length: UPLOAD_CONCURRENCY }, async () => {
3088
+ for (let next = queue.shift(); next; next = queue.shift()) {
3089
+ const part = partsByDigest.get(next.digest);
3090
+ if (!part) fail(`Cofactor asked for ${next.digest}, which this image doesn't have`);
3091
+ await putBlob(next.url, next.headers, part);
3092
+ sent += part.size;
3093
+ if (part.size > 10 * 1024 * 1024) log(` ${mib(sent)} of ${mib(uploadBytes)}`);
2813
3094
  }
2814
- if (local.skipped.length > 0) writeRawOutput(` not synced (symlinks and submodules): ${local.skipped.join(", ")}`);
2815
- }
2816
- const applyStarted = Date.now();
2817
- const batches = sending.length === 0 && remove.length === 0 && !build ? [] : applyBatches(
2818
- sending.map((file) => ({ ...file, bytes: readFileSync5(join3(local.root, to === void 0 ? file.path : file.path.slice(to.length + 1))) })),
2819
- remove
2820
- );
2821
- const applied = await apply(session, batches, build, json);
2822
- const applyMs = Date.now() - applyStarted;
2823
- const ms = Date.now() - started;
2824
- const recorded = await apiRequest(apiUrl, token, "POST", `/org/sandboxes-v2/sandboxes/${id}/syncs`, {
2825
- tree: local.tree,
2826
- buildOutput: to !== void 0,
2827
- written: applied.written,
2828
- deleted: applied.deleted,
2829
- ms
2830
- });
2831
- if (!recorded.ok) throw new SyncError(`the files were synced, but recording the sync failed: ${recorded.error}`);
2832
- if (json) {
2833
- writeRawOutput(
2834
- JSON.stringify(
2835
- {
2836
- id,
2837
- tree: local.tree,
2838
- buildOutput: to !== void 0,
2839
- written: sending.map((file) => file.path),
2840
- deleted: remove,
2841
- kept,
2842
- build: build ? { steps: applied.steps, restarted: applied.restarted } : null,
2843
- ms: { total: ms, plan: planMs, apply: applyMs }
2844
- },
2845
- null,
2846
- 2
2847
- )
3095
+ })
3096
+ );
3097
+ rmSync4(archive);
3098
+ let pushed = await apiOrFail(
3099
+ apiRequest(
3100
+ apiUrl,
3101
+ token,
3102
+ "POST",
3103
+ `/org/applications/${applicationId}/sandbox/uploads/${created.id}/complete`
3104
+ )
3105
+ );
3106
+ if (wait) {
3107
+ log("Importing (Cofactor boots the image until its services are healthy)...");
3108
+ const deadline = Date.now() + IMPORT_TIMEOUT_MS;
3109
+ const importStarted = Date.now();
3110
+ let reported = importStarted;
3111
+ while ((pushed.status === "importing" || pushed.status === "uploading") && Date.now() < deadline) {
3112
+ await new Promise((resolve4) => setTimeout(resolve4, IMPORT_POLL_MS));
3113
+ if (Date.now() - reported >= 6e4) {
3114
+ reported = Date.now();
3115
+ log(` still importing (${Math.round((reported - importStarted) / 6e4)} min)`);
3116
+ }
3117
+ pushed = await apiOrFail(
3118
+ apiRequest(apiUrl, token, "GET", `/org/applications/${applicationId}/sandbox/uploads/${created.id}`)
2848
3119
  );
2849
- return;
2850
3120
  }
2851
- writeRawOutput(` applied: ${applied.written} written, ${applied.deleted} deleted (${(applyMs / 1e3).toFixed(1)} s)`);
2852
- if (build) writeRawOutput(` restarted: ${applied.restarted.join(", ") || "nothing"}`);
2853
- writeRawOutput(`Synced ${to === void 0 ? "tree" : "build output"} ${local.tree} in ${(ms / 1e3).toFixed(1)} s.`);
2854
- if (!build) {
2855
- writeRawOutput(` Restart what runs it: cofactor sandbox exec ${id} -- cofactor-sandbox restart <service>`);
3121
+ }
3122
+ const uploaded = { blobs: created.uploads.length, of: layers.length + 1, bytes: uploadBytes, reusedBytes: imageBytes - uploadBytes };
3123
+ const application = shellWord(getArgValue(argv, "--application") ?? getArgValue(argv, "--property") ?? applicationId);
3124
+ const target = ["--env", "--org"].map((flag) => {
3125
+ const value = getArgValue(process7.argv, flag);
3126
+ return value ? ` ${flag} ${shellWord(value)}` : "";
3127
+ }).join("");
3128
+ if (json) {
3129
+ writeRawOutput(JSON.stringify({ ...pushed, uploaded }, null, 2));
3130
+ } else {
3131
+ console.log(`Push ${pushed.id}: ${pushed.status}${pushed.tags.length > 0 ? ` [${pushed.tags.join(", ")}]` : ""}`);
3132
+ if (pushed.status === "ready") {
3133
+ console.log(` Claim it: cofactor sandbox claim --application ${application} --environment default --wait${target}`);
3134
+ } else if (pushed.status !== "failed") {
3135
+ console.log(` Importing; follow it with: cofactor sandbox envs --application ${application}${target}`);
2856
3136
  }
2857
- const shown = await apiRequest(apiUrl, token, "GET", `/org/sandboxes-v2/sandboxes/${id}`);
2858
- for (const [name, url] of Object.entries(shown.data?.portUrls ?? {})) writeRawOutput(` ${name.padEnd(10)} ${url}`);
2859
- } finally {
2860
- cleanup();
2861
3137
  }
2862
- } catch (error) {
2863
- if (error instanceof SyncError) return fail4(error.message);
2864
- throw error;
3138
+ if (pushed.status === "failed") fail(`push ${pushed.id} failed:
3139
+ ${pushed.error ?? "no reason given"}`);
3140
+ process7.exitCode = 0;
3141
+ } finally {
3142
+ process7.off("SIGINT", interrupted);
3143
+ process7.off("SIGTERM", interrupted);
3144
+ cleanUp();
2865
3145
  }
2866
3146
  }
2867
- function isInsideRepo(dir) {
2868
- return git(["rev-parse", "--is-inside-work-tree"], { cwd: dir, okCodes: [0, 128] }).trim() === "true";
2869
- }
2870
3147
 
2871
3148
  // src/sandbox-refresh.ts
2872
- import process7 from "process";
3149
+ import process8 from "process";
2873
3150
  var POLL_INTERVAL_MS = 5e3;
2874
3151
  var WAIT_TIMEOUT_MS = 50 * 6e4;
2875
3152
  function shortVersion2(version) {
2876
3153
  return /^[0-9a-f]{40}$/.test(version) ? version.slice(0, 12) : version;
2877
3154
  }
2878
3155
  function sleep2(ms) {
2879
- return new Promise((resolve3) => setTimeout(resolve3, ms));
3156
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
2880
3157
  }
2881
3158
  async function handleSandboxRefresh(argv, apiUrl, token) {
2882
3159
  const applicationId = await resolveApplicationId(argv, apiUrl, token);
@@ -2889,7 +3166,7 @@ async function handleSandboxRefresh(argv, apiUrl, token) {
2889
3166
  });
2890
3167
  if (!res.ok) {
2891
3168
  console.error(`Error: ${res.error}`);
2892
- process7.exit(1);
3169
+ process8.exit(1);
2893
3170
  }
2894
3171
  return res.data;
2895
3172
  };
@@ -2909,7 +3186,7 @@ async function handleSandboxRefresh(argv, apiUrl, token) {
2909
3186
  );
2910
3187
  if (!res.ok) {
2911
3188
  console.error(`Error: ${res.error}`);
2912
- process7.exit(1);
3189
+ process8.exit(1);
2913
3190
  }
2914
3191
  if (res.data.status === "ready" && !res.data.isHead && !askedAgain) {
2915
3192
  askedAgain = true;
@@ -2939,11 +3216,11 @@ async function handleSandboxRefresh(argv, apiUrl, token) {
2939
3216
  }
2940
3217
  }
2941
3218
  }
2942
- if (latest.head.status === "failed" || wait && latest.head.status === "building") process7.exit(1);
3219
+ if (latest.head.status === "failed" || wait && latest.head.status === "building") process8.exit(1);
2943
3220
  }
2944
3221
 
2945
3222
  // src/sandbox-warm.ts
2946
- import process8 from "process";
3223
+ import process9 from "process";
2947
3224
  var STATE_LABELS = {
2948
3225
  warming: "Filling",
2949
3226
  warm: "On",
@@ -2970,7 +3247,7 @@ function isSandboxV2WarmCommand(first) {
2970
3247
  }
2971
3248
  function fail2(message) {
2972
3249
  console.error(`Error: ${message}`);
2973
- process8.exit(1);
3250
+ process9.exit(1);
2974
3251
  }
2975
3252
  function rejectUnknownWarmArgs(command, argv) {
2976
3253
  const spec = COMMANDS[command];
@@ -3044,7 +3321,7 @@ async function handleSandboxWarm(argv, apiUrl, token) {
3044
3321
  if (!command || !isSandboxV2WarmCommand(command)) {
3045
3322
  console.error(command ? `Unknown warm command: ${command}
3046
3323
  ${USAGE2}` : USAGE2);
3047
- process8.exit(1);
3324
+ process9.exit(1);
3048
3325
  }
3049
3326
  rejectUnknownWarmArgs(command, rest);
3050
3327
  if (command === "list") {
@@ -3075,7 +3352,7 @@ ${USAGE2}` : USAGE2);
3075
3352
  }
3076
3353
 
3077
3354
  // src/sandbox-secrets.ts
3078
- import process9 from "process";
3355
+ import process10 from "process";
3079
3356
  function scopeText(environment2) {
3080
3357
  return environment2 === null ? "all environments" : `environment ${environment2}`;
3081
3358
  }
@@ -3099,7 +3376,7 @@ function stdinRefusal(isTTY) {
3099
3376
  }
3100
3377
  function fail3(message) {
3101
3378
  console.error(`Error: ${message}`);
3102
- process9.exit(1);
3379
+ process10.exit(1);
3103
3380
  }
3104
3381
  function decodeSecretValue(bytes) {
3105
3382
  let text;
@@ -3113,7 +3390,7 @@ function decodeSecretValue(bytes) {
3113
3390
  }
3114
3391
  async function readStdin() {
3115
3392
  const chunks = [];
3116
- for await (const chunk of process9.stdin) chunks.push(chunk);
3393
+ for await (const chunk of process10.stdin) chunks.push(chunk);
3117
3394
  return Buffer.concat(chunks);
3118
3395
  }
3119
3396
  function environmentArg(argv) {
@@ -3129,7 +3406,7 @@ function printUsage() {
3129
3406
  console.error("Usage: cofactor sandbox secret set <NAME> --application <name|id> [--environment <e>] [--json] (value on stdin)");
3130
3407
  console.error(" cofactor sandbox secret list --application <name|id> [--json]");
3131
3408
  console.error(" cofactor sandbox secret rm <NAME> --application <name|id> [--environment <e>] [--json]");
3132
- process9.exit(1);
3409
+ process10.exit(1);
3133
3410
  }
3134
3411
  async function handleSandboxSecret(argv, apiUrl, token) {
3135
3412
  const [action2, name, ...extra] = v2Positionals("secret", argv);
@@ -3145,10 +3422,10 @@ async function handleSandboxSecret(argv, apiUrl, token) {
3145
3422
  const json = hasFlag(argv, "--json");
3146
3423
  const application = getArgValue(argv, "--application") ?? getArgValue(argv, "--property") ?? "";
3147
3424
  if (action2 === "set") {
3148
- const refusal = stdinRefusal(process9.stdin.isTTY);
3425
+ const refusal = stdinRefusal(process10.stdin.isTTY);
3149
3426
  if (refusal) {
3150
3427
  console.error(`Error: ${refusal}`);
3151
- process9.exit(1);
3428
+ process10.exit(1);
3152
3429
  }
3153
3430
  }
3154
3431
  let value;
@@ -3167,7 +3444,7 @@ async function handleSandboxSecret(argv, apiUrl, token) {
3167
3444
  );
3168
3445
  if (!res2.ok) {
3169
3446
  console.error(`Error: ${res2.error}`);
3170
- process9.exit(1);
3447
+ process10.exit(1);
3171
3448
  }
3172
3449
  if (json) {
3173
3450
  writeRawOutput(JSON.stringify(res2.data, null, 2));
@@ -3196,7 +3473,7 @@ async function handleSandboxSecret(argv, apiUrl, token) {
3196
3473
  );
3197
3474
  if (!res.ok) {
3198
3475
  console.error(`Error: ${res.error}`);
3199
- process9.exit(1);
3476
+ process10.exit(1);
3200
3477
  }
3201
3478
  if (json) {
3202
3479
  writeRawOutput(JSON.stringify(res.data, null, 2));
@@ -3214,7 +3491,7 @@ var parseEgressMode = (value) => {
3214
3491
  console.error(
3215
3492
  `Error: invalid --egress-mode '${value}'. Must be one of: ${SANDBOX_EGRESS_MODES.join(", ")}`
3216
3493
  );
3217
- process10.exit(1);
3494
+ process11.exit(1);
3218
3495
  };
3219
3496
  var parseEgressAllowlist = (argv) => {
3220
3497
  const rawEntries = getArgValues(argv, "--egress-allow").flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean);
@@ -3224,13 +3501,13 @@ var parseEgressAllowlist = (argv) => {
3224
3501
  console.error(
3225
3502
  `Error: invalid --egress-allow '${entry}'. Use domain:example.com or cidr:203.0.113.0/24`
3226
3503
  );
3227
- process10.exit(1);
3504
+ process11.exit(1);
3228
3505
  }
3229
3506
  const kind = entry.slice(0, separator);
3230
3507
  const value = entry.slice(separator + 1);
3231
3508
  if (kind !== "domain" && kind !== "cidr") {
3232
3509
  console.error(`Error: invalid --egress-allow kind '${kind}'. Must be domain or cidr.`);
3233
- process10.exit(1);
3510
+ process11.exit(1);
3234
3511
  }
3235
3512
  return { kind, value };
3236
3513
  });
@@ -3287,7 +3564,8 @@ function printHelp() {
3287
3564
  ' sync <id> <local-dir> --to <path> Mirror local build output into <path> in the sandbox (see "Sync options" below)',
3288
3565
  " destroy <id> Tear a claimed sandbox down",
3289
3566
  ' warm list|set|show|clear [options] Keep an environment warm so claims start in about a second (staff only; see "Warm options" below)',
3290
- " verify-env --application <a> [options] Claim each environment of the current version, sign its personas in, destroy",
3567
+ " auth-check <id> --persona <p> Prove sign-in on an existing sandbox",
3568
+ " verify-env --application <a> [options] Claim each environment (optionally --at <sha>), sign its personas in, destroy",
3291
3569
  " preview --application <a> --pr <n> A pull request's preview and its logins",
3292
3570
  " guide [topic] Show the Sandboxes v2 guide (`guide v2 [topic]` is the same)",
3293
3571
  ` topics: ${SANDBOX_V2_GUIDE_SECTIONS.map((section) => section.slug).join(", ")}`,
@@ -3306,6 +3584,9 @@ function printHelp() {
3306
3584
  " --wait Block until Cofactor has imported (booted) the image",
3307
3585
  "",
3308
3586
  "Claim options (Sandboxes v2):",
3587
+ " --from-local Claim local tracked and nonignored untracked files without committing or merging",
3588
+ " --repo <owner/name> With --from-local: GitHub repository (default: origin)",
3589
+ " --dir <path> With --from-local: definition directory relative to the repository root (default: cwd)",
3309
3590
  ' --environment <name> Environment to claim (default: "default")',
3310
3591
  " --at <version> A commit SHA, or a push id or tag (default: the default branch head / push head)",
3311
3592
  " --timeout <duration> The claim's time limit: not ready by then, it fails (e.g. 45m, 3h; default 2h,",
@@ -3495,11 +3776,11 @@ async function handleList(argv, apiUrl, token) {
3495
3776
  ]);
3496
3777
  if (!templatesRes.ok) {
3497
3778
  console.error(`Error: ${templatesRes.error}`);
3498
- process10.exit(1);
3779
+ process11.exit(1);
3499
3780
  }
3500
3781
  if (!instancesRes.ok) {
3501
3782
  console.error(`Error loading instances: ${instancesRes.error}`);
3502
- process10.exit(1);
3783
+ process11.exit(1);
3503
3784
  }
3504
3785
  if (json) {
3505
3786
  writeRawOutput(JSON.stringify({ templates: templatesRes.data, instances: instancesRes.data }, null, 2));
@@ -3547,7 +3828,7 @@ async function handleTemplate(argv, apiUrl, token) {
3547
3828
  );
3548
3829
  if (!res.ok) {
3549
3830
  console.error(`Error: ${res.error}`);
3550
- process10.exit(1);
3831
+ process11.exit(1);
3551
3832
  }
3552
3833
  if (json) {
3553
3834
  writeRawOutput(JSON.stringify(res.data, null, 2));
@@ -3576,7 +3857,7 @@ async function handleArtifacts(argv, apiUrl, token) {
3576
3857
  const json = hasFlag(argv, "--json");
3577
3858
  if (!templateId) {
3578
3859
  console.error("Error: template ID required. Usage: canary sandbox artifacts <template-id>");
3579
- process10.exit(1);
3860
+ process11.exit(1);
3580
3861
  }
3581
3862
  const res = await apiRequest(
3582
3863
  apiUrl,
@@ -3586,7 +3867,7 @@ async function handleArtifacts(argv, apiUrl, token) {
3586
3867
  );
3587
3868
  if (!res.ok) {
3588
3869
  console.error(`Error: ${res.error}`);
3589
- process10.exit(1);
3870
+ process11.exit(1);
3590
3871
  }
3591
3872
  if (json) {
3592
3873
  writeRawOutput(JSON.stringify(res.data, null, 2));
@@ -3616,7 +3897,7 @@ async function handleChecklist(argv, apiUrl, token) {
3616
3897
  const templateId = positional[0];
3617
3898
  if (!templateId) {
3618
3899
  console.error("Error: template ID required. Usage: canary sandbox checklist <template-id>");
3619
- process10.exit(1);
3900
+ process11.exit(1);
3620
3901
  }
3621
3902
  const res = await apiRequest(
3622
3903
  apiUrl,
@@ -3626,7 +3907,7 @@ async function handleChecklist(argv, apiUrl, token) {
3626
3907
  );
3627
3908
  if (!res.ok) {
3628
3909
  console.error(`Error: ${res.error}`);
3629
- process10.exit(1);
3910
+ process11.exit(1);
3630
3911
  }
3631
3912
  if (json) {
3632
3913
  writeRawOutput(JSON.stringify(res.data, null, 2));
@@ -3661,7 +3942,7 @@ async function handleChecklistSkip(argv, apiUrl, token) {
3661
3942
  const itemId = positional[1];
3662
3943
  if (!templateId || !itemId) {
3663
3944
  console.error("Error: both template ID and item ID required. Usage: canary sandbox checklist skip <template-id> <item-id>");
3664
- process10.exit(1);
3945
+ process11.exit(1);
3665
3946
  }
3666
3947
  const res = await apiRequest(
3667
3948
  apiUrl,
@@ -3671,7 +3952,7 @@ async function handleChecklistSkip(argv, apiUrl, token) {
3671
3952
  );
3672
3953
  if (!res.ok) {
3673
3954
  console.error(`Error: ${res.error}`);
3674
- process10.exit(1);
3955
+ process11.exit(1);
3675
3956
  }
3676
3957
  console.log(`Skipped checklist item: ${itemId}`);
3677
3958
  }
@@ -3683,7 +3964,7 @@ async function handleChecklistComplete(argv, apiUrl, token) {
3683
3964
  if (!templateId || !itemId) {
3684
3965
  console.error("Error: both template ID and item ID required.");
3685
3966
  console.error('Usage: canary sandbox checklist complete <template-id> <item-id> [--status success|failed] [--summary "..."]');
3686
- process10.exit(1);
3967
+ process11.exit(1);
3687
3968
  }
3688
3969
  const status = getArgValue(argv, "--status") ?? "success";
3689
3970
  const summary = getArgValue(argv, "--summary");
@@ -3695,11 +3976,11 @@ async function handleChecklistComplete(argv, apiUrl, token) {
3695
3976
  configSchema = JSON.parse(configSchemaArg);
3696
3977
  } catch {
3697
3978
  try {
3698
- configSchema = JSON.parse(readFileSync6(configSchemaArg, "utf-8"));
3979
+ configSchema = JSON.parse(readFileSync7(configSchemaArg, "utf-8"));
3699
3980
  } catch (e) {
3700
3981
  const msg = e instanceof Error ? e.message : String(e);
3701
3982
  console.error(`Error reading config schema from "${configSchemaArg}": ${msg}`);
3702
- process10.exit(1);
3983
+ process11.exit(1);
3703
3984
  }
3704
3985
  }
3705
3986
  }
@@ -3718,7 +3999,7 @@ async function handleChecklistComplete(argv, apiUrl, token) {
3718
3999
  );
3719
4000
  if (!res.ok) {
3720
4001
  console.error(`Error: ${res.error}`);
3721
- process10.exit(1);
4002
+ process11.exit(1);
3722
4003
  }
3723
4004
  if (json) {
3724
4005
  writeRawOutput(JSON.stringify(res.data, null, 2));
@@ -3732,7 +4013,7 @@ async function handleConfigList(argv, apiUrl, token) {
3732
4013
  const json = hasFlag(argv, "--json");
3733
4014
  if (!templateId) {
3734
4015
  console.error("Error: template ID required. Usage: canary sandbox config list <template-id>");
3735
- process10.exit(1);
4016
+ process11.exit(1);
3736
4017
  }
3737
4018
  const res = await apiRequest(
3738
4019
  apiUrl,
@@ -3742,7 +4023,7 @@ async function handleConfigList(argv, apiUrl, token) {
3742
4023
  );
3743
4024
  if (!res.ok) {
3744
4025
  console.error(`Error: ${res.error}`);
3745
- process10.exit(1);
4026
+ process11.exit(1);
3746
4027
  }
3747
4028
  const t = res.data;
3748
4029
  const schema = t.configSchema ?? [];
@@ -3792,16 +4073,16 @@ async function handleConfigSet(argv, apiUrl, token) {
3792
4073
  console.error("Error: template ID and key required.");
3793
4074
  console.error("Usage: canary sandbox config set <template-id> <key> <value>");
3794
4075
  console.error(' For sensitive values: echo "sk-ant-..." | canary sandbox config set <template-id> <key> --value-stdin');
3795
- process10.exit(1);
4076
+ process11.exit(1);
3796
4077
  }
3797
4078
  if (fromStdin) {
3798
4079
  const chunks = [];
3799
- for await (const chunk of process10.stdin) chunks.push(chunk);
4080
+ for await (const chunk of process11.stdin) chunks.push(chunk);
3800
4081
  value = Buffer.concat(chunks).toString("utf-8").trim();
3801
4082
  }
3802
4083
  if (value === void 0) {
3803
4084
  console.error("Error: value required. Pass as argument or use --value-stdin");
3804
- process10.exit(1);
4085
+ process11.exit(1);
3805
4086
  }
3806
4087
  const res = await apiRequest(
3807
4088
  apiUrl,
@@ -3815,7 +4096,7 @@ async function handleConfigSet(argv, apiUrl, token) {
3815
4096
  );
3816
4097
  if (!res.ok) {
3817
4098
  console.error(`Error: ${res.error}`);
3818
- process10.exit(1);
4099
+ process11.exit(1);
3819
4100
  }
3820
4101
  console.log(`Config set: ${key}`);
3821
4102
  }
@@ -3823,7 +4104,7 @@ async function handleConfigUnset(argv, apiUrl, token) {
3823
4104
  const [templateId, key] = argv.filter((arg) => !arg.startsWith("-"));
3824
4105
  if (!templateId || !key) {
3825
4106
  console.error("Usage: canary sandbox config unset <template-id> <key>");
3826
- process10.exit(1);
4107
+ process11.exit(1);
3827
4108
  }
3828
4109
  const res = await apiRequest(
3829
4110
  apiUrl,
@@ -3835,7 +4116,7 @@ async function handleConfigUnset(argv, apiUrl, token) {
3835
4116
  );
3836
4117
  if (!res.ok) {
3837
4118
  console.error(`Error: ${res.error}`);
3838
- process10.exit(1);
4119
+ process11.exit(1);
3839
4120
  }
3840
4121
  console.log(`Config unset: ${key}`);
3841
4122
  }
@@ -3844,12 +4125,12 @@ async function handleSetSize(argv, apiUrl, token) {
3844
4125
  if (!templateId || !sizeArg) {
3845
4126
  console.error("Error: template ID and size required.");
3846
4127
  console.error(`Usage: canary sandbox set-size <template-id> <${INSTANCE_SIZES.join("|")}>`);
3847
- process10.exit(1);
4128
+ process11.exit(1);
3848
4129
  }
3849
4130
  const size = parseInstanceSize(sizeArg);
3850
4131
  if (!size) {
3851
4132
  console.error(`Error: invalid size '${sizeArg}'. Must be one of: ${INSTANCE_SIZES.join(", ")}`);
3852
- process10.exit(1);
4133
+ process11.exit(1);
3853
4134
  }
3854
4135
  const res = await apiRequest(
3855
4136
  apiUrl,
@@ -3860,7 +4141,7 @@ async function handleSetSize(argv, apiUrl, token) {
3860
4141
  );
3861
4142
  if (!res.ok) {
3862
4143
  console.error(`Error: ${res.error}`);
3863
- process10.exit(1);
4144
+ process11.exit(1);
3864
4145
  }
3865
4146
  console.log(`Instance size set to ${size}`);
3866
4147
  }
@@ -3869,12 +4150,12 @@ async function handlePoolSetSize(argv, apiUrl, token) {
3869
4150
  if (!templateId || countArg === void 0) {
3870
4151
  console.error("Error: template ID and count required.");
3871
4152
  console.error("Usage: canary sandbox pool set-size <template-id> <count>");
3872
- process10.exit(1);
4153
+ process11.exit(1);
3873
4154
  }
3874
4155
  const poolMinReady = /^\d+$/.test(countArg) ? Number(countArg) : Number.NaN;
3875
4156
  if (!Number.isInteger(poolMinReady)) {
3876
4157
  console.error(`Error: invalid count '${countArg}'. Must be a non-negative integer.`);
3877
- process10.exit(1);
4158
+ process11.exit(1);
3878
4159
  }
3879
4160
  const res = await apiRequest(
3880
4161
  apiUrl,
@@ -3885,7 +4166,7 @@ async function handlePoolSetSize(argv, apiUrl, token) {
3885
4166
  );
3886
4167
  if (!res.ok) {
3887
4168
  console.error(`Error: ${res.error}`);
3888
- process10.exit(1);
4169
+ process11.exit(1);
3889
4170
  }
3890
4171
  console.log(`Warm stopped-pool size set to ${poolMinReady}`);
3891
4172
  }
@@ -3894,7 +4175,7 @@ async function handleCreate(argv, apiUrl, token) {
3894
4175
  const json = hasFlag(argv, "--json");
3895
4176
  const propertyHint = positional[0] ?? getArgValue(argv, "--property");
3896
4177
  const property = await selectProperty(apiUrl, token, propertyHint ?? void 0);
3897
- if (!property) process10.exit(1);
4178
+ if (!property) process11.exit(1);
3898
4179
  const reposRes = await apiRequest(
3899
4180
  apiUrl,
3900
4181
  token,
@@ -3903,7 +4184,7 @@ async function handleCreate(argv, apiUrl, token) {
3903
4184
  );
3904
4185
  if (!reposRes.ok || !reposRes.items?.length) {
3905
4186
  console.error(`No GitHub repos linked to property "${property.name}". Link one first in the web app.`);
3906
- process10.exit(1);
4187
+ process11.exit(1);
3907
4188
  }
3908
4189
  const linkedRepo = reposRes.items[0];
3909
4190
  const branch = getArgValue(argv, "--branch") ?? linkedRepo.defaultBranch ?? "main";
@@ -3913,7 +4194,7 @@ async function handleCreate(argv, apiUrl, token) {
3913
4194
  const instanceSize = sizeArg ? parseInstanceSize(sizeArg) : void 0;
3914
4195
  if (sizeArg && !instanceSize) {
3915
4196
  console.error(`Error: invalid --size '${sizeArg}'. Must be one of: ${INSTANCE_SIZES.join(", ")}`);
3916
- process10.exit(1);
4197
+ process11.exit(1);
3917
4198
  }
3918
4199
  const body = {
3919
4200
  name,
@@ -3933,7 +4214,7 @@ async function handleCreate(argv, apiUrl, token) {
3933
4214
  );
3934
4215
  if (!res.ok) {
3935
4216
  console.error(`Error: ${res.error}`);
3936
- process10.exit(1);
4217
+ process11.exit(1);
3937
4218
  }
3938
4219
  if (json) {
3939
4220
  writeRawOutput(JSON.stringify(res.data, null, 2));
@@ -3953,7 +4234,7 @@ async function handleProvision(argv, apiUrl, token) {
3953
4234
  const json = hasFlag(argv, "--json");
3954
4235
  if (!templateId) {
3955
4236
  console.error("Error: template ID required. Usage: canary sandbox provision <template-id> [--ttl 8]");
3956
- process10.exit(1);
4237
+ process11.exit(1);
3957
4238
  }
3958
4239
  const name = getArgValue(argv, "--name") ?? `instance-${Date.now()}`;
3959
4240
  const ttl = parseInt(getArgValue(argv, "--ttl") ?? "8", 10);
@@ -3980,7 +4261,7 @@ async function handleProvision(argv, apiUrl, token) {
3980
4261
  );
3981
4262
  if (!res.ok) {
3982
4263
  console.error(`Error: ${res.error}`);
3983
- process10.exit(1);
4264
+ process11.exit(1);
3984
4265
  }
3985
4266
  if (json) {
3986
4267
  writeRawOutput(JSON.stringify(res.data, null, 2));
@@ -4005,7 +4286,7 @@ async function handleWarm(argv, apiUrl, token) {
4005
4286
  "Error: template ID required. Usage: canary sandbox warm <template-id> [--wait] [--timeout <sec>]"
4006
4287
  );
4007
4288
  console.error("Find the template id with: canary sandbox list");
4008
- process10.exit(1);
4289
+ process11.exit(1);
4009
4290
  }
4010
4291
  const ensure = () => apiRequest(
4011
4292
  apiUrl,
@@ -4020,7 +4301,7 @@ async function handleWarm(argv, apiUrl, token) {
4020
4301
  } else {
4021
4302
  console.error(`Error: ${first.error ?? "Failed to warm sandbox"}`);
4022
4303
  }
4023
- process10.exit(1);
4304
+ process11.exit(1);
4024
4305
  }
4025
4306
  const terminalOk = /* @__PURE__ */ new Set(["running"]);
4026
4307
  const terminalFail = /* @__PURE__ */ new Set(["failed", "terminated", "stopping"]);
@@ -4033,7 +4314,7 @@ async function handleWarm(argv, apiUrl, token) {
4033
4314
  const poll = await ensure();
4034
4315
  if (!poll.ok || !poll.data) {
4035
4316
  console.error(`Error polling sandbox: ${poll.error ?? "Unknown error"}`);
4036
- process10.exit(1);
4317
+ process11.exit(1);
4037
4318
  }
4038
4319
  current = poll.data;
4039
4320
  if (terminalOk.has(current.status) || terminalFail.has(current.status)) break;
@@ -4057,8 +4338,8 @@ async function handleWarm(argv, apiUrl, token) {
4057
4338
  console.log("\nWarming in the background. Poll with: canary sandbox warm " + templateId + " --wait");
4058
4339
  }
4059
4340
  }
4060
- if (terminalFail.has(current.status)) process10.exit(1);
4061
- if (wait && current.status !== "running") process10.exit(1);
4341
+ if (terminalFail.has(current.status)) process11.exit(1);
4342
+ if (wait && current.status !== "running") process11.exit(1);
4062
4343
  }
4063
4344
  function parseDockerPs(stdout) {
4064
4345
  const lines = stdout.split("\n").map((l) => l.trim()).filter(Boolean);
@@ -4103,7 +4384,7 @@ async function handleVerify(argv, apiUrl, token) {
4103
4384
  const autoTerminateOnFailure = hasFlag(argv, "--auto-terminate-on-failure");
4104
4385
  if (!templateId) {
4105
4386
  console.error("Error: template ID required. Usage: canary sandbox verify <template-id>");
4106
- process10.exit(1);
4387
+ process11.exit(1);
4107
4388
  }
4108
4389
  console.log(`Verifying template ${templateId}...`);
4109
4390
  const verifyRes = await apiRequest(
@@ -4115,7 +4396,7 @@ async function handleVerify(argv, apiUrl, token) {
4115
4396
  );
4116
4397
  if (!verifyRes.ok) {
4117
4398
  console.error(`Error: ${verifyRes.error}`);
4118
- process10.exit(1);
4399
+ process11.exit(1);
4119
4400
  }
4120
4401
  const instanceId = verifyRes.data.instanceId;
4121
4402
  console.log(` Verification instance: ${instanceId}`);
@@ -4130,7 +4411,7 @@ async function handleVerify(argv, apiUrl, token) {
4130
4411
  let routes = [];
4131
4412
  let routesFetched = false;
4132
4413
  while (Date.now() - startTime < TIMEOUT_MS) {
4133
- await new Promise((resolve3) => setTimeout(resolve3, POLL_INTERVAL_MS2));
4414
+ await new Promise((resolve4) => setTimeout(resolve4, POLL_INTERVAL_MS2));
4134
4415
  const diag = await apiRequest(
4135
4416
  apiUrl,
4136
4417
  token,
@@ -4151,23 +4432,23 @@ async function handleVerify(argv, apiUrl, token) {
4151
4432
  const containers = parseDockerPs(dockerOutput);
4152
4433
  const broken = containers.filter(isBrokenContainer);
4153
4434
  const ttl = remaining > 60 ? `${elapsed}s elapsed` : `${remaining}s remaining`;
4154
- process10.stdout.write(
4435
+ process11.stdout.write(
4155
4436
  `\r [${ttl}] status: ${status}, app: ${appUp ? "up" : "waiting"}, containers: ${containers.length}, unhealthy: ${broken.length} `
4156
4437
  );
4157
4438
  if (status === "running" && appUp) {
4158
- process10.stdout.write("\n");
4439
+ process11.stdout.write("\n");
4159
4440
  finalStatus = "running";
4160
4441
  break;
4161
4442
  }
4162
4443
  if (status === "failed" || status === "terminated") {
4163
- process10.stdout.write("\n");
4444
+ process11.stdout.write("\n");
4164
4445
  finalStatus = status;
4165
4446
  break;
4166
4447
  }
4167
4448
  const shouldDump = !appUp && broken.length > 0 && now - lastDetailDumpAt >= DETAIL_DUMP_INTERVAL_MS;
4168
4449
  if (shouldDump) {
4169
4450
  lastDetailDumpAt = now;
4170
- process10.stdout.write("\n");
4451
+ process11.stdout.write("\n");
4171
4452
  console.log("");
4172
4453
  console.log(` App unreachable after ${elapsed}s. Container state:`);
4173
4454
  for (const c of containers) {
@@ -4232,7 +4513,7 @@ async function handleVerify(argv, apiUrl, token) {
4232
4513
  console.error(` Terminate manually with:`);
4233
4514
  console.error(` canary sandbox terminate ${instanceId}`);
4234
4515
  }
4235
- process10.exit(1);
4516
+ process11.exit(1);
4236
4517
  }
4237
4518
  const evidenceSummary = `Verified via canary sandbox verify \u2014 fresh instance ${instanceId} came up healthy.`;
4238
4519
  for (const itemId of ["server-running", "server-accessible"]) {
@@ -4258,7 +4539,7 @@ async function handleVerify(argv, apiUrl, token) {
4258
4539
  if (!completeRes.ok) {
4259
4540
  console.error(`Failed to record verification: ${completeRes.error}`);
4260
4541
  console.error(`Instance ${instanceId} left running for inspection.`);
4261
- process10.exit(1);
4542
+ process11.exit(1);
4262
4543
  }
4263
4544
  if (!keepInstance) {
4264
4545
  await apiRequest(apiUrl, token, "POST", `/org/environments/instances/${instanceId}/terminate`);
@@ -4303,7 +4584,7 @@ async function handleVerify(argv, apiUrl, token) {
4303
4584
  console.log('Complete: canary sandbox checklist complete <template-id> <item-id> --status success --summary "..."');
4304
4585
  console.log("Skip: canary sandbox checklist skip <template-id> <item-id>");
4305
4586
  }
4306
- process10.exit(1);
4587
+ process11.exit(1);
4307
4588
  }
4308
4589
  async function handleArtifactGet(argv, apiUrl, token) {
4309
4590
  const positional = argv.filter((a) => !a.startsWith("-"));
@@ -4312,7 +4593,7 @@ async function handleArtifactGet(argv, apiUrl, token) {
4312
4593
  const outputFile = getArgValue(argv, "--output");
4313
4594
  if (!templateId || !type) {
4314
4595
  console.error("Error: template ID and artifact type required. Usage: canary sandbox artifact get <template-id> <type>");
4315
- process10.exit(1);
4596
+ process11.exit(1);
4316
4597
  }
4317
4598
  const res = await apiRequest(
4318
4599
  apiUrl,
@@ -4322,14 +4603,14 @@ async function handleArtifactGet(argv, apiUrl, token) {
4322
4603
  );
4323
4604
  if (!res.ok) {
4324
4605
  console.error(`Error: ${res.error}`);
4325
- process10.exit(1);
4606
+ process11.exit(1);
4326
4607
  }
4327
4608
  if (outputFile) {
4328
- const { writeFileSync } = await import("fs");
4329
- writeFileSync(outputFile, res.data.content ?? "", "utf-8");
4609
+ const { writeFileSync: writeFileSync2 } = await import("fs");
4610
+ writeFileSync2(outputFile, res.data.content ?? "", "utf-8");
4330
4611
  console.log(`Written to ${outputFile}`);
4331
4612
  } else {
4332
- process10.stdout.write(res.data.content ?? "");
4613
+ process11.stdout.write(res.data.content ?? "");
4333
4614
  }
4334
4615
  }
4335
4616
  async function handleArtifactCreate(argv, apiUrl, token) {
@@ -4340,17 +4621,17 @@ async function handleArtifactCreate(argv, apiUrl, token) {
4340
4621
  const json = hasFlag(argv, "--json");
4341
4622
  if (!templateId || !type || !filePath) {
4342
4623
  console.error("Error: all args required. Usage: canary sandbox artifact create <template-id> <type> <file>");
4343
- process10.exit(1);
4624
+ process11.exit(1);
4344
4625
  }
4345
4626
  const WELL_KNOWN_TYPES = ["docker-compose", "dockerfile", "startup-script", "setup-script", "seed-script", "environment-config", "file"];
4346
4627
  if (!WELL_KNOWN_TYPES.includes(type)) {
4347
4628
  console.error(`Error: invalid artifact type "${type}". Valid types: ${WELL_KNOWN_TYPES.join(", ")}`);
4348
- process10.exit(1);
4629
+ process11.exit(1);
4349
4630
  }
4350
4631
  const destinationPath = getArgValue(argv, "--path");
4351
4632
  if (type === "file" && !destinationPath) {
4352
4633
  console.error("Error: file artifacts require --path <destination>. Example: --path /workspace/.canary/vite.sandbox.config.ts");
4353
- process10.exit(1);
4634
+ process11.exit(1);
4354
4635
  }
4355
4636
  if (destinationPath && !destinationPath.startsWith("/workspace/.canary/")) {
4356
4637
  const suggestedName = destinationPath.split("/").filter(Boolean).pop() ?? "yourfile";
@@ -4358,14 +4639,14 @@ async function handleArtifactCreate(argv, apiUrl, token) {
4358
4639
  `Error: --path must be under /workspace/.canary/ (the repo tree is user-owned \u2014 sandbox files live in .canary/).
4359
4640
  try: --path /workspace/.canary/${suggestedName}`
4360
4641
  );
4361
- process10.exit(1);
4642
+ process11.exit(1);
4362
4643
  }
4363
4644
  let content;
4364
4645
  try {
4365
- content = readFileSync6(filePath, "utf-8");
4646
+ content = readFileSync7(filePath, "utf-8");
4366
4647
  } catch (err) {
4367
4648
  console.error(`Error reading file: ${err}`);
4368
- process10.exit(1);
4649
+ process11.exit(1);
4369
4650
  }
4370
4651
  const filename = filePath.split("/").pop() ?? filePath;
4371
4652
  const res = await apiRequest(
@@ -4383,7 +4664,7 @@ async function handleArtifactCreate(argv, apiUrl, token) {
4383
4664
  );
4384
4665
  if (!res.ok) {
4385
4666
  console.error(`Error: ${res.error}`);
4386
- process10.exit(1);
4667
+ process11.exit(1);
4387
4668
  }
4388
4669
  if (json) {
4389
4670
  writeRawOutput(JSON.stringify(res.data, null, 2));
@@ -4403,7 +4684,7 @@ async function handleBuild(argv, apiUrl, token) {
4403
4684
  const instanceId = positional[0];
4404
4685
  if (!instanceId) {
4405
4686
  console.error("Error: instance ID required. Usage: canary sandbox build <instance-id>");
4406
- process10.exit(1);
4687
+ process11.exit(1);
4407
4688
  }
4408
4689
  console.log("Building sandbox (this takes a few minutes)...");
4409
4690
  const KICKOFF_TIMEOUT_MS = 8e3;
@@ -4425,7 +4706,7 @@ async function handleBuild(argv, apiUrl, token) {
4425
4706
  buildResult = data.data;
4426
4707
  } else if (data && !data.ok) {
4427
4708
  console.error(`Error: ${data.error ?? "build failed"}`);
4428
- process10.exit(1);
4709
+ process11.exit(1);
4429
4710
  }
4430
4711
  } else {
4431
4712
  const { failure } = result;
@@ -4433,7 +4714,7 @@ async function handleBuild(argv, apiUrl, token) {
4433
4714
  if (!isExpectedAsync) {
4434
4715
  if (isTerminalFailure(failure)) exitOnFailure(failure);
4435
4716
  console.error(`Error: ${failure.message}`);
4436
- process10.exit(1);
4717
+ process11.exit(1);
4437
4718
  }
4438
4719
  }
4439
4720
  if (buildCompleted && buildResult) {
@@ -4442,18 +4723,18 @@ async function handleBuild(argv, apiUrl, token) {
4442
4723
  return;
4443
4724
  }
4444
4725
  if (buildResult.stdout) {
4445
- process10.stdout.write(buildResult.stdout);
4446
- if (!buildResult.stdout.endsWith("\n")) process10.stdout.write("\n");
4726
+ process11.stdout.write(buildResult.stdout);
4727
+ if (!buildResult.stdout.endsWith("\n")) process11.stdout.write("\n");
4447
4728
  }
4448
4729
  if (buildResult.stderr) {
4449
- process10.stderr.write(`STDERR: ${buildResult.stderr}`);
4450
- if (!buildResult.stderr.endsWith("\n")) process10.stderr.write("\n");
4730
+ process11.stderr.write(`STDERR: ${buildResult.stderr}`);
4731
+ if (!buildResult.stderr.endsWith("\n")) process11.stderr.write("\n");
4451
4732
  }
4452
4733
  if (buildResult.exitCode === 0) {
4453
4734
  console.log("Build complete.");
4454
4735
  } else {
4455
4736
  console.error(`Build failed with exit code ${buildResult.exitCode}`);
4456
- process10.exit(1);
4737
+ process11.exit(1);
4457
4738
  }
4458
4739
  return;
4459
4740
  }
@@ -4467,7 +4748,7 @@ async function handleBuild(argv, apiUrl, token) {
4467
4748
  const POLL_INTERVAL_MS2 = 1e4;
4468
4749
  const startTime = Date.now();
4469
4750
  while (Date.now() - startTime < BUILD_POLL_TIMEOUT_MS) {
4470
- await new Promise((resolve3) => setTimeout(resolve3, POLL_INTERVAL_MS2));
4751
+ await new Promise((resolve4) => setTimeout(resolve4, POLL_INTERVAL_MS2));
4471
4752
  const diag = await apiRequest(
4472
4753
  apiUrl,
4473
4754
  token,
@@ -4483,18 +4764,18 @@ async function handleBuild(argv, apiUrl, token) {
4483
4764
  const appUp = appOutput.length > 0 && !appOutput.includes("UNREACHABLE");
4484
4765
  const containerCount = (dockerOutput.match(/\n/g) || []).length + (dockerOutput ? 1 : 0);
4485
4766
  const healthyCount = (dockerOutput.match(/\(healthy\)/g) || []).length;
4486
- process10.stdout.write(`\r [${elapsed}s] containers: ${containerCount}, healthy: ${healthyCount}, app: ${appUp ? "up" : "waiting"} `);
4767
+ process11.stdout.write(`\r [${elapsed}s] containers: ${containerCount}, healthy: ${healthyCount}, app: ${appUp ? "up" : "waiting"} `);
4487
4768
  if (appUp) {
4488
- process10.stdout.write("\n");
4769
+ process11.stdout.write("\n");
4489
4770
  console.log("Build complete.");
4490
4771
  return;
4491
4772
  }
4492
4773
  }
4493
- process10.stdout.write("\n");
4774
+ process11.stdout.write("\n");
4494
4775
  console.error("Build timed out waiting for the app to respond. Check diagnostics and logs:");
4495
4776
  console.error(` canary sandbox diagnostics ${instanceId}`);
4496
4777
  console.error(` canary sandbox logs ${instanceId} --service <name>`);
4497
- process10.exit(1);
4778
+ process11.exit(1);
4498
4779
  }
4499
4780
  async function handleRunCommand(argv, apiUrl, token) {
4500
4781
  const json = hasFlag(argv, "--json");
@@ -4511,7 +4792,7 @@ async function handleRunCommand(argv, apiUrl, token) {
4511
4792
  const command = firstNonFlag >= 0 ? filtered.slice(firstNonFlag + 1).join(" ") : "";
4512
4793
  if (!instanceId || !command) {
4513
4794
  console.error("Error: instance ID and command required. Usage: canary sandbox run-command <instance-id> [--service <name>] [--timeout <ms>] <command...>");
4514
- process10.exit(1);
4795
+ process11.exit(1);
4515
4796
  }
4516
4797
  const timeoutMs = timeoutFlag ? Math.max(parseInt(timeoutFlag, 10), 1e3) : 6e4;
4517
4798
  const res = await apiRequest(
@@ -4524,19 +4805,19 @@ async function handleRunCommand(argv, apiUrl, token) {
4524
4805
  );
4525
4806
  if (!res.ok) {
4526
4807
  console.error(`Error: ${res.error}`);
4527
- process10.exit(1);
4808
+ process11.exit(1);
4528
4809
  }
4529
4810
  if (json) {
4530
4811
  writeRawOutput(JSON.stringify(res.data, null, 2));
4531
4812
  return;
4532
4813
  }
4533
4814
  if (res.data.stdout) {
4534
- process10.stdout.write(res.data.stdout);
4535
- if (!res.data.stdout.endsWith("\n")) process10.stdout.write("\n");
4815
+ process11.stdout.write(res.data.stdout);
4816
+ if (!res.data.stdout.endsWith("\n")) process11.stdout.write("\n");
4536
4817
  }
4537
4818
  if (res.data.stderr) {
4538
- process10.stderr.write(`STDERR: ${res.data.stderr}`);
4539
- if (!res.data.stderr.endsWith("\n")) process10.stderr.write("\n");
4819
+ process11.stderr.write(`STDERR: ${res.data.stderr}`);
4820
+ if (!res.data.stderr.endsWith("\n")) process11.stderr.write("\n");
4540
4821
  }
4541
4822
  if (res.data.exitCode !== 0) {
4542
4823
  console.log(`Exit code: ${res.data.exitCode}`);
@@ -4548,7 +4829,7 @@ async function handleDiagnostics(argv, apiUrl, token) {
4548
4829
  const json = hasFlag(argv, "--json");
4549
4830
  if (!instanceId) {
4550
4831
  console.error("Error: instance ID required. Usage: canary sandbox diagnostics <instance-id>");
4551
- process10.exit(1);
4832
+ process11.exit(1);
4552
4833
  }
4553
4834
  const res = await apiRequest(
4554
4835
  apiUrl,
@@ -4560,7 +4841,7 @@ async function handleDiagnostics(argv, apiUrl, token) {
4560
4841
  );
4561
4842
  if (!res.ok) {
4562
4843
  console.error(`Error: ${res.error}`);
4563
- process10.exit(1);
4844
+ process11.exit(1);
4564
4845
  }
4565
4846
  const routes = await fetchInstanceRoutes(apiUrl, token, instanceId);
4566
4847
  const data = { ...res.data, routes };
@@ -4615,7 +4896,7 @@ async function handleTerminate(argv, apiUrl, token) {
4615
4896
  const instanceId = positional[0];
4616
4897
  if (!instanceId) {
4617
4898
  console.error("Error: instance ID required. Usage: canary sandbox terminate <instance-id>");
4618
- process10.exit(1);
4899
+ process11.exit(1);
4619
4900
  }
4620
4901
  const res = await apiRequest(
4621
4902
  apiUrl,
@@ -4625,7 +4906,7 @@ async function handleTerminate(argv, apiUrl, token) {
4625
4906
  );
4626
4907
  if (!res.ok) {
4627
4908
  console.error(`Error: ${res.error}`);
4628
- process10.exit(1);
4909
+ process11.exit(1);
4629
4910
  }
4630
4911
  console.log(`Instance ${instanceId} termination initiated.`);
4631
4912
  }
@@ -4634,7 +4915,7 @@ async function handleDelete(argv, apiUrl, token) {
4634
4915
  const templateId = positional[0];
4635
4916
  if (!templateId) {
4636
4917
  console.error("Error: template ID required. Usage: canary sandbox delete <template-id>");
4637
- process10.exit(1);
4918
+ process11.exit(1);
4638
4919
  }
4639
4920
  const res = await apiRequest(
4640
4921
  apiUrl,
@@ -4644,7 +4925,7 @@ async function handleDelete(argv, apiUrl, token) {
4644
4925
  );
4645
4926
  if (!res.ok) {
4646
4927
  console.error(`Error: ${res.error}`);
4647
- process10.exit(1);
4928
+ process11.exit(1);
4648
4929
  }
4649
4930
  console.log(`Template ${templateId} deleted.`);
4650
4931
  }
@@ -4654,7 +4935,7 @@ async function handleActionList(argv, apiUrl, token) {
4654
4935
  const json = hasFlag(argv, "--json");
4655
4936
  if (!templateId) {
4656
4937
  console.error("Error: template ID required. Usage: canary sandbox action list <template-id>");
4657
- process10.exit(1);
4938
+ process11.exit(1);
4658
4939
  }
4659
4940
  const res = await apiRequest(
4660
4941
  apiUrl,
@@ -4664,7 +4945,7 @@ async function handleActionList(argv, apiUrl, token) {
4664
4945
  );
4665
4946
  if (!res.ok) {
4666
4947
  console.error(`Error: ${res.error}`);
4667
- process10.exit(1);
4948
+ process11.exit(1);
4668
4949
  }
4669
4950
  if (json) {
4670
4951
  writeRawOutput(JSON.stringify(res.data, null, 2));
@@ -4688,7 +4969,7 @@ async function handleActionGet(argv, apiUrl, token) {
4688
4969
  const json = hasFlag(argv, "--json");
4689
4970
  if (!templateId || !slug) {
4690
4971
  console.error("Error: template ID and slug required. Usage: canary sandbox action get <template-id> <slug>");
4691
- process10.exit(1);
4972
+ process11.exit(1);
4692
4973
  }
4693
4974
  const res = await apiRequest(
4694
4975
  apiUrl,
@@ -4698,7 +4979,7 @@ async function handleActionGet(argv, apiUrl, token) {
4698
4979
  );
4699
4980
  if (!res.ok) {
4700
4981
  console.error(`Error: ${res.error}`);
4701
- process10.exit(1);
4982
+ process11.exit(1);
4702
4983
  }
4703
4984
  if (json) {
4704
4985
  writeRawOutput(JSON.stringify(res.data, null, 2));
@@ -4731,7 +5012,7 @@ async function handleActionCreate(argv, apiUrl, token) {
4731
5012
  const json = hasFlag(argv, "--json");
4732
5013
  if (!templateId || !slug) {
4733
5014
  console.error("Error: template ID and slug required. Usage: canary sandbox action create <template-id> <slug> [options]");
4734
- process10.exit(1);
5015
+ process11.exit(1);
4735
5016
  }
4736
5017
  const scriptPath = getArgValue(argv, "--script");
4737
5018
  const inputSchemaPath = getArgValue(argv, "--input-schema");
@@ -4749,63 +5030,63 @@ async function handleActionCreate(argv, apiUrl, token) {
4749
5030
  const provisionOrderStr = getArgValue(argv, "--provision-order");
4750
5031
  if (provisioningMode && provisioningMode !== "shared" && provisioningMode !== "unique") {
4751
5032
  console.error("Error: --provisioning-mode must be 'shared' or 'unique'");
4752
- process10.exit(1);
5033
+ process11.exit(1);
4753
5034
  }
4754
5035
  if (provisionOrderStr && !/^-?\d+$/.test(provisionOrderStr)) {
4755
5036
  console.error("Error: --provision-order must be an integer");
4756
- process10.exit(1);
5037
+ process11.exit(1);
4757
5038
  }
4758
5039
  if (!scriptPath) {
4759
5040
  console.error("Error: --script <file> is required");
4760
- process10.exit(1);
5041
+ process11.exit(1);
4761
5042
  }
4762
5043
  if (!inputSchemaPath) {
4763
5044
  console.error("Error: --input-schema <file> is required");
4764
- process10.exit(1);
5045
+ process11.exit(1);
4765
5046
  }
4766
5047
  let scriptContent;
4767
5048
  try {
4768
- scriptContent = readFileSync6(scriptPath, "utf-8");
5049
+ scriptContent = readFileSync7(scriptPath, "utf-8");
4769
5050
  } catch (err) {
4770
5051
  console.error(`Error reading script file: ${err}`);
4771
- process10.exit(1);
5052
+ process11.exit(1);
4772
5053
  }
4773
5054
  let inputSchema;
4774
5055
  try {
4775
- inputSchema = JSON.parse(readFileSync6(inputSchemaPath, "utf-8"));
5056
+ inputSchema = JSON.parse(readFileSync7(inputSchemaPath, "utf-8"));
4776
5057
  } catch (err) {
4777
5058
  console.error(`Error reading input schema file: ${err}`);
4778
- process10.exit(1);
5059
+ process11.exit(1);
4779
5060
  }
4780
5061
  let outputSchema;
4781
5062
  if (outputSchemaPath) {
4782
5063
  try {
4783
- outputSchema = JSON.parse(readFileSync6(outputSchemaPath, "utf-8"));
5064
+ outputSchema = JSON.parse(readFileSync7(outputSchemaPath, "utf-8"));
4784
5065
  } catch (err) {
4785
5066
  console.error(`Error reading output schema file: ${err}`);
4786
- process10.exit(1);
5067
+ process11.exit(1);
4787
5068
  }
4788
5069
  }
4789
5070
  let credentialOutputConfig;
4790
5071
  if (credentialOutputConfigPath) {
4791
5072
  try {
4792
- credentialOutputConfig = JSON.parse(readFileSync6(credentialOutputConfigPath, "utf-8"));
5073
+ credentialOutputConfig = JSON.parse(readFileSync7(credentialOutputConfigPath, "utf-8"));
4793
5074
  } catch (err) {
4794
5075
  console.error(`Error reading credential output config file: ${err}`);
4795
- process10.exit(1);
5076
+ process11.exit(1);
4796
5077
  }
4797
5078
  }
4798
5079
  let outputConfig;
4799
5080
  if (outputConfigPath) {
4800
5081
  try {
4801
- outputConfig = JSON.parse(readFileSync6(outputConfigPath, "utf-8"));
5082
+ outputConfig = JSON.parse(readFileSync7(outputConfigPath, "utf-8"));
4802
5083
  } catch (err) {
4803
5084
  console.error(`Error reading output config file: ${err}`);
4804
- process10.exit(1);
5085
+ process11.exit(1);
4805
5086
  }
4806
5087
  if (!Array.isArray(outputConfig)) {
4807
5088
  console.error("Error: --output-config must contain a JSON array of output slots");
4808
- process10.exit(1);
5089
+ process11.exit(1);
4809
5090
  }
4810
5091
  }
4811
5092
  const body = {
@@ -4833,7 +5114,7 @@ async function handleActionCreate(argv, apiUrl, token) {
4833
5114
  );
4834
5115
  if (!res.ok) {
4835
5116
  console.error(`Error: ${res.error}`);
4836
- process10.exit(1);
5117
+ process11.exit(1);
4837
5118
  }
4838
5119
  if (json) {
4839
5120
  writeRawOutput(JSON.stringify(res.data, null, 2));
@@ -4847,7 +5128,7 @@ async function handleActionDelete(argv, apiUrl, token) {
4847
5128
  const slug = positional[1];
4848
5129
  if (!templateId || !slug) {
4849
5130
  console.error("Error: template ID and slug required. Usage: canary sandbox action delete <template-id> <slug>");
4850
- process10.exit(1);
5131
+ process11.exit(1);
4851
5132
  }
4852
5133
  const res = await apiRequest(
4853
5134
  apiUrl,
@@ -4857,7 +5138,7 @@ async function handleActionDelete(argv, apiUrl, token) {
4857
5138
  );
4858
5139
  if (!res.ok) {
4859
5140
  console.error(`Error: ${res.error}`);
4860
- process10.exit(1);
5141
+ process11.exit(1);
4861
5142
  }
4862
5143
  console.log(`Action '${slug}' deleted.`);
4863
5144
  }
@@ -4868,16 +5149,16 @@ async function handleActionExecute(argv, apiUrl, token) {
4868
5149
  const json = hasFlag(argv, "--json");
4869
5150
  if (!instanceId || !slug) {
4870
5151
  console.error("Error: instance ID and slug required. Usage: canary sandbox action execute <instance-id> <slug> [options]");
4871
- process10.exit(1);
5152
+ process11.exit(1);
4872
5153
  }
4873
5154
  let params = {};
4874
5155
  const paramsFile = getArgValue(argv, "--params-file");
4875
5156
  if (paramsFile) {
4876
5157
  try {
4877
- params = JSON.parse(readFileSync6(paramsFile, "utf-8"));
5158
+ params = JSON.parse(readFileSync7(paramsFile, "utf-8"));
4878
5159
  } catch (err) {
4879
5160
  console.error(`Error reading params file: ${err}`);
4880
- process10.exit(1);
5161
+ process11.exit(1);
4881
5162
  }
4882
5163
  }
4883
5164
  for (let i = 0; i < argv.length; i++) {
@@ -4896,11 +5177,11 @@ async function handleActionExecute(argv, apiUrl, token) {
4896
5177
  const parsed = Number(timeoutArg);
4897
5178
  if (!Number.isFinite(parsed) || parsed <= 0) {
4898
5179
  console.error(`Error: --timeout-ms must be a positive number, got '${timeoutArg}'`);
4899
- process10.exit(1);
5180
+ process11.exit(1);
4900
5181
  }
4901
5182
  if (!runAsync) {
4902
5183
  console.error("Error: --timeout-ms only applies with --async");
4903
- process10.exit(1);
5184
+ process11.exit(1);
4904
5185
  }
4905
5186
  executionTimeoutMs = parsed;
4906
5187
  }
@@ -4918,7 +5199,7 @@ async function handleActionExecute(argv, apiUrl, token) {
4918
5199
  );
4919
5200
  if (!res.ok) {
4920
5201
  console.error(`Error: ${res.error}`);
4921
- process10.exit(1);
5202
+ process11.exit(1);
4922
5203
  }
4923
5204
  if (json) {
4924
5205
  writeRawOutput(JSON.stringify(res.data, null, 2));
@@ -4959,7 +5240,7 @@ async function handleActionExecutions(argv, apiUrl, token) {
4959
5240
  const json = hasFlag(argv, "--json");
4960
5241
  if (!instanceId) {
4961
5242
  console.error("Error: instance ID required. Usage: canary sandbox action executions <instance-id>");
4962
- process10.exit(1);
5243
+ process11.exit(1);
4963
5244
  }
4964
5245
  const res = await apiRequest(
4965
5246
  apiUrl,
@@ -4969,7 +5250,7 @@ async function handleActionExecutions(argv, apiUrl, token) {
4969
5250
  );
4970
5251
  if (!res.ok) {
4971
5252
  console.error(`Error: ${res.error}`);
4972
- process10.exit(1);
5253
+ process11.exit(1);
4973
5254
  }
4974
5255
  if (json) {
4975
5256
  writeRawOutput(JSON.stringify(res.data, null, 2));
@@ -5013,7 +5294,7 @@ async function handleActionExecutionGet(argv, apiUrl, token) {
5013
5294
  console.error(
5014
5295
  "Error: instance ID and execution ID required. Usage: cofactor sandbox action execution get <instance-id> <execution-id>"
5015
5296
  );
5016
- process10.exit(1);
5297
+ process11.exit(1);
5017
5298
  }
5018
5299
  const res = await apiRequest(
5019
5300
  apiUrl,
@@ -5023,7 +5304,7 @@ async function handleActionExecutionGet(argv, apiUrl, token) {
5023
5304
  );
5024
5305
  if (!res.ok) {
5025
5306
  console.error(`Error: ${res.error}`);
5026
- process10.exit(1);
5307
+ process11.exit(1);
5027
5308
  }
5028
5309
  if (json) {
5029
5310
  writeRawOutput(JSON.stringify(res.data, null, 2));
@@ -5040,7 +5321,7 @@ async function handleActionExecutionCancel(argv, apiUrl, token) {
5040
5321
  console.error(
5041
5322
  "Error: instance ID and execution ID required. Usage: cofactor sandbox action execution cancel <instance-id> <execution-id>"
5042
5323
  );
5043
- process10.exit(1);
5324
+ process11.exit(1);
5044
5325
  }
5045
5326
  const res = await apiRequest(
5046
5327
  apiUrl,
@@ -5050,7 +5331,7 @@ async function handleActionExecutionCancel(argv, apiUrl, token) {
5050
5331
  );
5051
5332
  if (!res.ok) {
5052
5333
  console.error(`Error: ${res.error}`);
5053
- process10.exit(1);
5334
+ process11.exit(1);
5054
5335
  }
5055
5336
  if (json) {
5056
5337
  writeRawOutput(JSON.stringify(res.data, null, 2));
@@ -5085,7 +5366,7 @@ async function handleRepoList(argv, apiUrl, token) {
5085
5366
  const templateId = getArgValue(argv, "--template");
5086
5367
  if (!templateId) {
5087
5368
  console.error("Error: --template is required.");
5088
- process10.exit(1);
5369
+ process11.exit(1);
5089
5370
  }
5090
5371
  const result = await apiRequest(
5091
5372
  apiUrl,
@@ -5095,7 +5376,7 @@ async function handleRepoList(argv, apiUrl, token) {
5095
5376
  );
5096
5377
  if (!result.ok) {
5097
5378
  console.error(`Error: ${result.error ?? "Unknown error"}`);
5098
- process10.exit(1);
5379
+ process11.exit(1);
5099
5380
  }
5100
5381
  if (json) {
5101
5382
  writeRawOutput(JSON.stringify(result.items ?? [], null, 2));
@@ -5124,7 +5405,7 @@ async function handleRepoAdd(argv, apiUrl, token) {
5124
5405
  if (!templateId || !propertyGithubRepoId) {
5125
5406
  console.error("Error: --template and --property-github-repo-id are required.");
5126
5407
  console.error("Tip: link the repo to the property first via `canary property repo link`, then pass its id here.");
5127
- process10.exit(1);
5408
+ process11.exit(1);
5128
5409
  }
5129
5410
  const parsed = spec ? parseRepoSpec(spec) : {};
5130
5411
  const body = { propertyGithubRepoId };
@@ -5145,7 +5426,7 @@ async function handleRepoAdd(argv, apiUrl, token) {
5145
5426
  if (!result.ok) {
5146
5427
  if (json) writeRawOutput(JSON.stringify(result, null, 2));
5147
5428
  else console.error(`Error: ${result.error ?? "Unknown error"}`);
5148
- process10.exit(1);
5429
+ process11.exit(1);
5149
5430
  }
5150
5431
  if (json) writeRawOutput(JSON.stringify(result.data, null, 2));
5151
5432
  else console.log(`Linked repo to template ${templateId} (link=${result.data?.id})`);
@@ -5160,7 +5441,7 @@ async function handleRepoUpdate(argv, apiUrl, token) {
5160
5441
  const serviceName = getArgValue(argv, "--service-name");
5161
5442
  if (!templateId || !linkId) {
5162
5443
  console.error("Error: --template and --link-id are required.");
5163
- process10.exit(1);
5444
+ process11.exit(1);
5164
5445
  }
5165
5446
  const body = {};
5166
5447
  if (role !== void 0) body.role = role;
@@ -5177,7 +5458,7 @@ async function handleRepoUpdate(argv, apiUrl, token) {
5177
5458
  if (!result.ok) {
5178
5459
  if (json) writeRawOutput(JSON.stringify(result, null, 2));
5179
5460
  else console.error(`Error: ${result.error ?? "Unknown error"}`);
5180
- process10.exit(1);
5461
+ process11.exit(1);
5181
5462
  }
5182
5463
  if (json) writeRawOutput(JSON.stringify(result.data, null, 2));
5183
5464
  else console.log(`Updated repo link ${linkId}`);
@@ -5188,7 +5469,7 @@ async function handleRepoRemove(argv, apiUrl, token) {
5188
5469
  const linkId = getArgValue(argv, "--link-id");
5189
5470
  if (!templateId || !linkId) {
5190
5471
  console.error("Error: --template and --link-id are required.");
5191
- process10.exit(1);
5472
+ process11.exit(1);
5192
5473
  }
5193
5474
  const result = await apiRequest(
5194
5475
  apiUrl,
@@ -5199,7 +5480,7 @@ async function handleRepoRemove(argv, apiUrl, token) {
5199
5480
  if (!result.ok) {
5200
5481
  if (json) writeRawOutput(JSON.stringify(result, null, 2));
5201
5482
  else console.error(`Error: ${result.error ?? "Unknown error"}`);
5202
- process10.exit(1);
5483
+ process11.exit(1);
5203
5484
  }
5204
5485
  if (json) writeRawOutput(JSON.stringify(result, null, 2));
5205
5486
  else console.log(`Removed repo link ${linkId} from template ${templateId}`);
@@ -5228,7 +5509,7 @@ Examples:
5228
5509
  canary sandbox repo add --template <template-id> --property-github-repo-id <uuid> --role frontend
5229
5510
  canary sandbox repo add --template <template-id> --property-github-repo-id <uuid> --spec frontend=owner/web@main:/apps/web`
5230
5511
  );
5231
- process10.exit(1);
5512
+ process11.exit(1);
5232
5513
  }
5233
5514
  }
5234
5515
  async function runSandbox(argv) {
@@ -5247,7 +5528,7 @@ async function runSandbox(argv) {
5247
5528
  const sections = legacy ? SANDBOX_GUIDE_SECTIONS : SANDBOX_V2_GUIDE_SECTIONS;
5248
5529
  if (section && !sections.some((s) => s.slug === section) && SANDBOX_GUIDE_SECTIONS.some((s) => s.slug === section)) {
5249
5530
  console.error(`"${section}" is a section of the v1 sandbox guide: run "cofactor sandbox guide v1 ${section}".`);
5250
- process10.exit(1);
5531
+ process11.exit(1);
5251
5532
  }
5252
5533
  await printServedGuide(legacy ? "sandbox" : "sandbox-v2", guideRest);
5253
5534
  return;
@@ -5256,7 +5537,7 @@ async function runSandbox(argv) {
5256
5537
  handleSandboxValidate(rest);
5257
5538
  return;
5258
5539
  }
5259
- const { apiUrl, token } = await resolveConfig(options);
5540
+ const { apiUrl, token, appSubdomain } = await resolveConfig(options);
5260
5541
  const globalFlags = /* @__PURE__ */ new Set(["--env", "--api-url", "--app-url", "--token", "--org"]);
5261
5542
  const args = rest.filter((arg, i) => {
5262
5543
  if (globalFlags.has(arg)) return false;
@@ -5302,7 +5583,7 @@ async function runSandbox(argv) {
5302
5583
  await handlePoolSetSize(poolRest, apiUrl, token);
5303
5584
  } else {
5304
5585
  console.error(`Unknown pool action: ${poolAction}. Use "set-size".`);
5305
- process10.exit(1);
5586
+ process11.exit(1);
5306
5587
  }
5307
5588
  break;
5308
5589
  }
@@ -5314,7 +5595,7 @@ async function runSandbox(argv) {
5314
5595
  await handleArtifactCreate(actionRest, apiUrl, token);
5315
5596
  } else {
5316
5597
  console.error(`Unknown artifact action: ${action2}. Use "get" or "create".`);
5317
- process10.exit(1);
5598
+ process11.exit(1);
5318
5599
  }
5319
5600
  break;
5320
5601
  }
@@ -5328,7 +5609,7 @@ async function runSandbox(argv) {
5328
5609
  await handleConfigUnset(configRest, apiUrl, token);
5329
5610
  } else {
5330
5611
  console.error(`Unknown config action: ${configAction}. Use "list", "set", or "unset".`);
5331
- process10.exit(1);
5612
+ process11.exit(1);
5332
5613
  }
5333
5614
  break;
5334
5615
  }
@@ -5364,13 +5645,13 @@ async function runSandbox(argv) {
5364
5645
  await handleActionExecutionCancel(execRest, apiUrl, token);
5365
5646
  } else {
5366
5647
  console.error(`Unknown execution sub-command: ${execSub}. Use "get" or "cancel".`);
5367
- process10.exit(1);
5648
+ process11.exit(1);
5368
5649
  }
5369
5650
  break;
5370
5651
  }
5371
5652
  default:
5372
5653
  console.error(`Unknown action sub-command: ${actionSub}. Use "list", "get", "create", "delete", "execute", "executions", or "execution".`);
5373
- process10.exit(1);
5654
+ process11.exit(1);
5374
5655
  }
5375
5656
  break;
5376
5657
  }
@@ -5387,7 +5668,7 @@ async function runSandbox(argv) {
5387
5668
  await handleLogs(args, apiUrl, token);
5388
5669
  break;
5389
5670
  case "preview":
5390
- await handlePreview(args, apiUrl, token);
5671
+ await handlePreview(args, apiUrl, token, appSubdomain);
5391
5672
  break;
5392
5673
  case "envs":
5393
5674
  await handleSandboxEnvs(args, apiUrl, token);
@@ -5422,6 +5703,9 @@ async function runSandbox(argv) {
5422
5703
  case "sync":
5423
5704
  await handleSandboxSync(v2Positionals("sync", args), args, apiUrl, token);
5424
5705
  break;
5706
+ case "auth-check":
5707
+ await handleSandboxAuthCheck(args, apiUrl, token);
5708
+ break;
5425
5709
  case "verify-env":
5426
5710
  await handleSandboxVerifyEnv(args, apiUrl, token);
5427
5711
  break;
@@ -5437,7 +5721,7 @@ async function runSandbox(argv) {
5437
5721
  default:
5438
5722
  console.error(`Unknown sub-command: ${subCommand}`);
5439
5723
  printHelp();
5440
- process10.exit(1);
5724
+ process11.exit(1);
5441
5725
  }
5442
5726
  }
5443
5727
  export {
@@ -5454,4 +5738,4 @@ export {
5454
5738
  withPluginEnabled,
5455
5739
  withoutPlugin
5456
5740
  };
5457
- //# sourceMappingURL=sandbox-ZXDZTCCN.js.map
5741
+ //# sourceMappingURL=sandbox-FBLJEYA2.js.map