sliccy 3.5.4 → 3.6.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 (240) hide show
  1. package/README.md +1 -2
  2. package/dist/node-server/_cloud_core/src/errors.d.ts +14 -0
  3. package/dist/node-server/_cloud_core/src/errors.d.ts.map +1 -0
  4. package/dist/node-server/_cloud_core/src/errors.js +13 -0
  5. package/dist/node-server/_cloud_core/src/index.d.ts +19 -0
  6. package/dist/node-server/_cloud_core/src/index.d.ts.map +1 -0
  7. package/dist/node-server/_cloud_core/src/index.js +14 -0
  8. package/dist/node-server/_cloud_core/src/operations/kill.d.ts +18 -0
  9. package/dist/node-server/_cloud_core/src/operations/kill.d.ts.map +1 -0
  10. package/dist/node-server/_cloud_core/src/operations/kill.js +32 -0
  11. package/dist/node-server/_cloud_core/src/operations/list.d.ts +25 -0
  12. package/dist/node-server/_cloud_core/src/operations/list.d.ts.map +1 -0
  13. package/dist/node-server/_cloud_core/src/operations/list.js +145 -0
  14. package/dist/node-server/_cloud_core/src/operations/pause.d.ts +15 -0
  15. package/dist/node-server/_cloud_core/src/operations/pause.d.ts.map +1 -0
  16. package/dist/node-server/_cloud_core/src/operations/pause.js +27 -0
  17. package/dist/node-server/_cloud_core/src/operations/resume.d.ts +22 -0
  18. package/dist/node-server/_cloud_core/src/operations/resume.d.ts.map +1 -0
  19. package/dist/node-server/_cloud_core/src/operations/resume.js +86 -0
  20. package/dist/node-server/_cloud_core/src/operations/start.d.ts +67 -0
  21. package/dist/node-server/_cloud_core/src/operations/start.d.ts.map +1 -0
  22. package/dist/node-server/_cloud_core/src/operations/start.js +228 -0
  23. package/dist/node-server/_cloud_core/src/polling.d.ts +18 -0
  24. package/dist/node-server/_cloud_core/src/polling.d.ts.map +1 -0
  25. package/dist/node-server/_cloud_core/src/polling.js +79 -0
  26. package/dist/node-server/_cloud_core/src/registry.d.ts +27 -0
  27. package/dist/node-server/_cloud_core/src/registry.d.ts.map +1 -0
  28. package/dist/node-server/_cloud_core/src/registry.js +1 -0
  29. package/dist/node-server/_cloud_core/src/secrets-filter.d.ts +2 -0
  30. package/dist/node-server/_cloud_core/src/secrets-filter.d.ts.map +1 -0
  31. package/dist/node-server/_cloud_core/src/secrets-filter.js +17 -0
  32. package/dist/node-server/_cloud_core/src/substrate-factory.d.ts +3 -0
  33. package/dist/node-server/_cloud_core/src/substrate-factory.d.ts.map +1 -0
  34. package/dist/node-server/_cloud_core/src/substrate-factory.js +6 -0
  35. package/dist/node-server/_cloud_core/src/substrate.d.ts +53 -0
  36. package/dist/node-server/_cloud_core/src/substrate.d.ts.map +1 -0
  37. package/dist/node-server/_cloud_core/src/substrate.js +1 -0
  38. package/dist/node-server/_cloud_core/src/substrates/e2b.d.ts +3 -0
  39. package/dist/node-server/_cloud_core/src/substrates/e2b.d.ts.map +1 -0
  40. package/dist/node-server/_cloud_core/src/substrates/e2b.js +98 -0
  41. package/dist/node-server/_cloud_core/src/types.d.ts +94 -0
  42. package/dist/node-server/_cloud_core/src/types.d.ts.map +1 -0
  43. package/dist/node-server/_cloud_core/src/types.js +3 -0
  44. package/dist/node-server/_cloud_core/tests/fixtures/fake-substrate.d.ts +39 -0
  45. package/dist/node-server/_cloud_core/tests/fixtures/fake-substrate.d.ts.map +1 -0
  46. package/dist/node-server/_cloud_core/tests/fixtures/fake-substrate.js +75 -0
  47. package/dist/node-server/_cloud_core/tests/fixtures/index.d.ts +4 -0
  48. package/dist/node-server/_cloud_core/tests/fixtures/index.d.ts.map +1 -0
  49. package/dist/node-server/_cloud_core/tests/fixtures/index.js +2 -0
  50. package/dist/node-server/_cloud_core/tests/fixtures/mem-registry.d.ts +10 -0
  51. package/dist/node-server/_cloud_core/tests/fixtures/mem-registry.d.ts.map +1 -0
  52. package/dist/node-server/_cloud_core/tests/fixtures/mem-registry.js +25 -0
  53. package/dist/node-server/_cloud_core/tests/kill.test.d.ts +2 -0
  54. package/dist/node-server/_cloud_core/tests/kill.test.d.ts.map +1 -0
  55. package/dist/node-server/_cloud_core/tests/kill.test.js +164 -0
  56. package/dist/node-server/_cloud_core/tests/list.test.d.ts +2 -0
  57. package/dist/node-server/_cloud_core/tests/list.test.d.ts.map +1 -0
  58. package/dist/node-server/_cloud_core/tests/list.test.js +300 -0
  59. package/dist/node-server/_cloud_core/tests/pause.test.d.ts +2 -0
  60. package/dist/node-server/_cloud_core/tests/pause.test.d.ts.map +1 -0
  61. package/dist/node-server/_cloud_core/tests/pause.test.js +167 -0
  62. package/dist/node-server/_cloud_core/tests/resume.test.d.ts +2 -0
  63. package/dist/node-server/_cloud_core/tests/resume.test.d.ts.map +1 -0
  64. package/dist/node-server/_cloud_core/tests/resume.test.js +203 -0
  65. package/dist/node-server/_cloud_core/tests/secrets-filter.test.d.ts +2 -0
  66. package/dist/node-server/_cloud_core/tests/secrets-filter.test.d.ts.map +1 -0
  67. package/dist/node-server/_cloud_core/tests/secrets-filter.test.js +30 -0
  68. package/dist/node-server/_cloud_core/tests/start.test.d.ts +2 -0
  69. package/dist/node-server/_cloud_core/tests/start.test.d.ts.map +1 -0
  70. package/dist/node-server/_cloud_core/tests/start.test.js +513 -0
  71. package/dist/node-server/chrome-launch.d.ts +1 -0
  72. package/dist/node-server/chrome-launch.js +3 -0
  73. package/dist/node-server/cloud/dispatch.d.ts +34 -0
  74. package/dist/node-server/cloud/dispatch.js +79 -0
  75. package/dist/node-server/cloud/kill.d.ts +12 -0
  76. package/dist/node-server/cloud/kill.js +11 -0
  77. package/dist/node-server/cloud/list.d.ts +6 -0
  78. package/dist/node-server/cloud/list.js +6 -0
  79. package/dist/node-server/cloud/pause.d.ts +7 -0
  80. package/dist/node-server/cloud/pause.js +6 -0
  81. package/dist/node-server/cloud/registry-file.d.ts +13 -0
  82. package/dist/node-server/cloud/registry-file.js +85 -0
  83. package/dist/node-server/cloud/resume.d.ts +11 -0
  84. package/dist/node-server/cloud/resume.js +17 -0
  85. package/dist/node-server/cloud/start.d.ts +16 -0
  86. package/dist/node-server/cloud/start.js +49 -0
  87. package/dist/node-server/cloud-status.d.ts +11 -0
  88. package/dist/node-server/cloud-status.js +49 -0
  89. package/dist/node-server/hosted-bootstrap.d.ts +21 -0
  90. package/dist/node-server/hosted-bootstrap.js +14 -0
  91. package/dist/node-server/index.js +146 -3
  92. package/dist/node-server/leader-restart.d.ts +29 -0
  93. package/dist/node-server/leader-restart.js +146 -0
  94. package/dist/node-server/runtime-flags.d.ts +1 -0
  95. package/dist/node-server/runtime-flags.js +6 -0
  96. package/dist/ui/assets/adobe-BLY_1hN7.js +1 -0
  97. package/dist/ui/assets/adobe-BZS2Kmci.js +2 -0
  98. package/dist/ui/assets/agent-bridge-BFRFxJAl.js +1 -0
  99. package/dist/ui/assets/{agent-message-to-chat-H07zq-Kv.js → agent-message-to-chat-Cd-DQATg.js} +1 -1
  100. package/dist/ui/assets/anthropic-BbwUhO9H.js +39 -0
  101. package/dist/ui/assets/{anthropic-BK3Od7MT.js → anthropic-Dg9_3G1u.js} +3 -3
  102. package/dist/ui/assets/{apps-mZJxOz_q.js → apps-BQuoIsxV.js} +1 -1
  103. package/dist/ui/assets/azure-openai-CilK3M_Q.js +1 -0
  104. package/dist/ui/assets/{azure-openai-Dch5aR9j.js → azure-openai-CleJGzBE.js} +1 -1
  105. package/dist/ui/assets/azure-openai-responses-DMLD0Ano.js +1 -0
  106. package/dist/ui/assets/azure-openai-responses-_joBD1ru.js +1 -0
  107. package/dist/ui/assets/{bsh-watchdog-i5GXbvla.js → bsh-watchdog-CpJwB685.js} +1 -1
  108. package/dist/ui/assets/{cdp-Cv6W51Ld.js → cdp-BXSNdZRM.js} +3 -3
  109. package/dist/ui/assets/cloud-CCHD45tb.js +1 -0
  110. package/dist/ui/assets/cloud-DyAca2cv.css +1 -0
  111. package/dist/ui/assets/cost-command-Bv5UQVUC.js +1 -0
  112. package/dist/ui/assets/{dist-cK1g1pHF.js → dist-ChvbMSOY.js} +1 -1
  113. package/dist/ui/assets/{dist-BTGs16AV.js → dist-CltZJzbd.js} +1 -1
  114. package/dist/ui/assets/{dist-BcBxL8nL.js → dist-cZSF0917.js} +1 -1
  115. package/dist/ui/assets/{dist-Bgcuvc8h.js → dist-ljtB3O-J.js} +1 -1
  116. package/dist/ui/assets/{dist-EkY3RL7q.js → dist-tLLu0PBg.js} +1 -1
  117. package/dist/ui/assets/{es-Crcyr8Z7.js → es-C2wm9v2H.js} +1 -1
  118. package/dist/ui/assets/follower-sprinkle-bridge-Bg1mCcnG.js +1 -0
  119. package/dist/ui/assets/{github-DKcpc18I.js → github-BDzC2pCD.js} +1 -1
  120. package/dist/ui/assets/github-BzwlgA3s.js +2 -0
  121. package/dist/ui/assets/{google-IZXg3PJn.js → google-BVxsfaV3.js} +1 -1
  122. package/dist/ui/assets/google-C0Zd1qsl.js +1 -0
  123. package/dist/ui/assets/google-shared-DcZhZ_Z7.js +11 -0
  124. package/dist/ui/assets/{google-shared-BaIigNCs.js → google-shared-KyLYJrXa.js} +1 -1
  125. package/dist/ui/assets/google-vertex-BGpbWEWq.js +1 -0
  126. package/dist/ui/assets/{google-vertex-DDwmtWoJ.js → google-vertex-BSOalca9.js} +1 -1
  127. package/dist/ui/assets/{intercepted-oauth-CEgKdcyY.js → intercepted-oauth-D6eAxYca.js} +1 -1
  128. package/dist/ui/assets/{kernel-worker-dg2bRx7X.js → kernel-worker-D1_EtuZW.js} +1016 -980
  129. package/dist/ui/assets/{lick-ws-bridge-DgWfdUlv.js → lick-ws-bridge-DXFDOui-.js} +1 -1
  130. package/dist/ui/assets/{local-llm-DuUGv7wX.js → local-llm-B3qsU55x.js} +1 -1
  131. package/dist/ui/assets/magick-wasm-CasStH-w.js +1 -0
  132. package/dist/ui/assets/main-BSfjQXCf.js +1612 -0
  133. package/dist/ui/assets/{mistral-czd8-m0c.js → mistral-BkhZQqeV.js} +1 -1
  134. package/dist/ui/assets/{mistral-BS_Gm2MH.js → mistral-D130SR-b.js} +1 -1
  135. package/dist/ui/assets/modulepreload-polyfill-wMinxHhO.js +1 -0
  136. package/dist/ui/assets/nuke-command-D_sxu_AA.js +1 -0
  137. package/dist/ui/assets/{oauth-CJxNn7hC.js → oauth-BocTO4G9.js} +1 -1
  138. package/dist/ui/assets/oauth-bootstrap-DZdd7KZS.js +2 -0
  139. package/dist/ui/assets/{oauth-service-BAnfipgD.js → oauth-service-DAWR1CpY.js} +1 -1
  140. package/dist/ui/assets/oauth-service-y04jhWb_.js +1 -0
  141. package/dist/ui/assets/offscreen-client-HCLuFFlN.js +1 -0
  142. package/dist/ui/assets/onboarding-orchestrator-qbEVluIu.js +1 -0
  143. package/dist/ui/assets/openai-codex-responses-Bmd17Jq9.js +7 -0
  144. package/dist/ui/assets/openai-codex-responses-CMOclqki.js +7 -0
  145. package/dist/ui/assets/openai-completions-DmwgEsb4.js +5 -0
  146. package/dist/ui/assets/openai-responses-BYdEOxUq.js +1 -0
  147. package/dist/ui/assets/openai-responses-C3bs8sd1.js +1 -0
  148. package/dist/ui/assets/{openai-responses-shared-DDootAUL.js → openai-responses-shared-AM04ghTC.js} +1 -1
  149. package/dist/ui/assets/{openai-responses-shared-07lPyxEg.js → openai-responses-shared-Be22tDzQ.js} +1 -1
  150. package/dist/ui/assets/openrouter-BIFqVYxG.js +1 -0
  151. package/dist/ui/assets/openrouter-Dc1D0QOS.js +1 -0
  152. package/dist/ui/assets/panel-rpc-handlers-DDaHHzWR.js +1 -0
  153. package/dist/ui/assets/{provider-rBD-3FHW.js → provider-CU5ao6kF.js} +2 -2
  154. package/dist/ui/assets/{provider-BhabAJGA.js → provider-ChUiWhH0.js} +1 -1
  155. package/dist/ui/assets/provider-store-access-Bliwcbgg.js +1 -0
  156. package/dist/ui/assets/provider-store-access-iTh_6mMO.js +1 -0
  157. package/dist/ui/assets/proxied-fetch-8v3TaZYg.js +1 -0
  158. package/dist/ui/assets/{pyodide-BC4PaR54.js → pyodide-B7x60zRI.js} +2 -2
  159. package/dist/ui/assets/{remote-terminal-view-CXbFLcuS.js → remote-terminal-view-BREvd00U.js} +1 -1
  160. package/dist/ui/assets/secret-env-Js2g2sNO.js +1 -0
  161. package/dist/ui/assets/slicc-editor-Bu07BjW4.js +12 -0
  162. package/dist/ui/assets/{spawn-qtlw6x6p.js → spawn-CQ5XACZ1.js} +1 -1
  163. package/dist/ui/assets/{sql-wasm-DeVtn0z8.js → sql-wasm-C-pXJvOw.js} +1 -1
  164. package/dist/ui/assets/{store-DLeVoNF3.js → store-DQUahHdW.js} +1 -1
  165. package/dist/ui/assets/tray-leave-runtime-BEBMIYY_.js +1 -0
  166. package/dist/ui/assets/{upgrade-detection-CKrzeeRx.js → upgrade-detection-T74KTnyb.js} +1 -1
  167. package/dist/ui/assets/{xai-grok-CNyu04Tg.js → xai-grok-CkPPMxfZ.js} +1 -1
  168. package/dist/ui/assets/{xai-grok-sanitize-BeI8rdV5.js → xai-grok-sanitize-C7TgWvwD.js} +1 -1
  169. package/dist/ui/assets/{xai-grok-sanitize-h_8XMTD9.js → xai-grok-sanitize-L5nUbDcW.js} +1 -1
  170. package/dist/ui/assets/xai-grok-u9MyUN8m.js +1 -0
  171. package/dist/ui/electron-overlay-entry.js +9 -9
  172. package/dist/ui/index.html +3 -2
  173. package/dist/ui/packages/webapp/cloud/index.html +38 -0
  174. package/dist/ui/packages/webapp/index.html +3 -2
  175. package/package.json +10 -8
  176. package/dist/ui/assets/adobe-DpCIcyEb.js +0 -2
  177. package/dist/ui/assets/adobe-NMBK1vG2.js +0 -1
  178. package/dist/ui/assets/agent-bridge-D2k69QZ-.js +0 -1
  179. package/dist/ui/assets/anthropic-ZtfIcLyL.js +0 -39
  180. package/dist/ui/assets/azure-openai-CN9QA_pv.js +0 -1
  181. package/dist/ui/assets/azure-openai-responses-Bhr0mNdj.js +0 -1
  182. package/dist/ui/assets/azure-openai-responses-D400BxTc.js +0 -1
  183. package/dist/ui/assets/cost-command-BGqK_hbH.js +0 -1
  184. package/dist/ui/assets/follower-sprinkle-bridge-BayCf20v.js +0 -1
  185. package/dist/ui/assets/github-DpDjTcq9.js +0 -2
  186. package/dist/ui/assets/google-B97zVBac.js +0 -1
  187. package/dist/ui/assets/google-shared-DwzANLbz.js +0 -11
  188. package/dist/ui/assets/google-vertex-ICKJGS1M.js +0 -1
  189. package/dist/ui/assets/index-CCGqFzR9.js +0 -1575
  190. package/dist/ui/assets/magick-wasm-Bu7zx3Ux.js +0 -1
  191. package/dist/ui/assets/nuke-command-BSkSFZ4H.js +0 -1
  192. package/dist/ui/assets/oauth-bootstrap-BeZQrd7y.js +0 -2
  193. package/dist/ui/assets/oauth-service-BX55wTV9.js +0 -1
  194. package/dist/ui/assets/offscreen-client-DSEhUCxj.js +0 -1
  195. package/dist/ui/assets/onboarding-orchestrator-DjNti83y.js +0 -1
  196. package/dist/ui/assets/openai-codex-responses-DJKJH9AX.js +0 -7
  197. package/dist/ui/assets/openai-codex-responses-jB5RkWeq.js +0 -7
  198. package/dist/ui/assets/openai-completions-BWWM3S2J.js +0 -5
  199. package/dist/ui/assets/openai-responses-C_3YpAmj.js +0 -1
  200. package/dist/ui/assets/openai-responses-tJegTQYT.js +0 -1
  201. package/dist/ui/assets/panel-rpc-handlers-B4jZTNHO.js +0 -1
  202. package/dist/ui/assets/provider-store-access-H9Lu5HRL.js +0 -1
  203. package/dist/ui/assets/provider-store-access-biaB--zG.js +0 -1
  204. package/dist/ui/assets/proxied-fetch-C6dm2Ugc.js +0 -1
  205. package/dist/ui/assets/secret-env-s_8cP7Wf.js +0 -1
  206. package/dist/ui/assets/slicc-editor-pZcWEwTm.js +0 -12
  207. package/dist/ui/assets/tray-leave-runtime-DqA9PbxV.js +0 -1
  208. package/dist/ui/assets/xai-grok-CN1nP4TF.js +0 -1
  209. /package/dist/ui/assets/{active-transport-CfU1UxY6.js → active-transport-Clj9slj7.js} +0 -0
  210. /package/dist/ui/assets/{active-transport-Dm6CWYVE.js → active-transport-DHcaBd4j.js} +0 -0
  211. /package/dist/ui/assets/{addon-fit-BDUxDgg1.js → addon-fit-CBG3Hsec.js} +0 -0
  212. /package/dist/ui/assets/{addon-fit-Dkm5m5XK.js → addon-fit-CUt9lrQO.js} +0 -0
  213. /package/dist/ui/assets/{azure-ai-foundry-BHLfY_0t.js → azure-ai-foundry-B4QrVklT.js} +0 -0
  214. /package/dist/ui/assets/{azure-ai-foundry-1pnZpxAJ.js → azure-ai-foundry-CGkSI_4f.js} +0 -0
  215. /package/dist/ui/assets/{chat-fixture-DOzffHLr.js → chat-fixture-ChGRNFda.js} +0 -0
  216. /package/dist/ui/assets/{dist-BYoCEWmq.js → dist-9RlRmGag.js} +0 -0
  217. /package/dist/ui/assets/{dist-BNe7ZIzA.js → dist-CZcDRTma.js} +0 -0
  218. /package/dist/ui/assets/{dist-BrjVVq8c.js → dist-D0kTaNET.js} +0 -0
  219. /package/dist/ui/assets/{git-config-CGMfR0WK.js → git-config-DcQbkikl.js} +0 -0
  220. /package/dist/ui/assets/{github-copilot-headers-CTUP_cqn.js → github-copilot-headers-C5CocKfn.js} +0 -0
  221. /package/dist/ui/assets/{global-db-B97_fwjD.js → global-db-DL3jdM9H.js} +0 -0
  222. /package/dist/ui/assets/{hash-B9Dtr1VK.js → hash-iitWbNjK.js} +0 -0
  223. /package/dist/ui/assets/{headers-AeYLn7L4.js → headers-qDMOQQGF.js} +0 -0
  224. /package/dist/ui/assets/{lick-manager-proxy-BnnMFbgv.js → lick-manager-proxy-1L0EGIWD.js} +0 -0
  225. /package/dist/ui/assets/{index-CvlDeETm.css → main-CvlDeETm.css} +0 -0
  226. /package/dist/ui/assets/{openai-Bf1npfRy.js → openai-BR_xAflc.js} +0 -0
  227. /package/dist/ui/assets/{page-storage-sync-CqMAh8jG.js → page-storage-sync-sv2ogNKh.js} +0 -0
  228. /package/dist/ui/assets/{pdfjs-DDDkZHZ4.js → pdfjs-CW51NNE6.js} +0 -0
  229. /package/dist/ui/assets/{rum-DCU8sePD.js → rum-z5wz4Dnj.js} +0 -0
  230. /package/dist/ui/assets/{sanitize-unicode-h0KEBmEw.js → sanitize-unicode-B1z_Y4Dq.js} +0 -0
  231. /package/dist/ui/assets/{sprinkle-bridge-channel-DojIojh7.js → sprinkle-bridge-channel-Bw5UaanD.js} +0 -0
  232. /package/dist/ui/assets/{sprinkle-bridge-channel-DJC5SGWr.js → sprinkle-bridge-channel-ChD0T07C.js} +0 -0
  233. /package/dist/ui/assets/{sprinkle-proxy-rF3NSOGq.js → sprinkle-proxy-D6wqmewK.js} +0 -0
  234. /package/dist/ui/assets/{src-BQRb5emh.js → src-DRmQ1wr-.js} +0 -0
  235. /package/dist/ui/assets/{xai-grok-errors-Bzn3oNvW.js → xai-grok-errors-BdY43UIn.js} +0 -0
  236. /package/dist/ui/assets/{xai-grok-errors-WbFsMZum.js → xai-grok-errors-C6VnD_MH.js} +0 -0
  237. /package/dist/ui/assets/{xai-grok-models-cvcIXmNM.js → xai-grok-models-BndZgIFZ.js} +0 -0
  238. /package/dist/ui/assets/{xai-grok-models-DMYWtapT.js → xai-grok-models-C9MIa-Aj.js} +0 -0
  239. /package/dist/ui/assets/{xterm-D8ldLLKi.js → xterm-DLCsxsB1.js} +0 -0
  240. /package/dist/ui/assets/{xterm-DhusGoAm.js → xterm-VndicwFp.js} +0 -0
@@ -0,0 +1,79 @@
1
+ const VALID_SUBCOMMANDS = ['start', 'list', 'pause', 'resume', 'kill'];
2
+ export function parseCloudArgs(argv) {
3
+ if (argv.includes('--hosted') && argv.includes('--cloud')) {
4
+ throw new Error('--cloud and --hosted are mutually exclusive');
5
+ }
6
+ const cloudIdx = argv.indexOf('--cloud');
7
+ if (cloudIdx === -1)
8
+ return null;
9
+ const sub = argv[cloudIdx + 1];
10
+ if (!sub || !VALID_SUBCOMMANDS.includes(sub)) {
11
+ throw new Error(`unknown subcommand: ${sub ?? '(none)'} (expected one of: ${VALID_SUBCOMMANDS.join(', ')})`);
12
+ }
13
+ const rest = argv.slice(cloudIdx + 2);
14
+ const baseArgs = {
15
+ substrate: 'e2b',
16
+ };
17
+ let i = 0;
18
+ while (i < rest.length) {
19
+ const a = rest[i];
20
+ if (a === '--name') {
21
+ baseArgs.name = rest[++i];
22
+ }
23
+ else if (a === '--env-file') {
24
+ baseArgs.envFile = rest[++i];
25
+ }
26
+ else if (a === '--substrate') {
27
+ const v = rest[++i];
28
+ if (v !== 'e2b')
29
+ throw new Error(`unsupported substrate: ${v} (MVP only supports 'e2b')`);
30
+ baseArgs.substrate = v;
31
+ }
32
+ else if (!a.startsWith('--') &&
33
+ !baseArgs.query &&
34
+ (sub === 'pause' || sub === 'resume' || sub === 'kill')) {
35
+ baseArgs.query = a;
36
+ }
37
+ else {
38
+ throw new Error(`unrecognized arg: ${a}`);
39
+ }
40
+ i++;
41
+ }
42
+ // Build discriminated union based on subcommand
43
+ switch (sub) {
44
+ case 'start':
45
+ return {
46
+ subcommand: 'start',
47
+ args: {
48
+ substrate: baseArgs.substrate,
49
+ name: baseArgs.name,
50
+ envFile: baseArgs.envFile,
51
+ },
52
+ };
53
+ case 'list':
54
+ return { subcommand: 'list', args: { substrate: baseArgs.substrate } };
55
+ case 'pause':
56
+ case 'kill':
57
+ if (!baseArgs.query) {
58
+ throw new Error(`${sub} requires a query argument (sandbox ID or name)`);
59
+ }
60
+ return {
61
+ subcommand: sub,
62
+ args: { substrate: baseArgs.substrate, query: baseArgs.query },
63
+ };
64
+ case 'resume':
65
+ if (!baseArgs.query) {
66
+ throw new Error(`${sub} requires a query argument (sandbox ID or name)`);
67
+ }
68
+ return {
69
+ subcommand: 'resume',
70
+ args: {
71
+ substrate: baseArgs.substrate,
72
+ query: baseArgs.query,
73
+ envFile: baseArgs.envFile,
74
+ },
75
+ };
76
+ default:
77
+ throw new Error(`unknown subcommand: ${sub}`);
78
+ }
79
+ }
@@ -0,0 +1,12 @@
1
+ import { type SandboxSubstrate } from '../_cloud_core/src/index.js';
2
+ export interface RunKillOpts {
3
+ substrate: SandboxSubstrate;
4
+ registryPath: string;
5
+ query: string;
6
+ }
7
+ /**
8
+ * Thin wrapper over killCone from @slicc/cloud-core.
9
+ * Adapts the cloud-core interface to the node-server's file-backed registry.
10
+ * Returns void for backward compatibility with existing callers.
11
+ */
12
+ export declare function runKill(opts: RunKillOpts): Promise<void>;
@@ -0,0 +1,11 @@
1
+ import { killCone } from '../_cloud_core/src/index.js';
2
+ import { FileRegistry } from './registry-file.js';
3
+ /**
4
+ * Thin wrapper over killCone from @slicc/cloud-core.
5
+ * Adapts the cloud-core interface to the node-server's file-backed registry.
6
+ * Returns void for backward compatibility with existing callers.
7
+ */
8
+ export async function runKill(opts) {
9
+ const registry = new FileRegistry(opts.registryPath);
10
+ await killCone({ substrate: opts.substrate, registry }, opts.query);
11
+ }
@@ -0,0 +1,6 @@
1
+ import { type ConeEntry, type SandboxSubstrate } from '../_cloud_core/src/index.js';
2
+ export interface RunListOpts {
3
+ substrate: SandboxSubstrate;
4
+ registryPath: string;
5
+ }
6
+ export declare function runList(opts: RunListOpts): Promise<ConeEntry[]>;
@@ -0,0 +1,6 @@
1
+ import { listCones } from '../_cloud_core/src/index.js';
2
+ import { FileRegistry } from './registry-file.js';
3
+ export async function runList(opts) {
4
+ const registry = new FileRegistry(opts.registryPath);
5
+ return listCones({ substrate: opts.substrate, registry });
6
+ }
@@ -0,0 +1,7 @@
1
+ import { type SandboxSubstrate } from '../_cloud_core/src/index.js';
2
+ export interface RunPauseOpts {
3
+ substrate: SandboxSubstrate;
4
+ registryPath: string;
5
+ query: string;
6
+ }
7
+ export declare function runPause(opts: RunPauseOpts): Promise<void>;
@@ -0,0 +1,6 @@
1
+ import { pauseCone } from '../_cloud_core/src/index.js';
2
+ import { FileRegistry } from './registry-file.js';
3
+ export async function runPause(opts) {
4
+ const registry = new FileRegistry(opts.registryPath);
5
+ return pauseCone({ substrate: opts.substrate, registry }, opts.query);
6
+ }
@@ -0,0 +1,13 @@
1
+ import type { ConeEntry, Registry } from '../_cloud_core/src/index.js';
2
+ export declare class FileRegistry implements Registry {
3
+ private readonly filePath;
4
+ constructor(filePath: string);
5
+ static defaultPath(): string;
6
+ list(): Promise<ConeEntry[]>;
7
+ append(entry: ConeEntry): Promise<void>;
8
+ update(sandboxId: string, patch: Partial<ConeEntry>): Promise<void>;
9
+ remove(sandboxId: string): Promise<void>;
10
+ findByNameOrId(query: string): Promise<ConeEntry | null>;
11
+ private read;
12
+ private write;
13
+ }
@@ -0,0 +1,85 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+ function isConeEntry(x) {
4
+ if (typeof x !== 'object' || x === null)
5
+ return false;
6
+ const e = x;
7
+ return (typeof e.substrate === 'string' &&
8
+ typeof e.sandboxId === 'string' &&
9
+ typeof e.createdAt === 'string' &&
10
+ typeof e.joinUrl === 'string' &&
11
+ typeof e.lastSeen === 'string' &&
12
+ typeof e.state === 'string' &&
13
+ (e.state === 'running' || e.state === 'paused' || e.state === 'dead' || e.state === 'reserved'));
14
+ }
15
+ export class FileRegistry {
16
+ filePath;
17
+ constructor(filePath) {
18
+ this.filePath = filePath;
19
+ }
20
+ static defaultPath() {
21
+ const home = process.env['HOME'] ?? process.env['USERPROFILE'] ?? '.';
22
+ return path.join(home, '.slicc', 'cloud-sessions.json');
23
+ }
24
+ async list() {
25
+ const data = await this.read();
26
+ return data.sessions;
27
+ }
28
+ async append(entry) {
29
+ const data = await this.read();
30
+ // UPSERT semantics: filter out existing sandboxId, then push the new entry
31
+ data.sessions = data.sessions.filter((s) => s.sandboxId !== entry.sandboxId);
32
+ data.sessions.push(entry);
33
+ await this.write(data);
34
+ }
35
+ async update(sandboxId, patch) {
36
+ const data = await this.read();
37
+ const idx = data.sessions.findIndex((s) => s.sandboxId === sandboxId);
38
+ if (idx === -1) {
39
+ throw new Error(`entry not found: ${sandboxId}`);
40
+ }
41
+ data.sessions[idx] = { ...data.sessions[idx], ...patch, sandboxId };
42
+ await this.write(data);
43
+ }
44
+ async remove(sandboxId) {
45
+ const data = await this.read();
46
+ data.sessions = data.sessions.filter((s) => s.sandboxId !== sandboxId);
47
+ await this.write(data);
48
+ }
49
+ async findByNameOrId(query) {
50
+ const data = await this.read();
51
+ return (data.sessions.find((s) => s.sandboxId === query) ??
52
+ data.sessions.find((s) => s.name === query) ??
53
+ null);
54
+ }
55
+ async read() {
56
+ let raw;
57
+ try {
58
+ raw = JSON.parse(await fs.readFile(this.filePath, 'utf-8'));
59
+ }
60
+ catch (err) {
61
+ if (err.code === 'ENOENT')
62
+ return { sessions: [] };
63
+ throw err;
64
+ }
65
+ if (typeof raw !== 'object' ||
66
+ raw === null ||
67
+ !Array.isArray(raw.sessions)) {
68
+ console.warn('cloud-sessions.json is malformed; treating as empty', this.filePath);
69
+ return { sessions: [] };
70
+ }
71
+ const candidates = raw.sessions;
72
+ const sessions = [];
73
+ for (const c of candidates) {
74
+ if (isConeEntry(c))
75
+ sessions.push(c);
76
+ else
77
+ console.warn('skipping malformed cloud-sessions entry', c);
78
+ }
79
+ return { sessions };
80
+ }
81
+ async write(data) {
82
+ await fs.mkdir(path.dirname(this.filePath), { recursive: true });
83
+ await fs.writeFile(this.filePath, JSON.stringify(data, null, 2));
84
+ }
85
+ }
@@ -0,0 +1,11 @@
1
+ import { type ResumeResult, type SandboxSubstrate } from '../_cloud_core/src/index.js';
2
+ export interface RunResumeOpts {
3
+ substrate: SandboxSubstrate;
4
+ envFilePath: string;
5
+ registryPath: string;
6
+ query: string;
7
+ localSliccVersion: string;
8
+ pollIntervalMs?: number;
9
+ pollTimeoutMs?: number;
10
+ }
11
+ export declare function runResume(opts: RunResumeOpts): Promise<ResumeResult>;
@@ -0,0 +1,17 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import { resumeCone } from '../_cloud_core/src/index.js';
3
+ import { FileRegistry } from './registry-file.js';
4
+ export async function runResume(opts) {
5
+ // Refresh on resume so cones paused for >24h pick up a freshly-issued token
6
+ // from the local secrets.env. This was a pre-existing CLI gap — resume would
7
+ // succeed but Adobe LLM calls would 401 on expired tokens.
8
+ const envContents = await fs.readFile(opts.envFilePath, 'utf-8');
9
+ const registry = new FileRegistry(opts.registryPath);
10
+ return resumeCone({ substrate: opts.substrate, registry }, {
11
+ query: opts.query,
12
+ localSliccVersion: opts.localSliccVersion,
13
+ refreshSecretsContents: envContents,
14
+ pollIntervalMs: opts.pollIntervalMs,
15
+ pollTimeoutMs: opts.pollTimeoutMs,
16
+ });
17
+ }
@@ -0,0 +1,16 @@
1
+ import { type StartResult, type SandboxHandle } from '../_cloud_core/src/index.js';
2
+ import type { SandboxSubstrate } from '../_cloud_core/src/index.js';
3
+ export interface RunStartOpts {
4
+ substrate: SandboxSubstrate;
5
+ envFilePath: string;
6
+ registryPath: string;
7
+ workerBaseUrl: string;
8
+ sliccVersion: string;
9
+ template?: string;
10
+ name?: string;
11
+ pollIntervalMs?: number;
12
+ pollTimeoutMs?: number;
13
+ /** Test-only hook: invoked after substrate.create but before pollCloudStatus. */
14
+ onAfterCreate?: (handle: SandboxHandle) => Promise<void>;
15
+ }
16
+ export declare function runStart(opts: RunStartOpts): Promise<StartResult>;
@@ -0,0 +1,49 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import { startCone } from '../_cloud_core/src/index.js';
3
+ import { FileRegistry } from './registry-file.js';
4
+ /**
5
+ * Extract ADOBE_IMS_TOKEN (+ DOMAINS) from an env-file body so they can be
6
+ * injected as sandbox env vars at Sandbox.create. start.sh writes them to
7
+ * /slicc/secrets.env BEFORE node-server boots, eliminating the historical 5s
8
+ * race window where the page-side bootstrap fetch found no secrets file.
9
+ */
10
+ function extractAdobeBootstrap(envContents) {
11
+ const envs = {};
12
+ for (const line of envContents.split('\n')) {
13
+ const m = line.match(/^\s*(ADOBE_IMS_TOKEN(?:_DOMAINS)?)\s*=\s*(.*)$/);
14
+ if (m)
15
+ envs[m[1]] = m[2].trim();
16
+ }
17
+ return envs;
18
+ }
19
+ export async function runStart(opts) {
20
+ const envContents = await fs.readFile(opts.envFilePath, 'utf-8');
21
+ const adobeBootstrap = extractAdobeBootstrap(envContents);
22
+ const registry = new FileRegistry(opts.registryPath);
23
+ // If we have a test hook, wrap the substrate to inject it.
24
+ let substrate = opts.substrate;
25
+ if (opts.onAfterCreate) {
26
+ const originalCreate = substrate.create.bind(substrate);
27
+ substrate = {
28
+ ...substrate,
29
+ create: async (createOpts) => {
30
+ const handle = await originalCreate(createOpts);
31
+ await opts.onAfterCreate(handle);
32
+ return handle;
33
+ },
34
+ };
35
+ }
36
+ return startCone({ substrate, registry }, {
37
+ envContents,
38
+ envs: adobeBootstrap,
39
+ workerBaseUrl: opts.workerBaseUrl,
40
+ template: opts.template,
41
+ name: opts.name,
42
+ sliccVersion: opts.sliccVersion,
43
+ pollTimeoutMs: opts.pollTimeoutMs,
44
+ pollIntervalMs: opts.pollIntervalMs,
45
+ metadata: {
46
+ createdBy: process.env['USER'] ?? 'unknown',
47
+ },
48
+ });
49
+ }
@@ -0,0 +1,11 @@
1
+ import { type Express, type Request, type Response, type NextFunction } from 'express';
2
+ export interface CloudStatusEndpointOptions {
3
+ joinFilePath: string;
4
+ }
5
+ /**
6
+ * Reject non-loopback requests. The sandbox is a private execution boundary,
7
+ * but defense in depth: someone might wire a port-forward and we want this
8
+ * endpoint to be unreachable from the outside.
9
+ */
10
+ export declare function requireLoopback(req: Request, res: Response, next: NextFunction): void;
11
+ export declare function registerCloudStatusEndpoint(app: Express, options: CloudStatusEndpointOptions): void;
@@ -0,0 +1,49 @@
1
+ import express from 'express';
2
+ import { promises as fs } from 'node:fs';
3
+ function isCloudStatusPayload(x) {
4
+ if (typeof x !== 'object' || x === null)
5
+ return false;
6
+ const p = x;
7
+ if (typeof p.joinUrl !== 'string' || p.joinUrl.length === 0)
8
+ return false;
9
+ // Optional fields: validate type if present, but don't require.
10
+ for (const key of ['trayId', 'controllerUrl', 'webhookUrl', 'runtime', 'sliccVersion']) {
11
+ if (key in p && typeof p[key] !== 'string')
12
+ return false;
13
+ }
14
+ return true;
15
+ }
16
+ /**
17
+ * Reject non-loopback requests. The sandbox is a private execution boundary,
18
+ * but defense in depth: someone might wire a port-forward and we want this
19
+ * endpoint to be unreachable from the outside.
20
+ */
21
+ export function requireLoopback(req, res, next) {
22
+ const addr = req.socket.remoteAddress ?? '';
23
+ const isLoopback = addr === '127.0.0.1' || addr === '::1' || addr === '::ffff:127.0.0.1' || addr === 'localhost';
24
+ if (!isLoopback) {
25
+ res.status(403).json({ error: 'localhost only' });
26
+ return;
27
+ }
28
+ next();
29
+ }
30
+ export function registerCloudStatusEndpoint(app, options) {
31
+ app.post('/api/cloud-status', requireLoopback, express.json(), async (req, res) => {
32
+ if (!isCloudStatusPayload(req.body)) {
33
+ res.status(400).json({ error: 'invalid cloud-status payload' });
34
+ return;
35
+ }
36
+ const body = req.body;
37
+ const payload = {
38
+ joinUrl: body.joinUrl,
39
+ trayId: body.trayId ?? null,
40
+ controllerUrl: body.controllerUrl ?? null,
41
+ webhookUrl: body.webhookUrl ?? null,
42
+ runtime: body.runtime ?? null,
43
+ sliccVersion: body.sliccVersion ?? null,
44
+ updatedAt: new Date().toISOString(),
45
+ };
46
+ await fs.writeFile(options.joinFilePath, JSON.stringify(payload, null, 2));
47
+ res.json({ ok: true });
48
+ });
49
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Loopback-only endpoint that hands the hosted-leader page any pre-acquired
3
+ * provider credentials that were uploaded with secrets.env.
4
+ *
5
+ * Currently exposes ADOBE_IMS_TOKEN so the laptop can paste in an IMS bearer
6
+ * once (avoiding OAuth, which can't complete in headless chromium). The
7
+ * webapp's hosted-leader boot calls saveOAuthAccount with the returned token
8
+ * before any LLM call goes out.
9
+ *
10
+ * Same loopback discipline as /api/cloud-status and /api/leader-restart:
11
+ * chromium-on-localhost is the only thing that can hit it; the laptop-side
12
+ * orchestrator talks to the leader through the tray-worker tunnel, not http.
13
+ */
14
+ import type { Express } from 'express';
15
+ import type { SecretStore } from './secrets/types.js';
16
+ export interface HostedBootstrapPayload {
17
+ adobeImsToken?: string;
18
+ }
19
+ export declare function registerHostedBootstrapEndpoint(app: Express, options: {
20
+ secretStore: SecretStore;
21
+ }): void;
@@ -0,0 +1,14 @@
1
+ import { requireLoopback } from './cloud-status.js';
2
+ export function registerHostedBootstrapEndpoint(app, options) {
3
+ app.get('/api/hosted-bootstrap', requireLoopback, (_req, res) => {
4
+ const payload = {};
5
+ // EnvSecretStore.get(name) requires both the value AND a <name>_DOMAINS
6
+ // entry. The user's secrets.env must contain BOTH:
7
+ // ADOBE_IMS_TOKEN=<bearer>
8
+ // ADOBE_IMS_TOKEN_DOMAINS=adobe-llm-proxy.paolo-moz.workers.dev
9
+ const token = options.secretStore.get('ADOBE_IMS_TOKEN');
10
+ if (token?.value)
11
+ payload.adobeImsToken = token.value;
12
+ res.json(payload);
13
+ });
14
+ }
@@ -22,10 +22,30 @@ import { SecretProxyManager } from './secrets/proxy-manager.js';
22
22
  import { OauthSecretStore } from './secrets/oauth-secret-store.js';
23
23
  import { handleDaSignAndForward, handleS3SignAndForward } from './secrets/sign-and-forward.js';
24
24
  import { readOrCreateSessionId } from './secrets/session-id-file.js';
25
+ import { registerCloudStatusEndpoint } from './cloud-status.js';
26
+ import { registerHostedBootstrapEndpoint } from './hosted-bootstrap.js';
27
+ import { createHttpCdp, registerLeaderRestartEndpoint } from './leader-restart.js';
25
28
  import { FETCH_PROXY_SKIP_HEADERS } from './fetch-proxy-headers.js';
26
29
  import { buildLocalApiDescriptor, sliccLinksMiddleware } from './links-middleware.js';
30
+ import { parseCloudArgs } from './cloud/dispatch.js';
31
+ import { createSubstrate } from './_cloud_core/src/index.js';
32
+ import { FileRegistry } from './cloud/registry-file.js';
33
+ import { runStart } from './cloud/start.js';
34
+ import { runList } from './cloud/list.js';
35
+ import { runPause } from './cloud/pause.js';
36
+ import { runResume } from './cloud/resume.js';
37
+ import { runKill } from './cloud/kill.js';
38
+ import { promises as fsPromises } from 'node:fs';
27
39
  const __dirname = fileURLToPath(new URL('.', import.meta.url));
28
40
  const PROJECT_ROOT = resolve(__dirname, '..', '..');
41
+ // ---------------------------------------------------------------------------
42
+ // Cloud dispatcher — must run BEFORE any other boot logic
43
+ // ---------------------------------------------------------------------------
44
+ const _parsedCloudArgs = parseCloudArgs(process.argv.slice(2));
45
+ if (_parsedCloudArgs) {
46
+ await runCloudSubcommand(_parsedCloudArgs);
47
+ process.exit(0);
48
+ }
29
49
  const RUNTIME_FLAGS = parseCliRuntimeFlags(process.argv.slice(2));
30
50
  // Version command — exit immediately, no side effects
31
51
  if (RUNTIME_FLAGS.version) {
@@ -420,6 +440,11 @@ async function main() {
420
440
  join: RUNTIME_FLAGS.join,
421
441
  joinUrl: RUNTIME_FLAGS.joinUrl,
422
442
  });
443
+ // Append runtime parameter for hosted mode
444
+ if (RUNTIME_FLAGS.hosted) {
445
+ const sep = browserLaunchUrl.includes('?') ? '&' : '?';
446
+ browserLaunchUrl += `${sep}runtime=hosted-leader`;
447
+ }
423
448
  // Append optional prompt parameter
424
449
  if (RUNTIME_FLAGS.prompt) {
425
450
  const sep = browserLaunchUrl.includes('?') ? '&' : '?';
@@ -433,12 +458,17 @@ async function main() {
433
458
  }
434
459
  const chromeProfile = (() => {
435
460
  try {
436
- return resolveChromeLaunchProfile({
461
+ const resolved = resolveChromeLaunchProfile({
437
462
  projectRoot: PROJECT_ROOT,
438
463
  tmpDir: process.env['TMPDIR'] ?? '/tmp',
439
464
  profile: RUNTIME_FLAGS.profile,
440
465
  servePort: SERVE_PORT,
441
466
  });
467
+ // Override user data dir in hosted mode to use persistent profile
468
+ if (RUNTIME_FLAGS.hosted) {
469
+ resolved.userDataDir = process.env['CHROME_USER_DATA_DIR'] ?? '/data/profile';
470
+ }
471
+ return resolved;
442
472
  }
443
473
  catch (error) {
444
474
  console.error(error instanceof Error ? error.message : String(error));
@@ -471,6 +501,7 @@ async function main() {
471
501
  cdpPort: REQUESTED_CDP_PORT,
472
502
  launchUrl: browserLaunchUrl,
473
503
  profile: chromeProfile,
504
+ hosted: RUNTIME_FLAGS.hosted,
474
505
  });
475
506
  // Profile directories are reused across runs (both the dev
476
507
  // `/tmp/browser-coding-agent-chrome` profile and the persistent
@@ -678,7 +709,10 @@ async function main() {
678
709
  }));
679
710
  app.get('/api/runtime-config', (_req, res) => {
680
711
  res.json({
681
- trayWorkerBaseUrl: RUNTIME_FLAGS.leadWorkerBaseUrl ??
712
+ trayWorkerBaseUrl:
713
+ // Hosted mode source: env var injected at sandbox-create time.
714
+ (RUNTIME_FLAGS.hosted ? process.env['SLICC_TRAY_WORKER_BASE_URL']?.trim() : null) ??
715
+ RUNTIME_FLAGS.leadWorkerBaseUrl ??
682
716
  (process.env['WORKER_BASE_URL']?.trim() || null) ??
683
717
  (DEV_MODE
684
718
  ? 'https://slicc-tray-hub-staging.minivelos.workers.dev'
@@ -992,6 +1026,14 @@ async function main() {
992
1026
  await secretProxy.reload();
993
1027
  res.status(204).end();
994
1028
  });
1029
+ // Cloud status endpoint (hosted-only) — writes join info to /tmp/slicc-join.json
1030
+ // Register BEFORE Chromium launches. The webapp's first action after
1031
+ // ?runtime=hosted-leader boot is to mint a tray and POST /api/cloud-status.
1032
+ // If the route doesn't exist yet, the post 404s and the CLI poll times out.
1033
+ if (RUNTIME_FLAGS.hosted) {
1034
+ registerCloudStatusEndpoint(app, { joinFilePath: '/tmp/slicc-join.json' });
1035
+ registerHostedBootstrapEndpoint(app, { secretStore });
1036
+ }
995
1037
  // Fetch proxy — forwards cross-origin requests from the browser to bypass CORS.
996
1038
  // Used by just-bash's curl which calls the browser's fetch() API.
997
1039
  // Note: express.json() may have already parsed the body, so we check req.body first.
@@ -1304,7 +1346,7 @@ async function main() {
1304
1346
  });
1305
1347
  // Create the HTTP server BEFORE Vite so we can register our upgrade handler first
1306
1348
  const server = createServer(app);
1307
- if (DEV_MODE) {
1349
+ if (DEV_MODE && !RUNTIME_FLAGS.hosted) {
1308
1350
  // Dev mode: use Vite's dev server as middleware for HMR
1309
1351
  const { createServer: createViteServer } = await import('vite');
1310
1352
  const webappIndexHtml = resolve(process.cwd(), 'packages/webapp/index.html');
@@ -1730,6 +1772,14 @@ async function main() {
1730
1772
  console.log(`[cdp-proxy] Pre-connected: CDP available at ${cdpUrl}`);
1731
1773
  await ensureChromeConnection(cdpUrl);
1732
1774
  console.log('[cdp-proxy] Chrome WebSocket ready (pre-warmed)');
1775
+ // Register leader-restart endpoint now that CDP is ready (hosted mode only)
1776
+ if (RUNTIME_FLAGS.hosted) {
1777
+ registerLeaderRestartEndpoint(app, {
1778
+ cdp: createHttpCdp(CDP_PORT),
1779
+ localUrlPrefix: `http://localhost:${SERVE_PORT}/`,
1780
+ });
1781
+ console.log('[hosted] /api/leader-restart endpoint registered');
1782
+ }
1733
1783
  }
1734
1784
  catch (err) {
1735
1785
  console.log('[cdp-proxy] Pre-connect failed (will retry on first client):', err);
@@ -1762,6 +1812,99 @@ async function main() {
1762
1812
  }
1763
1813
  });
1764
1814
  }
1815
+ // ---------------------------------------------------------------------------
1816
+ // Cloud dispatcher helpers
1817
+ // ---------------------------------------------------------------------------
1818
+ async function readSecretsEnvKey(name) {
1819
+ try {
1820
+ const home = process.env['HOME'] ?? process.env['USERPROFILE'] ?? '.';
1821
+ const file = process.env['SLICC_SECRETS_FILE'] ?? join(home, '.slicc', 'secrets.env');
1822
+ const contents = await fsPromises.readFile(file, 'utf-8');
1823
+ for (const line of contents.split('\n')) {
1824
+ const m = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(line);
1825
+ if (m && m[1] === name)
1826
+ return m[2].trim();
1827
+ }
1828
+ }
1829
+ catch {
1830
+ /* file missing → undefined */
1831
+ }
1832
+ return undefined;
1833
+ }
1834
+ function defaultSecretsPath() {
1835
+ const home = process.env['HOME'] ?? process.env['USERPROFILE'] ?? '.';
1836
+ return join(home, '.slicc', 'secrets.env');
1837
+ }
1838
+ function readPackageVersion() {
1839
+ try {
1840
+ const pkgPath = join(PROJECT_ROOT, 'package.json');
1841
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
1842
+ return pkg.version;
1843
+ }
1844
+ catch {
1845
+ return 'unknown';
1846
+ }
1847
+ }
1848
+ async function runCloudSubcommand(parsed) {
1849
+ const apiKey = process.env['E2B_API_KEY'] ?? (await readSecretsEnvKey('E2B_API_KEY'));
1850
+ if (!apiKey) {
1851
+ console.error('E2B_API_KEY not set. Add it to ~/.slicc/secrets.env (with E2B_API_KEY_DOMAINS=e2b.dev) ' +
1852
+ 'or export it.');
1853
+ process.exit(2);
1854
+ }
1855
+ const substrate = createSubstrate(parsed.args.substrate, { apiKey });
1856
+ const registryPath = FileRegistry.defaultPath();
1857
+ const localSliccVersion = readPackageVersion();
1858
+ switch (parsed.subcommand) {
1859
+ case 'start': {
1860
+ const result = await runStart({
1861
+ substrate,
1862
+ envFilePath: parsed.args.envFile ?? defaultSecretsPath(),
1863
+ registryPath,
1864
+ workerBaseUrl: process.env['SLICC_TRAY_WORKER_BASE_URL']?.trim() || 'https://www.sliccy.ai',
1865
+ sliccVersion: localSliccVersion,
1866
+ name: parsed.args.name,
1867
+ });
1868
+ console.log(`Sandbox ${result.sandboxId} ready.`);
1869
+ console.log(`Open: ${result.joinUrl}`);
1870
+ console.log('Attach from iOS, desktop SLICC, or any browser tab.');
1871
+ break;
1872
+ }
1873
+ case 'list': {
1874
+ const entries = await runList({ substrate, registryPath });
1875
+ for (const e of entries) {
1876
+ console.log(`${e.substrate}\t${e.sandboxId}\t${e.name ?? '-'}\t${e.state}\t${e.joinUrl}`);
1877
+ }
1878
+ break;
1879
+ }
1880
+ case 'pause':
1881
+ await runPause({ substrate, registryPath, query: parsed.args.query });
1882
+ console.log('Paused.');
1883
+ break;
1884
+ case 'resume': {
1885
+ const result = await runResume({
1886
+ substrate,
1887
+ envFilePath: parsed.args.envFile ?? defaultSecretsPath(),
1888
+ registryPath,
1889
+ query: parsed.args.query,
1890
+ localSliccVersion,
1891
+ });
1892
+ if (result.versionMismatch) {
1893
+ console.warn(`Warning: running sandbox is sliccVersion=${result.versionMismatch.running}, ` +
1894
+ `local CLI is ${result.versionMismatch.local}. Proceeding anyway.`);
1895
+ }
1896
+ if (result.trayRebuilt) {
1897
+ console.warn('Tray was rebuilt; existing followers must re-attach to the new join URL.');
1898
+ }
1899
+ console.log(`Resumed. Open: ${result.joinUrl}`);
1900
+ break;
1901
+ }
1902
+ case 'kill':
1903
+ await runKill({ substrate, registryPath, query: parsed.args.query });
1904
+ console.log('Killed.');
1905
+ break;
1906
+ }
1907
+ }
1765
1908
  main().catch((err) => {
1766
1909
  console.error('Fatal error:', err);
1767
1910
  const errorData = err instanceof Error