@shadowob/cloud 1.1.6-dev.311

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 (334) hide show
  1. package/README.md +509 -0
  2. package/dist/agent-browser-CERTMCDL.js +117 -0
  3. package/dist/agent-browser-CIRZRIY4.js +118 -0
  4. package/dist/agent-pack-LF3O5TR4.js +1236 -0
  5. package/dist/agent-pack-RQT27V7R.js +1235 -0
  6. package/dist/airtable-BG2Q75G2.js +82 -0
  7. package/dist/airtable-JCQXFM5D.js +83 -0
  8. package/dist/alipay-MZX2XCDB.js +52 -0
  9. package/dist/alipay-TZQI34RB.js +51 -0
  10. package/dist/amap-5RQB3VGC.js +45 -0
  11. package/dist/amap-KPCLZYYL.js +44 -0
  12. package/dist/atlassian-LGOEWYC7.js +54 -0
  13. package/dist/atlassian-TVS2A4IU.js +55 -0
  14. package/dist/baidu-appbuilder-6UMESXHW.js +41 -0
  15. package/dist/baidu-appbuilder-QRRL3ETM.js +42 -0
  16. package/dist/baidu-maps-HEPMVP5D.js +44 -0
  17. package/dist/baidu-maps-HXC4FBVP.js +45 -0
  18. package/dist/baidu-netdisk-G5Q6B5NH.js +45 -0
  19. package/dist/baidu-netdisk-RS2K5W2M.js +44 -0
  20. package/dist/baidu-smartprogram-EWTK5WKK.js +41 -0
  21. package/dist/baidu-smartprogram-JHD3XWF6.js +40 -0
  22. package/dist/browserbase-IUIYVYI7.js +67 -0
  23. package/dist/browserbase-JFO2PCIA.js +68 -0
  24. package/dist/canva-3YOFL7JS.js +62 -0
  25. package/dist/canva-FMYN65SM.js +61 -0
  26. package/dist/chunk-6P2K6QZR.js +529 -0
  27. package/dist/chunk-7VMRQ7MG.js +90 -0
  28. package/dist/chunk-AD3JTIU3.js +17 -0
  29. package/dist/chunk-BF6CV2Y4.js +64 -0
  30. package/dist/chunk-CTNUKOQE.js +439 -0
  31. package/dist/chunk-EEFMJYKB.js +97 -0
  32. package/dist/chunk-EJKFQ35I.js +739 -0
  33. package/dist/chunk-HUICDC56.js +62 -0
  34. package/dist/chunk-JUPAE5IA.js +527 -0
  35. package/dist/chunk-JY2HTT7Q.js +437 -0
  36. package/dist/chunk-KEPTCLUO.js +121 -0
  37. package/dist/chunk-KKK5H7YX.js +3622 -0
  38. package/dist/chunk-POSVEKIY.js +210 -0
  39. package/dist/chunk-QET4LT4J.js +5769 -0
  40. package/dist/chunk-QV4XWO3P.js +30 -0
  41. package/dist/chunk-R52J3PH2.js +120 -0
  42. package/dist/chunk-R5U7XKVJ.js +16 -0
  43. package/dist/chunk-RECNVWMT.js +212 -0
  44. package/dist/chunk-RTPBU5HF.js +92 -0
  45. package/dist/chunk-SUZ2ATT6.js +5774 -0
  46. package/dist/chunk-SVMXSIMG.js +98 -0
  47. package/dist/chunk-TV3CBM7R.js +28 -0
  48. package/dist/chunk-V2LU736V.js +3495 -0
  49. package/dist/chunk-ZUYL3W53.js +741 -0
  50. package/dist/claude-plugin-577TAQVS.js +1463 -0
  51. package/dist/claude-plugin-L3MXJJ6J.js +1464 -0
  52. package/dist/cli.js +7021 -0
  53. package/dist/cloudflare-HBBABPK6.js +114 -0
  54. package/dist/cloudflare-RDFPKMM5.js +115 -0
  55. package/dist/cnb-FLP3QX46.js +44 -0
  56. package/dist/cnb-YAVVEYFB.js +45 -0
  57. package/dist/console/index.html +12 -0
  58. package/dist/console/logo.png +0 -0
  59. package/dist/console/static/css/5079.f9e0918d.css +1 -0
  60. package/dist/console/static/css/index.7f91f806.css +1 -0
  61. package/dist/console/static/font/codicon.5b7d6fac.ttf +0 -0
  62. package/dist/console/static/js/5079.72a51ca2.js +699 -0
  63. package/dist/console/static/js/5079.72a51ca2.js.LICENSE.txt +35 -0
  64. package/dist/console/static/js/7426.f8d483ea.js +1 -0
  65. package/dist/console/static/js/async/1008.4df521b7.js +1 -0
  66. package/dist/console/static/js/async/102.1d473ec7.js +1 -0
  67. package/dist/console/static/js/async/1134.3f9fd9e7.js +1 -0
  68. package/dist/console/static/js/async/1318.4b8e48e7.js +1 -0
  69. package/dist/console/static/js/async/1360.5606da88.js +7 -0
  70. package/dist/console/static/js/async/1546.045f484f.js +1 -0
  71. package/dist/console/static/js/async/1562.187de2a8.js +1 -0
  72. package/dist/console/static/js/async/168.456d4813.js +1 -0
  73. package/dist/console/static/js/async/1750.e6dc2664.js +1 -0
  74. package/dist/console/static/js/async/1994.3fc86066.js +1 -0
  75. package/dist/console/static/js/async/2348.613ae3d9.js +1 -0
  76. package/dist/console/static/js/async/2390.1b890b9d.js +1 -0
  77. package/dist/console/static/js/async/2414.9d040212.js +1 -0
  78. package/dist/console/static/js/async/2454.4c1784ab.js +1 -0
  79. package/dist/console/static/js/async/2498.f5f92030.js +1 -0
  80. package/dist/console/static/js/async/2924.b823cd1a.js +1 -0
  81. package/dist/console/static/js/async/3062.63fddea6.js +1 -0
  82. package/dist/console/static/js/async/3078.dd712008.js +1 -0
  83. package/dist/console/static/js/async/3198.1f307065.js +1 -0
  84. package/dist/console/static/js/async/3246.3d5a899f.js +1 -0
  85. package/dist/console/static/js/async/3286.871676eb.js +1 -0
  86. package/dist/console/static/js/async/342.10bf3b90.js +1 -0
  87. package/dist/console/static/js/async/3446.9681a4d7.js +1 -0
  88. package/dist/console/static/js/async/3698.ccfaabec.js +1 -0
  89. package/dist/console/static/js/async/3790.2a1106a6.js +1 -0
  90. package/dist/console/static/js/async/4231.b29784d4.js +1 -0
  91. package/dist/console/static/js/async/4551.515bd41d.js +1 -0
  92. package/dist/console/static/js/async/4596.40f6e71b.js +1 -0
  93. package/dist/console/static/js/async/4600.4aaebe6d.js +1 -0
  94. package/dist/console/static/js/async/4718.1aae022f.js +1 -0
  95. package/dist/console/static/js/async/4846.a347c020.js +1 -0
  96. package/dist/console/static/js/async/4860.83dadf89.js +1 -0
  97. package/dist/console/static/js/async/500.fcfa37cb.js +1 -0
  98. package/dist/console/static/js/async/5096.b360203d.js +1 -0
  99. package/dist/console/static/js/async/5222.043274fe.js +1 -0
  100. package/dist/console/static/js/async/5362.f498001c.js +1 -0
  101. package/dist/console/static/js/async/54.c94f0755.js +1 -0
  102. package/dist/console/static/js/async/5478.50dd9ef0.js +2 -0
  103. package/dist/console/static/js/async/5478.50dd9ef0.js.LICENSE.txt +3 -0
  104. package/dist/console/static/js/async/5507.a6a1f793.js +1 -0
  105. package/dist/console/static/js/async/5638.bc6b102d.js +1 -0
  106. package/dist/console/static/js/async/5722.e0029049.js +1 -0
  107. package/dist/console/static/js/async/5942.74635c6b.js +1 -0
  108. package/dist/console/static/js/async/5994.1c5629c1.js +1 -0
  109. package/dist/console/static/js/async/6054.6bddf720.js +1 -0
  110. package/dist/console/static/js/async/6118.45e754e5.js +1 -0
  111. package/dist/console/static/js/async/6127.adcbcbb6.js +1 -0
  112. package/dist/console/static/js/async/614.3f434c20.js +1 -0
  113. package/dist/console/static/js/async/6234.ba3b002d.js +1 -0
  114. package/dist/console/static/js/async/6310.6546a9ba.js +1 -0
  115. package/dist/console/static/js/async/6378.9f805419.js +1 -0
  116. package/dist/console/static/js/async/6380.e4433c49.js +1 -0
  117. package/dist/console/static/js/async/6418.f23bcfda.js +1 -0
  118. package/dist/console/static/js/async/6428.77c86114.js +1 -0
  119. package/dist/console/static/js/async/6443.83318a6c.js +1 -0
  120. package/dist/console/static/js/async/6508.2b445d62.js +3 -0
  121. package/dist/console/static/js/async/6542.e82a26c8.js +1 -0
  122. package/dist/console/static/js/async/6544.62111ecb.js +1 -0
  123. package/dist/console/static/js/async/6612.a0c9fcf4.js +1 -0
  124. package/dist/console/static/js/async/6740.695aebf0.js +1 -0
  125. package/dist/console/static/js/async/6822.dbbb32bc.js +1 -0
  126. package/dist/console/static/js/async/6824.ad3540ab.js +1 -0
  127. package/dist/console/static/js/async/6930.585dab94.js +1 -0
  128. package/dist/console/static/js/async/6982.c81b95e6.js +1 -0
  129. package/dist/console/static/js/async/7046.ab2378c1.js +1 -0
  130. package/dist/console/static/js/async/7110.a603277f.js +1 -0
  131. package/dist/console/static/js/async/7142.4a21366f.js +1 -0
  132. package/dist/console/static/js/async/7348.15cc6148.js +1373 -0
  133. package/dist/console/static/js/async/7348.15cc6148.js.LICENSE.txt +14 -0
  134. package/dist/console/static/js/async/7374.b1ac5c44.js +1 -0
  135. package/dist/console/static/js/async/742.847f17ca.js +1 -0
  136. package/dist/console/static/js/async/7446.743954d8.js +1 -0
  137. package/dist/console/static/js/async/7673.59bbbaac.js +1 -0
  138. package/dist/console/static/js/async/7684.c5760c8c.js +1 -0
  139. package/dist/console/static/js/async/7714.c30d0f94.js +1 -0
  140. package/dist/console/static/js/async/8118.36d5a3bf.js +298 -0
  141. package/dist/console/static/js/async/8145.4bcf043a.js +1 -0
  142. package/dist/console/static/js/async/8246.408de938.js +1 -0
  143. package/dist/console/static/js/async/8390.bdae1f7d.js +1 -0
  144. package/dist/console/static/js/async/8422.fd94dbe1.js +1 -0
  145. package/dist/console/static/js/async/8434.94a0e2ae.js +1 -0
  146. package/dist/console/static/js/async/8518.3158de13.js +1 -0
  147. package/dist/console/static/js/async/8564.fc2eb841.js +1 -0
  148. package/dist/console/static/js/async/8678.73af4c9b.js +1 -0
  149. package/dist/console/static/js/async/8694.79747168.js +1 -0
  150. package/dist/console/static/js/async/8756.1de37b83.js +1 -0
  151. package/dist/console/static/js/async/8804.7fe6bdf9.js +3 -0
  152. package/dist/console/static/js/async/8883.e717ee94.js +1 -0
  153. package/dist/console/static/js/async/8886.fe6e876c.js +1 -0
  154. package/dist/console/static/js/async/9030.fc1ae402.js +1 -0
  155. package/dist/console/static/js/async/9094.5598d084.js +1 -0
  156. package/dist/console/static/js/async/9218.ee7b84b7.js +1 -0
  157. package/dist/console/static/js/async/94.9b80bc35.js +1 -0
  158. package/dist/console/static/js/async/9526.92aba34c.js +1 -0
  159. package/dist/console/static/js/async/9762.f83bc4f3.js +1 -0
  160. package/dist/console/static/js/async/984.e11c113a.js +1 -0
  161. package/dist/console/static/js/async/9846.246653cd.js +1 -0
  162. package/dist/console/static/js/index.4487e1ff.js +1 -0
  163. package/dist/console/static/js/lib-react.15d7ca9a.js +2 -0
  164. package/dist/console/static/js/lib-react.15d7ca9a.js.LICENSE.txt +49 -0
  165. package/dist/coze-C6PMDPBI.js +49 -0
  166. package/dist/coze-E6VGRNLV.js +48 -0
  167. package/dist/dashboard.command-J7XOZNXU.js +8 -0
  168. package/dist/dashboard.command-RV2NHDKW.js +7 -0
  169. package/dist/dingtalk-JNRNRN7E.js +77 -0
  170. package/dist/dingtalk-WZGGIAHJ.js +76 -0
  171. package/dist/douyin-miniprogram-AIJPPIZH.js +41 -0
  172. package/dist/douyin-miniprogram-HCYZ5NBW.js +42 -0
  173. package/dist/figma-2YYNSCDX.js +103 -0
  174. package/dist/figma-RYOBMENP.js +102 -0
  175. package/dist/firebase-2IJDDBXX.js +112 -0
  176. package/dist/firebase-OYSY6HPT.js +111 -0
  177. package/dist/firecrawl-2T3SBUW7.js +66 -0
  178. package/dist/firecrawl-IYYXLAZM.js +65 -0
  179. package/dist/flyai-7FJ4TRAG.js +81 -0
  180. package/dist/flyai-QS5Q6FJR.js +82 -0
  181. package/dist/gitagent-MWI75OIX.js +725 -0
  182. package/dist/gitagent-YBMWY7NZ.js +726 -0
  183. package/dist/gitee-3N7OFOM7.js +53 -0
  184. package/dist/gitee-KVNK6KLZ.js +54 -0
  185. package/dist/github-LUEC2LID.js +143 -0
  186. package/dist/github-XRO5Z3GC.js +142 -0
  187. package/dist/google-ads-A3QAJI4D.js +88 -0
  188. package/dist/google-ads-VPKWTX67.js +89 -0
  189. package/dist/google-analytics-C4UR5ZR2.js +50 -0
  190. package/dist/google-analytics-XDYZA2B7.js +49 -0
  191. package/dist/google-workspace-LL3EWVHH.js +320 -0
  192. package/dist/google-workspace-YX35SHHX.js +321 -0
  193. package/dist/huawei-xiaoyi-6BSMGJHR.js +40 -0
  194. package/dist/huawei-xiaoyi-KPWLTSHB.js +41 -0
  195. package/dist/hubspot-DIUHGEDI.js +45 -0
  196. package/dist/hubspot-FTIEMNZO.js +44 -0
  197. package/dist/huggingface-MJCOXA7E.js +116 -0
  198. package/dist/huggingface-UUXK2RHK.js +117 -0
  199. package/dist/index.d.ts +3013 -0
  200. package/dist/index.js +15649 -0
  201. package/dist/inference-ai-image-generation-CMI6R5T3.js +106 -0
  202. package/dist/inference-ai-image-generation-PXV6IG4U.js +107 -0
  203. package/dist/inference-sh-7AZOLEFI.js +94 -0
  204. package/dist/inference-sh-ABQOD3YF.js +95 -0
  205. package/dist/init.command-6E24K4H3.js +9 -0
  206. package/dist/init.command-O4HG4HKR.js +10 -0
  207. package/dist/klaviyo-6K5YEFNH.js +45 -0
  208. package/dist/klaviyo-LDPBWBSS.js +44 -0
  209. package/dist/kuaidi100-HGFM5VK2.js +42 -0
  210. package/dist/kuaidi100-UHPFCVXP.js +41 -0
  211. package/dist/lark-6LNA3LUQ.js +103 -0
  212. package/dist/lark-URVBZNS4.js +102 -0
  213. package/dist/linear-7QFSFPOD.js +57 -0
  214. package/dist/linear-T4ORUP7N.js +56 -0
  215. package/dist/lovart-PDUXRUHJ.js +99 -0
  216. package/dist/lovart-QO3SK55T.js +100 -0
  217. package/dist/meta-ads-SCNFI45S.js +42 -0
  218. package/dist/meta-ads-V6XPZWX3.js +41 -0
  219. package/dist/miclaw-5CNTW7VV.js +36 -0
  220. package/dist/miclaw-TPPPS2WN.js +35 -0
  221. package/dist/model-provider-AVSFJSZP.js +393 -0
  222. package/dist/model-provider-KFB76XV5.js +392 -0
  223. package/dist/notion-FZK76MN2.js +69 -0
  224. package/dist/notion-WFA7KGZZ.js +70 -0
  225. package/dist/oceanengine-3JZUS3PP.js +43 -0
  226. package/dist/oceanengine-5BRIJVJE.js +42 -0
  227. package/dist/opencli-PFXHGCS2.js +81 -0
  228. package/dist/opencli-VIGRJTGH.js +80 -0
  229. package/dist/paypal-33UADIPR.js +54 -0
  230. package/dist/paypal-Z5JYHIWD.js +55 -0
  231. package/dist/playwright-MG5WHK47.js +58 -0
  232. package/dist/playwright-SQAQ3DZG.js +59 -0
  233. package/dist/plugins-HZBWK3WQ.js +120 -0
  234. package/dist/plugins-I4GD5SZX.js +121 -0
  235. package/dist/posthog-MU5MAJOQ.js +79 -0
  236. package/dist/posthog-RJRRKDWB.js +80 -0
  237. package/dist/salesforce-34FVIJTG.js +82 -0
  238. package/dist/salesforce-3QZ6OFVO.js +83 -0
  239. package/dist/sentry-MCIRMACU.js +111 -0
  240. package/dist/sentry-PIWW46VA.js +110 -0
  241. package/dist/seo-suite-4SQ3I67Q.js +54 -0
  242. package/dist/seo-suite-WJXMA3S4.js +55 -0
  243. package/dist/serve.command-5FMIPQRY.js +10 -0
  244. package/dist/serve.command-DNE6GPMK.js +9 -0
  245. package/dist/shadowob-JELOWHWX.js +1068 -0
  246. package/dist/shadowob-PRSMI5MW.js +1069 -0
  247. package/dist/sherlock-2PKY2E2Y.js +66 -0
  248. package/dist/sherlock-C5ZWPPVT.js +67 -0
  249. package/dist/shopify-GL3NFVGE.js +94 -0
  250. package/dist/shopify-R4G3UXM6.js +93 -0
  251. package/dist/skill-discovery-7INAUP4D.js +77 -0
  252. package/dist/skill-discovery-YPXXV622.js +78 -0
  253. package/dist/state-7MCZBTR5.js +17 -0
  254. package/dist/state-FGOFLFBE.js +18 -0
  255. package/dist/stripe-C22RR4ZS.js +83 -0
  256. package/dist/stripe-LJNPQ3CQ.js +82 -0
  257. package/dist/supabase-IRNQ54FJ.js +98 -0
  258. package/dist/supabase-N4ONFJNQ.js +97 -0
  259. package/dist/taobao-aipaas-LRR4GMO3.js +45 -0
  260. package/dist/taobao-aipaas-RVKORSF4.js +46 -0
  261. package/dist/tapd-3JPVJ7XH.js +46 -0
  262. package/dist/tapd-TMQRSMFG.js +47 -0
  263. package/dist/tencent-ads-IGD33LO7.js +42 -0
  264. package/dist/tencent-ads-UHC6OPBV.js +43 -0
  265. package/dist/tencent-docs-C3A4J3CJ.js +47 -0
  266. package/dist/tencent-docs-O2SC4FHL.js +48 -0
  267. package/dist/tencent-maps-HMMWMNF4.js +37 -0
  268. package/dist/tencent-maps-OQOKHVW2.js +36 -0
  269. package/dist/vercel-KOXDDTHX.js +73 -0
  270. package/dist/vercel-OLNVDWMG.js +74 -0
  271. package/dist/webflow-FULU5Q2I.js +114 -0
  272. package/dist/webflow-OMJKZM54.js +115 -0
  273. package/dist/wechat-miniprogram-skyline-KYCDMQNW.js +74 -0
  274. package/dist/wechat-miniprogram-skyline-VR4FVIQL.js +75 -0
  275. package/dist/wechat-pay-BCMAJ6UW.js +50 -0
  276. package/dist/wechat-pay-YQQKXVUI.js +51 -0
  277. package/dist/wonda-NGWIORYN.js +81 -0
  278. package/dist/wonda-RBABXFNM.js +82 -0
  279. package/dist/wordpress-woocommerce-RDIUTHYT.js +57 -0
  280. package/dist/wordpress-woocommerce-RNA5HB3N.js +58 -0
  281. package/dist/wps-DAEFQHDE.js +47 -0
  282. package/dist/wps-LUWHMZQQ.js +48 -0
  283. package/dist/yuque-HCHTJWNI.js +72 -0
  284. package/dist/yuque-KRH5O74J.js +71 -0
  285. package/images/RUNNERS.md +270 -0
  286. package/images/cc-connect-runner/entrypoint.mjs +311 -0
  287. package/images/claude-runner/Dockerfile +88 -0
  288. package/images/claude-runner/RUNNER.md +222 -0
  289. package/images/claude-runner/entrypoint.mjs +2 -0
  290. package/images/codex-runner/Dockerfile +87 -0
  291. package/images/codex-runner/RUNNER.md +226 -0
  292. package/images/codex-runner/entrypoint.mjs +2 -0
  293. package/images/gemini-runner/Dockerfile +87 -0
  294. package/images/gemini-runner/RUNNER.md +218 -0
  295. package/images/gemini-runner/entrypoint.mjs +2 -0
  296. package/images/hermes-runner/Dockerfile +74 -0
  297. package/images/hermes-runner/RUNNER.md +243 -0
  298. package/images/hermes-runner/entrypoint.mjs +283 -0
  299. package/images/openclaw-runner/Dockerfile +212 -0
  300. package/images/openclaw-runner/RUNNER.md +170 -0
  301. package/images/openclaw-runner/entrypoint.mjs +1113 -0
  302. package/images/openclaw-runner/warm-runtime-deps.mjs +95 -0
  303. package/images/opencode-runner/Dockerfile +87 -0
  304. package/images/opencode-runner/RUNNER.md +202 -0
  305. package/images/opencode-runner/entrypoint.mjs +2 -0
  306. package/package.json +121 -0
  307. package/templates/agent-marketplace-buddy.template.json +131 -0
  308. package/templates/ai-werewolf.template.json +92 -0
  309. package/templates/bmad-method-buddy.template.json +123 -0
  310. package/templates/brain-fix.template.json +92 -0
  311. package/templates/claude-ads-buddy.template.json +123 -0
  312. package/templates/claude-financial-services-buddy.template.json +111 -0
  313. package/templates/claude-seo-buddy.template.json +123 -0
  314. package/templates/code-arena.template.json +92 -0
  315. package/templates/daily-brief.template.json +92 -0
  316. package/templates/e-wife.template.json +92 -0
  317. package/templates/everything-claude-code-buddy.template.json +125 -0
  318. package/templates/financial-freedom.template.json +92 -0
  319. package/templates/gitstory.template.json +92 -0
  320. package/templates/google-workspace-buddy.template.json +88 -0
  321. package/templates/gsd-buddy.template.json +119 -0
  322. package/templates/gstack-buddy.template.json +143 -0
  323. package/templates/gstack.template.json +92 -0
  324. package/templates/little-match-girl.template.json +114 -0
  325. package/templates/lovart-buddy.template.json +110 -0
  326. package/templates/marketingskills-buddy.template.json +102 -0
  327. package/templates/retire-buddy.template.json +92 -0
  328. package/templates/scientific-skills-buddy.template.json +119 -0
  329. package/templates/seomachine-buddy.template.json +113 -0
  330. package/templates/shadow-server-app-demo.template.json +105 -0
  331. package/templates/slavingia-skills-buddy.template.json +102 -0
  332. package/templates/superclaude-buddy.template.json +146 -0
  333. package/templates/superpowers-buddy.template.json +108 -0
  334. package/templates/world-pulse.template.json +92 -0
@@ -0,0 +1,3622 @@
1
+ import {
2
+ applyRuntimeEnvRefPolicy,
3
+ collectRuntimeEnvFields,
4
+ collectRuntimeEnvRefPolicy,
5
+ collectRuntimeEnvRequirements,
6
+ isDeploymentRuntimeContextEmpty,
7
+ normalizeDeploymentRuntimeContext,
8
+ parseJsonc,
9
+ runtimeStatePvcName
10
+ } from "./chunk-SUZ2ATT6.js";
11
+ import {
12
+ getPluginRegistry,
13
+ loadAllPlugins
14
+ } from "./chunk-JUPAE5IA.js";
15
+ import {
16
+ __export,
17
+ __require
18
+ } from "./chunk-R5U7XKVJ.js";
19
+
20
+ // src/interfaces/cli/serve.command.ts
21
+ import { Command } from "commander";
22
+
23
+ // src/interfaces/http/server.ts
24
+ import { randomBytes as randomBytes3 } from "crypto";
25
+ import { existsSync as existsSync7 } from "fs";
26
+ import { resolve as resolve6 } from "path";
27
+ import { fileURLToPath as fileURLToPath2 } from "url";
28
+ import { serve } from "@hono/node-server";
29
+
30
+ // src/dao/activity.dao.ts
31
+ import { desc } from "drizzle-orm";
32
+
33
+ // src/db/schema.ts
34
+ var schema_exports = {};
35
+ __export(schema_exports, {
36
+ activities: () => activities,
37
+ configVersions: () => configVersions,
38
+ configs: () => configs,
39
+ deploymentBackups: () => deploymentBackups,
40
+ deploymentLogs: () => deploymentLogs,
41
+ deployments: () => deployments,
42
+ envGroups: () => envGroups,
43
+ envVars: () => envVars,
44
+ secrets: () => secrets
45
+ });
46
+ import { sql } from "drizzle-orm";
47
+ import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
48
+ var secrets = sqliteTable("secrets", {
49
+ id: integer("id").primaryKey({ autoIncrement: true }),
50
+ providerId: text("provider_id").notNull(),
51
+ key: text("key").notNull(),
52
+ encryptedValue: text("encrypted_value").notNull(),
53
+ iv: text("iv").notNull(),
54
+ groupName: text("group_name").notNull().default("default"),
55
+ createdAt: text("created_at").default(sql`(datetime('now'))`),
56
+ updatedAt: text("updated_at").default(sql`(datetime('now'))`)
57
+ });
58
+ var configs = sqliteTable("configs", {
59
+ id: integer("id").primaryKey({ autoIncrement: true }),
60
+ name: text("name").notNull().unique(),
61
+ content: text("content", { mode: "json" }).notNull(),
62
+ templateSlug: text("template_slug"),
63
+ version: integer("version").notNull().default(1),
64
+ createdAt: text("created_at").default(sql`(datetime('now'))`),
65
+ updatedAt: text("updated_at").default(sql`(datetime('now'))`)
66
+ });
67
+ var deployments = sqliteTable("deployments", {
68
+ id: integer("id").primaryKey({ autoIncrement: true }),
69
+ namespace: text("namespace").notNull(),
70
+ templateSlug: text("template_slug"),
71
+ version: integer("version"),
72
+ status: text("status").notNull().default("pending"),
73
+ config: text("config", { mode: "json" }),
74
+ agentCount: integer("agent_count"),
75
+ error: text("error"),
76
+ createdAt: text("created_at").default(sql`(datetime('now'))`),
77
+ updatedAt: text("updated_at").default(sql`(datetime('now'))`)
78
+ });
79
+ var deploymentLogs = sqliteTable("deployment_logs", {
80
+ id: integer("id").primaryKey({ autoIncrement: true }),
81
+ deploymentId: integer("deployment_id").notNull().references(() => deployments.id, { onDelete: "cascade" }),
82
+ event: text("event").notNull().default("log"),
83
+ message: text("message").notNull(),
84
+ createdAt: text("created_at").default(sql`(datetime('now'))`)
85
+ });
86
+ var deploymentBackups = sqliteTable("deployment_backups", {
87
+ id: integer("id").primaryKey({ autoIncrement: true }),
88
+ deploymentId: integer("deployment_id").references(() => deployments.id, { onDelete: "cascade" }),
89
+ namespace: text("namespace").notNull(),
90
+ agentId: text("agent_id").notNull(),
91
+ sandboxName: text("sandbox_name"),
92
+ pvcName: text("pvc_name").notNull(),
93
+ driver: text("driver").notNull(),
94
+ snapshotName: text("snapshot_name"),
95
+ objectKey: text("object_key"),
96
+ status: text("status").notNull().default("pending"),
97
+ error: text("error"),
98
+ expiresAt: text("expires_at"),
99
+ createdAt: text("created_at").default(sql`(datetime('now'))`),
100
+ updatedAt: text("updated_at").default(sql`(datetime('now'))`)
101
+ });
102
+ var activities = sqliteTable("activities", {
103
+ id: integer("id").primaryKey({ autoIncrement: true }),
104
+ type: text("type").notNull(),
105
+ title: text("title").notNull(),
106
+ detail: text("detail"),
107
+ namespace: text("namespace"),
108
+ template: text("template"),
109
+ createdAt: text("created_at").default(sql`(datetime('now'))`)
110
+ });
111
+ var envVars = sqliteTable("env_vars", {
112
+ id: integer("id").primaryKey({ autoIncrement: true }),
113
+ scope: text("scope").notNull().default("global"),
114
+ key: text("key").notNull(),
115
+ encryptedValue: text("encrypted_value").notNull(),
116
+ iv: text("iv").notNull(),
117
+ isSecret: integer("is_secret", { mode: "boolean" }).default(true),
118
+ groupName: text("group_name").notNull().default("default"),
119
+ createdAt: text("created_at").default(sql`(datetime('now'))`),
120
+ updatedAt: text("updated_at").default(sql`(datetime('now'))`)
121
+ });
122
+ var envGroups = sqliteTable("env_groups", {
123
+ id: integer("id").primaryKey({ autoIncrement: true }),
124
+ name: text("name").notNull().unique(),
125
+ createdAt: text("created_at").default(sql`(datetime('now'))`),
126
+ updatedAt: text("updated_at").default(sql`(datetime('now'))`)
127
+ });
128
+ var configVersions = sqliteTable("config_versions", {
129
+ id: integer("id").primaryKey({ autoIncrement: true }),
130
+ configName: text("config_name").notNull(),
131
+ version: integer("version").notNull(),
132
+ content: text("content", { mode: "json" }).notNull(),
133
+ message: text("message"),
134
+ createdAt: text("created_at").default(sql`(datetime('now'))`)
135
+ });
136
+
137
+ // src/dao/activity.dao.ts
138
+ var ActivityDao = class {
139
+ constructor(db) {
140
+ this.db = db;
141
+ }
142
+ findAll(limit = 500) {
143
+ return this.db.select().from(activities).orderBy(desc(activities.createdAt)).limit(limit).all();
144
+ }
145
+ create(data) {
146
+ return this.db.insert(activities).values(data).returning().get();
147
+ }
148
+ clear() {
149
+ this.db.delete(activities).run();
150
+ }
151
+ };
152
+
153
+ // src/dao/config.dao.ts
154
+ import { desc as desc2, eq } from "drizzle-orm";
155
+ var ConfigDao = class {
156
+ constructor(db) {
157
+ this.db = db;
158
+ }
159
+ findByName(name) {
160
+ return this.db.select().from(configs).where(eq(configs.name, name)).get();
161
+ }
162
+ findAll() {
163
+ return this.db.select().from(configs).all();
164
+ }
165
+ upsert(name, content, templateSlug) {
166
+ const existing = this.findByName(name);
167
+ if (existing) {
168
+ this.db.insert(configVersions).values({
169
+ configName: name,
170
+ version: existing.version ?? 1,
171
+ content: existing.content
172
+ }).run();
173
+ const newVersion = (existing.version ?? 1) + 1;
174
+ return this.db.update(configs).set({
175
+ content,
176
+ templateSlug,
177
+ version: newVersion,
178
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
179
+ }).where(eq(configs.name, name)).returning().get();
180
+ }
181
+ return this.db.insert(configs).values({ name, content, templateSlug, version: 1 }).returning().get();
182
+ }
183
+ getVersionHistory(name) {
184
+ return this.db.select().from(configVersions).where(eq(configVersions.configName, name)).orderBy(desc2(configVersions.version)).all();
185
+ }
186
+ getVersion(name, version) {
187
+ return this.db.select().from(configVersions).where(eq(configVersions.configName, name)).all().find((v) => v.version === version);
188
+ }
189
+ delete(name) {
190
+ this.db.delete(configs).where(eq(configs.name, name)).run();
191
+ this.db.delete(configVersions).where(eq(configVersions.configName, name)).run();
192
+ }
193
+ };
194
+
195
+ // src/dao/deployment.dao.ts
196
+ import { desc as desc3, eq as eq2 } from "drizzle-orm";
197
+ var DeploymentDao = class {
198
+ constructor(db) {
199
+ this.db = db;
200
+ }
201
+ findAll() {
202
+ return this.db.select().from(deployments).orderBy(desc3(deployments.createdAt)).all();
203
+ }
204
+ findById(id) {
205
+ return this.db.select().from(deployments).where(eq2(deployments.id, id)).get();
206
+ }
207
+ findByNamespace(namespace) {
208
+ return this.db.select().from(deployments).where(eq2(deployments.namespace, namespace)).orderBy(desc3(deployments.createdAt)).all();
209
+ }
210
+ create(data) {
211
+ return this.db.insert(deployments).values(data).returning().get();
212
+ }
213
+ update(id, data) {
214
+ return this.db.update(deployments).set({ ...data, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq2(deployments.id, id)).returning().get();
215
+ }
216
+ updateStatus(id, status, error) {
217
+ return this.update(id, { status, error });
218
+ }
219
+ delete(id) {
220
+ this.db.delete(deployments).where(eq2(deployments.id, id)).run();
221
+ }
222
+ };
223
+
224
+ // src/dao/deployment-backup.dao.ts
225
+ import { and, desc as desc4, eq as eq3 } from "drizzle-orm";
226
+ var DeploymentBackupDao = class {
227
+ constructor(db) {
228
+ this.db = db;
229
+ }
230
+ findByAgent(namespace, agentId) {
231
+ return this.db.select().from(deploymentBackups).where(
232
+ and(eq3(deploymentBackups.namespace, namespace), eq3(deploymentBackups.agentId, agentId))
233
+ ).orderBy(desc4(deploymentBackups.createdAt)).all();
234
+ }
235
+ create(data) {
236
+ return this.db.insert(deploymentBackups).values(data).returning().get();
237
+ }
238
+ update(id, data) {
239
+ return this.db.update(deploymentBackups).set({ ...data, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq3(deploymentBackups.id, id)).returning().get();
240
+ }
241
+ };
242
+
243
+ // src/dao/deployment-log.dao.ts
244
+ import { asc, eq as eq4 } from "drizzle-orm";
245
+ var DeploymentLogDao = class {
246
+ constructor(db) {
247
+ this.db = db;
248
+ }
249
+ findByDeploymentId(deploymentId) {
250
+ return this.db.select().from(deploymentLogs).where(eq4(deploymentLogs.deploymentId, deploymentId)).orderBy(asc(deploymentLogs.id)).all();
251
+ }
252
+ findByDeploymentIdSince(deploymentId, lastId) {
253
+ return this.findByDeploymentId(deploymentId).filter((log) => log.id > lastId);
254
+ }
255
+ create(data) {
256
+ return this.db.insert(deploymentLogs).values(data).returning().get();
257
+ }
258
+ };
259
+
260
+ // src/dao/env-group.dao.ts
261
+ import { asc as asc2, eq as eq5 } from "drizzle-orm";
262
+ var EnvGroupDao = class {
263
+ constructor(db) {
264
+ this.db = db;
265
+ }
266
+ findAll() {
267
+ const rows = this.db.select().from(envGroups).orderBy(asc2(envGroups.name)).all();
268
+ return rows.map((row) => row.name).sort((a, b) => a === "default" ? -1 : b === "default" ? 1 : a.localeCompare(b));
269
+ }
270
+ ensure(name) {
271
+ const normalized = name.trim() || "default";
272
+ this.db.insert(envGroups).values({ name: normalized }).onConflictDoNothing().run();
273
+ }
274
+ create(name) {
275
+ this.ensure(name);
276
+ }
277
+ delete(name) {
278
+ if (name === "default") return;
279
+ this.db.delete(envGroups).where(eq5(envGroups.name, name)).run();
280
+ }
281
+ };
282
+
283
+ // src/dao/envvar.dao.ts
284
+ import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
285
+ import { and as and2, eq as eq6 } from "drizzle-orm";
286
+
287
+ // src/utils/env-names.ts
288
+ function normalizeToken(value) {
289
+ return value.trim().toUpperCase().replace(/-/g, "_");
290
+ }
291
+ function normalizeGroupName(value) {
292
+ const normalized = value?.trim();
293
+ return normalized ? normalized : "default";
294
+ }
295
+ function toProviderSecretEnvKey(providerId, key) {
296
+ const providerToken = normalizeToken(providerId);
297
+ const keyToken = normalizeToken(key);
298
+ if (keyToken === "APIKEY" || keyToken === "API_KEY") {
299
+ return `${providerToken}_API_KEY`;
300
+ }
301
+ return `${providerToken}_${keyToken}`;
302
+ }
303
+ function withLegacyEnvAliases(key, value) {
304
+ const envs = { [key]: value };
305
+ if (key.endsWith("_API_KEY")) {
306
+ envs[key.replace(/_API_KEY$/, "_APIKEY")] = value;
307
+ }
308
+ return envs;
309
+ }
310
+
311
+ // src/dao/envvar.dao.ts
312
+ var ALGORITHM = "aes-256-gcm";
313
+ var KEY_LENGTH = 32;
314
+ var IV_LENGTH = 16;
315
+ var AUTH_TAG_LENGTH = 16;
316
+ function deriveKey(passphrase, salt) {
317
+ return scryptSync(passphrase, salt, KEY_LENGTH);
318
+ }
319
+ var EnvVarDao = class {
320
+ constructor(db, passphrase) {
321
+ this.db = db;
322
+ const configuredPassphrase = passphrase ?? process.env.SHADOWOB_PASSPHRASE;
323
+ if (configuredPassphrase) {
324
+ this.passphrase = configuredPassphrase;
325
+ return;
326
+ }
327
+ if (process.env.NODE_ENV === "production") {
328
+ throw new Error("SHADOWOB_PASSPHRASE is required in production");
329
+ }
330
+ console.warn("[cloud] SHADOWOB_PASSPHRASE is not set; using insecure development fallback");
331
+ this.passphrase = "shadowob-cloud-default";
332
+ }
333
+ passphrase;
334
+ encrypt(plaintext) {
335
+ const salt = randomBytes(16);
336
+ const key = deriveKey(this.passphrase, salt);
337
+ const iv = randomBytes(IV_LENGTH);
338
+ const cipher = createCipheriv(ALGORITHM, key, iv);
339
+ let encrypted = cipher.update(plaintext, "utf-8", "hex");
340
+ encrypted += cipher.final("hex");
341
+ const authTag = cipher.getAuthTag();
342
+ return {
343
+ encrypted: salt.toString("hex") + authTag.toString("hex") + encrypted,
344
+ iv: iv.toString("hex")
345
+ };
346
+ }
347
+ decrypt(encryptedHex, ivHex) {
348
+ const salt = Buffer.from(encryptedHex.slice(0, 32), "hex");
349
+ const authTag = Buffer.from(encryptedHex.slice(32, 32 + AUTH_TAG_LENGTH * 2), "hex");
350
+ const ciphertext = encryptedHex.slice(32 + AUTH_TAG_LENGTH * 2);
351
+ const key = deriveKey(this.passphrase, salt);
352
+ const iv = Buffer.from(ivHex, "hex");
353
+ const decipher = createDecipheriv(ALGORITHM, key, iv);
354
+ decipher.setAuthTag(authTag);
355
+ let decrypted = decipher.update(ciphertext, "hex", "utf-8");
356
+ decrypted += decipher.final("utf-8");
357
+ return decrypted;
358
+ }
359
+ findByScope(scope) {
360
+ const rows = this.db.select().from(envVars).where(eq6(envVars.scope, scope)).all();
361
+ return rows.map((r) => ({
362
+ key: r.key,
363
+ value: this.decrypt(r.encryptedValue, r.iv),
364
+ isSecret: r.isSecret ?? true
365
+ }));
366
+ }
367
+ findOne(scope, key) {
368
+ const row = this.db.select().from(envVars).where(and2(eq6(envVars.scope, scope), eq6(envVars.key, key))).get();
369
+ if (!row) {
370
+ return null;
371
+ }
372
+ return {
373
+ scope: row.scope,
374
+ key: row.key,
375
+ value: this.decrypt(row.encryptedValue, row.iv),
376
+ isSecret: row.isSecret ?? true,
377
+ groupName: normalizeGroupName(row.groupName)
378
+ };
379
+ }
380
+ findAllMasked() {
381
+ const rows = this.db.select().from(envVars).all();
382
+ return rows.map((r) => {
383
+ const val = this.decrypt(r.encryptedValue, r.iv);
384
+ return {
385
+ scope: r.scope,
386
+ key: r.key,
387
+ maskedValue: r.isSecret ? val.length > 8 ? `${val.slice(0, 4)}${"\u2022".repeat(Math.min(val.length - 8, 20))}${val.slice(-4)}` : "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : val,
388
+ isSecret: r.isSecret ?? true,
389
+ groupName: normalizeGroupName(r.groupName)
390
+ };
391
+ });
392
+ }
393
+ findMaskedByScope(scope) {
394
+ return this.findAllMasked().filter((entry) => entry.scope === scope);
395
+ }
396
+ findAllMaskedByScopes(scopes) {
397
+ const scopedEntries = this.findAllMasked().filter((entry) => scopes.includes(entry.scope));
398
+ const merged = /* @__PURE__ */ new Map();
399
+ for (const scope of scopes) {
400
+ for (const entry of scopedEntries.filter((item) => item.scope === scope)) {
401
+ merged.set(entry.key, entry);
402
+ }
403
+ }
404
+ return [...merged.values()].sort((left, right) => left.key.localeCompare(right.key));
405
+ }
406
+ upsert(scope, key, value, isSecret = true, groupName = "default") {
407
+ const existing = this.db.select().from(envVars).where(and2(eq6(envVars.scope, scope), eq6(envVars.key, key))).get();
408
+ const { encrypted, iv } = this.encrypt(value);
409
+ if (existing) {
410
+ return this.db.update(envVars).set({
411
+ encryptedValue: encrypted,
412
+ iv,
413
+ isSecret,
414
+ groupName: normalizeGroupName(groupName),
415
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
416
+ }).where(eq6(envVars.id, existing.id)).returning().get();
417
+ }
418
+ return this.db.insert(envVars).values({
419
+ scope,
420
+ key,
421
+ encryptedValue: encrypted,
422
+ iv,
423
+ isSecret,
424
+ groupName: normalizeGroupName(groupName)
425
+ }).returning().get();
426
+ }
427
+ delete(scope, key) {
428
+ this.db.delete(envVars).where(and2(eq6(envVars.scope, scope), eq6(envVars.key, key))).run();
429
+ }
430
+ getValue(scope, key) {
431
+ const row = this.db.select().from(envVars).where(and2(eq6(envVars.scope, scope), eq6(envVars.key, key))).get();
432
+ if (!row) return null;
433
+ return this.decrypt(row.encryptedValue, row.iv);
434
+ }
435
+ /** Return all env vars (decrypted) as a flat key→value map for deploy resolution. */
436
+ findAllDecrypted() {
437
+ const rows = this.db.select().from(envVars).all();
438
+ const result = {};
439
+ for (const r of rows) {
440
+ Object.assign(result, withLegacyEnvAliases(r.key, this.decrypt(r.encryptedValue, r.iv)));
441
+ }
442
+ return result;
443
+ }
444
+ findAllDecryptedByScopes(scopes) {
445
+ const rows = this.db.select().from(envVars).all();
446
+ const result = {};
447
+ for (const scope of scopes) {
448
+ for (const row of rows.filter((entry) => entry.scope === scope)) {
449
+ Object.assign(
450
+ result,
451
+ withLegacyEnvAliases(row.key, this.decrypt(row.encryptedValue, row.iv))
452
+ );
453
+ }
454
+ }
455
+ return result;
456
+ }
457
+ };
458
+
459
+ // src/dao/secret.dao.ts
460
+ import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, randomBytes as randomBytes2, scryptSync as scryptSync2 } from "crypto";
461
+ import { and as and3, eq as eq7 } from "drizzle-orm";
462
+ var ALGORITHM2 = "aes-256-gcm";
463
+ var KEY_LENGTH2 = 32;
464
+ var IV_LENGTH2 = 16;
465
+ var AUTH_TAG_LENGTH2 = 16;
466
+ function deriveKey2(passphrase, salt) {
467
+ return scryptSync2(passphrase, salt, KEY_LENGTH2);
468
+ }
469
+ var SecretDao = class {
470
+ constructor(db, passphrase) {
471
+ this.db = db;
472
+ const configuredPassphrase = passphrase ?? process.env.SHADOWOB_PASSPHRASE;
473
+ if (configuredPassphrase) {
474
+ this.passphrase = configuredPassphrase;
475
+ return;
476
+ }
477
+ if (process.env.NODE_ENV === "production") {
478
+ throw new Error("SHADOWOB_PASSPHRASE is required in production");
479
+ }
480
+ console.warn("[cloud] SHADOWOB_PASSPHRASE is not set; using insecure development fallback");
481
+ this.passphrase = "shadowob-cloud-default";
482
+ }
483
+ passphrase;
484
+ encrypt(plaintext) {
485
+ const salt = randomBytes2(16);
486
+ const key = deriveKey2(this.passphrase, salt);
487
+ const iv = randomBytes2(IV_LENGTH2);
488
+ const cipher = createCipheriv2(ALGORITHM2, key, iv);
489
+ let encrypted = cipher.update(plaintext, "utf-8", "hex");
490
+ encrypted += cipher.final("hex");
491
+ const authTag = cipher.getAuthTag();
492
+ return {
493
+ encrypted: salt.toString("hex") + authTag.toString("hex") + encrypted,
494
+ iv: iv.toString("hex")
495
+ };
496
+ }
497
+ decrypt(encryptedHex, ivHex) {
498
+ const salt = Buffer.from(encryptedHex.slice(0, 32), "hex");
499
+ const authTag = Buffer.from(encryptedHex.slice(32, 32 + AUTH_TAG_LENGTH2 * 2), "hex");
500
+ const ciphertext = encryptedHex.slice(32 + AUTH_TAG_LENGTH2 * 2);
501
+ const key = deriveKey2(this.passphrase, salt);
502
+ const iv = Buffer.from(ivHex, "hex");
503
+ const decipher = createDecipheriv2(ALGORITHM2, key, iv);
504
+ decipher.setAuthTag(authTag);
505
+ let decrypted = decipher.update(ciphertext, "hex", "utf-8");
506
+ decrypted += decipher.final("utf-8");
507
+ return decrypted;
508
+ }
509
+ findByProvider(providerId) {
510
+ const rows = this.db.select().from(secrets).where(eq7(secrets.providerId, providerId)).all();
511
+ return rows.map((r) => ({
512
+ key: r.key,
513
+ value: this.decrypt(r.encryptedValue, r.iv)
514
+ }));
515
+ }
516
+ findAll() {
517
+ const rows = this.db.select().from(secrets).all();
518
+ return rows.map((r) => {
519
+ const val = this.decrypt(r.encryptedValue, r.iv);
520
+ return {
521
+ providerId: r.providerId,
522
+ key: r.key,
523
+ maskedValue: val.length > 8 ? `${val.slice(0, 4)}${"\u2022".repeat(val.length - 8)}${val.slice(-4)}` : "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
524
+ groupName: normalizeGroupName(r.groupName)
525
+ };
526
+ });
527
+ }
528
+ findAllDecryptedEntries() {
529
+ const rows = this.db.select().from(secrets).all();
530
+ return rows.map((row) => ({
531
+ providerId: row.providerId,
532
+ key: row.key,
533
+ value: this.decrypt(row.encryptedValue, row.iv),
534
+ groupName: normalizeGroupName(row.groupName)
535
+ }));
536
+ }
537
+ findByGroup(groupName) {
538
+ const rows = this.db.select().from(secrets).where(eq7(secrets.groupName, groupName)).all();
539
+ return rows.map((r) => {
540
+ const val = this.decrypt(r.encryptedValue, r.iv);
541
+ return {
542
+ providerId: r.providerId,
543
+ key: r.key,
544
+ maskedValue: val.length > 8 ? `${val.slice(0, 4)}${"\u2022".repeat(val.length - 8)}${val.slice(-4)}` : "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"
545
+ };
546
+ });
547
+ }
548
+ upsert(providerId, key, value, groupName = "default") {
549
+ const existing = this.db.select().from(secrets).where(and3(eq7(secrets.providerId, providerId), eq7(secrets.key, key))).get();
550
+ const { encrypted, iv } = this.encrypt(value);
551
+ if (existing) {
552
+ return this.db.update(secrets).set({
553
+ encryptedValue: encrypted,
554
+ iv,
555
+ groupName: normalizeGroupName(groupName),
556
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
557
+ }).where(eq7(secrets.id, existing.id)).returning().get();
558
+ }
559
+ return this.db.insert(secrets).values({
560
+ providerId,
561
+ key,
562
+ encryptedValue: encrypted,
563
+ iv,
564
+ groupName: normalizeGroupName(groupName)
565
+ }).returning().get();
566
+ }
567
+ delete(providerId, key) {
568
+ this.db.delete(secrets).where(and3(eq7(secrets.providerId, providerId), eq7(secrets.key, key))).run();
569
+ }
570
+ deleteProvider(providerId) {
571
+ this.db.delete(secrets).where(eq7(secrets.providerId, providerId)).run();
572
+ }
573
+ deleteAll() {
574
+ this.db.delete(secrets).run();
575
+ }
576
+ /** Get decrypted value for a provider key — used internally by deploy flow. */
577
+ getValue(providerId, key) {
578
+ const row = this.db.select().from(secrets).where(and3(eq7(secrets.providerId, providerId), eq7(secrets.key, key))).get();
579
+ if (!row) return null;
580
+ return this.decrypt(row.encryptedValue, row.iv);
581
+ }
582
+ /** Return all secrets (decrypted) mapped to env var names for deploy resolution. */
583
+ findAllDecrypted() {
584
+ const result = {};
585
+ for (const entry of this.findAllDecryptedEntries()) {
586
+ Object.assign(
587
+ result,
588
+ withLegacyEnvAliases(toProviderSecretEnvKey(entry.providerId, entry.key), entry.value)
589
+ );
590
+ }
591
+ return result;
592
+ }
593
+ };
594
+
595
+ // src/db/index.ts
596
+ import { mkdirSync } from "fs";
597
+ import { homedir } from "os";
598
+ import { join } from "path";
599
+ import Database from "better-sqlite3";
600
+ import { drizzle } from "drizzle-orm/better-sqlite3";
601
+ function createDatabase(dbPath) {
602
+ const resolvedPath = dbPath ?? join(homedir(), ".shadowob", "cloud.db");
603
+ mkdirSync(join(homedir(), ".shadowob"), { recursive: true });
604
+ const sqlite = new Database(resolvedPath);
605
+ sqlite.pragma("journal_mode = WAL");
606
+ sqlite.pragma("foreign_keys = ON");
607
+ return drizzle(sqlite, { schema: schema_exports });
608
+ }
609
+
610
+ // src/db/migrate.ts
611
+ function runMigrations(db) {
612
+ db.run(
613
+ /*sql*/
614
+ `
615
+ CREATE TABLE IF NOT EXISTS templates (
616
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
617
+ slug TEXT NOT NULL UNIQUE,
618
+ name TEXT NOT NULL,
619
+ description TEXT,
620
+ category TEXT,
621
+ featured INTEGER DEFAULT 0,
622
+ content TEXT NOT NULL,
623
+ version TEXT DEFAULT '1.0.0',
624
+ metadata TEXT,
625
+ created_at TEXT DEFAULT (datetime('now')),
626
+ updated_at TEXT DEFAULT (datetime('now'))
627
+ )
628
+ `
629
+ );
630
+ db.run(
631
+ /*sql*/
632
+ `
633
+ CREATE TABLE IF NOT EXISTS secrets (
634
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
635
+ provider_id TEXT NOT NULL,
636
+ key TEXT NOT NULL,
637
+ encrypted_value TEXT NOT NULL,
638
+ iv TEXT NOT NULL,
639
+ group_name TEXT NOT NULL DEFAULT 'default',
640
+ created_at TEXT DEFAULT (datetime('now')),
641
+ updated_at TEXT DEFAULT (datetime('now'))
642
+ )
643
+ `
644
+ );
645
+ db.run(
646
+ /*sql*/
647
+ `
648
+ CREATE TABLE IF NOT EXISTS configs (
649
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
650
+ name TEXT NOT NULL UNIQUE,
651
+ content TEXT NOT NULL,
652
+ template_slug TEXT,
653
+ created_at TEXT DEFAULT (datetime('now')),
654
+ updated_at TEXT DEFAULT (datetime('now'))
655
+ )
656
+ `
657
+ );
658
+ db.run(
659
+ /*sql*/
660
+ `
661
+ CREATE TABLE IF NOT EXISTS deployments (
662
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
663
+ namespace TEXT NOT NULL,
664
+ template_slug TEXT,
665
+ status TEXT NOT NULL DEFAULT 'pending',
666
+ config TEXT,
667
+ agent_count INTEGER,
668
+ error TEXT,
669
+ created_at TEXT DEFAULT (datetime('now')),
670
+ updated_at TEXT DEFAULT (datetime('now'))
671
+ )
672
+ `
673
+ );
674
+ db.run(
675
+ /*sql*/
676
+ `
677
+ CREATE TABLE IF NOT EXISTS deployment_logs (
678
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
679
+ deployment_id INTEGER NOT NULL,
680
+ event TEXT NOT NULL DEFAULT 'log',
681
+ message TEXT NOT NULL,
682
+ created_at TEXT DEFAULT (datetime('now')),
683
+ FOREIGN KEY (deployment_id) REFERENCES deployments(id) ON DELETE CASCADE
684
+ )
685
+ `
686
+ );
687
+ db.run(
688
+ /*sql*/
689
+ `
690
+ CREATE TABLE IF NOT EXISTS deployment_backups (
691
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
692
+ deployment_id INTEGER,
693
+ namespace TEXT NOT NULL,
694
+ agent_id TEXT NOT NULL,
695
+ sandbox_name TEXT,
696
+ pvc_name TEXT NOT NULL,
697
+ driver TEXT NOT NULL,
698
+ snapshot_name TEXT,
699
+ object_key TEXT,
700
+ status TEXT NOT NULL DEFAULT 'pending',
701
+ error TEXT,
702
+ expires_at TEXT,
703
+ created_at TEXT DEFAULT (datetime('now')),
704
+ updated_at TEXT DEFAULT (datetime('now')),
705
+ FOREIGN KEY (deployment_id) REFERENCES deployments(id) ON DELETE CASCADE
706
+ )
707
+ `
708
+ );
709
+ db.run(
710
+ /*sql*/
711
+ `
712
+ CREATE TABLE IF NOT EXISTS activities (
713
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
714
+ type TEXT NOT NULL,
715
+ title TEXT NOT NULL,
716
+ detail TEXT,
717
+ namespace TEXT,
718
+ template TEXT,
719
+ created_at TEXT DEFAULT (datetime('now'))
720
+ )
721
+ `
722
+ );
723
+ db.run(
724
+ /*sql*/
725
+ `
726
+ CREATE TABLE IF NOT EXISTS env_vars (
727
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
728
+ scope TEXT NOT NULL DEFAULT 'global',
729
+ key TEXT NOT NULL,
730
+ encrypted_value TEXT NOT NULL,
731
+ iv TEXT NOT NULL,
732
+ is_secret INTEGER DEFAULT 1,
733
+ group_name TEXT NOT NULL DEFAULT 'default',
734
+ created_at TEXT DEFAULT (datetime('now')),
735
+ updated_at TEXT DEFAULT (datetime('now'))
736
+ )
737
+ `
738
+ );
739
+ db.run(
740
+ /*sql*/
741
+ `
742
+ CREATE TABLE IF NOT EXISTS env_groups (
743
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
744
+ name TEXT NOT NULL UNIQUE,
745
+ created_at TEXT DEFAULT (datetime('now')),
746
+ updated_at TEXT DEFAULT (datetime('now'))
747
+ )
748
+ `
749
+ );
750
+ try {
751
+ db.run(
752
+ /*sql*/
753
+ `ALTER TABLE secrets ADD COLUMN group_name TEXT NOT NULL DEFAULT 'default'`
754
+ );
755
+ } catch {
756
+ }
757
+ try {
758
+ db.run(
759
+ /*sql*/
760
+ `ALTER TABLE env_vars ADD COLUMN group_name TEXT NOT NULL DEFAULT 'default'`
761
+ );
762
+ } catch {
763
+ }
764
+ db.run(
765
+ /*sql*/
766
+ `CREATE INDEX IF NOT EXISTS idx_deployment_logs_deployment_id ON deployment_logs(deployment_id, id)`
767
+ );
768
+ db.run(
769
+ /*sql*/
770
+ `CREATE INDEX IF NOT EXISTS idx_deployment_backups_agent ON deployment_backups(namespace, agent_id, id)`
771
+ );
772
+ db.run(
773
+ /*sql*/
774
+ `INSERT OR IGNORE INTO env_groups (name) VALUES ('default')`
775
+ );
776
+ try {
777
+ db.run(
778
+ /*sql*/
779
+ `ALTER TABLE configs ADD COLUMN version INTEGER NOT NULL DEFAULT 1`
780
+ );
781
+ } catch {
782
+ }
783
+ try {
784
+ db.run(
785
+ /*sql*/
786
+ `ALTER TABLE deployments ADD COLUMN version INTEGER`
787
+ );
788
+ } catch {
789
+ }
790
+ db.run(
791
+ /*sql*/
792
+ `
793
+ CREATE TABLE IF NOT EXISTS config_versions (
794
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
795
+ config_name TEXT NOT NULL,
796
+ version INTEGER NOT NULL,
797
+ content TEXT NOT NULL,
798
+ message TEXT,
799
+ created_at TEXT DEFAULT (datetime('now'))
800
+ )
801
+ `
802
+ );
803
+ }
804
+
805
+ // src/interfaces/http/app.ts
806
+ import { existsSync as existsSync6, readFileSync as readFileSync5, statSync } from "fs";
807
+ import { extname, join as join5, resolve as resolve5 } from "path";
808
+ import { fileURLToPath } from "url";
809
+ import { Hono as Hono12 } from "hono";
810
+ import { cors } from "hono/cors";
811
+
812
+ // src/interfaces/http/handlers/activity.handler.ts
813
+ import { Hono } from "hono";
814
+ function createActivityHandler(ctx) {
815
+ const app = new Hono();
816
+ app.get("/activity", (c) => {
817
+ const activities2 = ctx.activityDao.findAll();
818
+ return c.json({ activities: activities2 });
819
+ });
820
+ app.post("/activity", async (c) => {
821
+ const entry = await c.req.json();
822
+ ctx.activityDao.create({
823
+ type: entry.type ?? "unknown",
824
+ title: entry.title ?? "",
825
+ detail: entry.detail ?? void 0,
826
+ namespace: entry.namespace ?? void 0,
827
+ template: entry.template ?? void 0
828
+ });
829
+ return c.json({ success: true });
830
+ });
831
+ return app;
832
+ }
833
+
834
+ // src/interfaces/http/handlers/cluster.handler.ts
835
+ import { createHash } from "crypto";
836
+ import { Hono as Hono2 } from "hono";
837
+ import { streamSSE } from "hono/streaming";
838
+
839
+ // src/utils/deployment-scope.ts
840
+ var GLOBAL_ENV_SCOPE = "global";
841
+ var DEPLOYMENT_ENV_SCOPE_PREFIX = "deployment:";
842
+ function normalizeNamespace(value) {
843
+ const normalized = value?.trim();
844
+ return normalized ? normalized : "shadowob-cloud";
845
+ }
846
+ function toDeploymentEnvScope(namespace) {
847
+ return `${DEPLOYMENT_ENV_SCOPE_PREFIX}${normalizeNamespace(namespace)}`;
848
+ }
849
+
850
+ // src/utils/redact.ts
851
+ var REDACTED = "[REDACTED]";
852
+ var KEY_PATTERNS = [
853
+ // Anthropic: sk-ant-api03-...
854
+ /\bsk-ant-[A-Za-z0-9_-]{20,}\b/g,
855
+ // OpenAI project key: sk-proj-...
856
+ /\bsk-proj-[A-Za-z0-9_-]{20,}\b/g,
857
+ // OpenAI legacy: sk-...
858
+ /\bsk-[A-Za-z0-9]{20,}\b/g,
859
+ // Groq: gsk_...
860
+ /\bgsk_[A-Za-z0-9]{20,}\b/g,
861
+ // xAI: xai-...
862
+ /\bxai-[A-Za-z0-9]{20,}\b/g,
863
+ // DeepSeek: sk-... (covered above)
864
+ // Generic key patterns
865
+ /\bkey-[A-Za-z0-9]{20,}\b/g,
866
+ // GitHub PAT
867
+ /\bghp_[A-Za-z0-9]{20,}\b/g,
868
+ // GitHub fine-grained PAT
869
+ /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g,
870
+ // Bearer token in values
871
+ /Bearer\s+[A-Za-z0-9._-]{20,}/g
872
+ ];
873
+ function redactSecrets(input) {
874
+ let result = input;
875
+ for (const pattern of KEY_PATTERNS) {
876
+ pattern.lastIndex = 0;
877
+ result = result.replace(pattern, REDACTED);
878
+ }
879
+ return result;
880
+ }
881
+ var INLINE_KEY_PREFIXES = [
882
+ "sk-ant-",
883
+ "sk-proj-",
884
+ "sk-",
885
+ "gsk_",
886
+ "xai-",
887
+ "key-",
888
+ "ghp_",
889
+ "github_pat_"
890
+ ];
891
+ function detectInlineKey(value) {
892
+ if (/^\$\{(env|secret|file):/.test(value)) return null;
893
+ for (const prefix of INLINE_KEY_PREFIXES) {
894
+ if (value.startsWith(prefix) && value.length > prefix.length + 10) {
895
+ return prefix;
896
+ }
897
+ }
898
+ return null;
899
+ }
900
+
901
+ // src/interfaces/http/handlers/cluster.handler.ts
902
+ function isRecord(value) {
903
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
904
+ }
905
+ function stableJsonStringify(value) {
906
+ if (Array.isArray(value)) {
907
+ return `[${value.map((item) => stableJsonStringify(item)).join(",")}]`;
908
+ }
909
+ if (isRecord(value)) {
910
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableJsonStringify(value[key])}`).join(",")}}`;
911
+ }
912
+ return JSON.stringify(value);
913
+ }
914
+ function shortHash(value) {
915
+ return createHash("sha256").update(stableJsonStringify(value)).digest("hex").slice(0, 16);
916
+ }
917
+ function readNonEmptyString(record, key) {
918
+ const value = record?.[key];
919
+ return typeof value === "string" && value.trim() ? value.trim() : null;
920
+ }
921
+ function inferTemplateSlugFromConfig(config) {
922
+ if (!isRecord(config)) return null;
923
+ const metadata = isRecord(config.metadata) ? config.metadata : null;
924
+ return readNonEmptyString(config, "templateSlug") ?? readNonEmptyString(config, "template") ?? readNonEmptyString(metadata, "templateSlug") ?? readNonEmptyString(metadata, "template") ?? readNonEmptyString(metadata, "sourceTemplateSlug") ?? readNonEmptyString(config, "name");
925
+ }
926
+ function uniqueNonEmptyStrings(values) {
927
+ const result = [];
928
+ const seen = /* @__PURE__ */ new Set();
929
+ for (const value of values) {
930
+ const normalized = value?.trim();
931
+ if (!normalized || seen.has(normalized)) continue;
932
+ seen.add(normalized);
933
+ result.push(normalized);
934
+ }
935
+ return result;
936
+ }
937
+ function localTemplateSlugCandidates(options) {
938
+ return uniqueNonEmptyStrings([
939
+ options.templateSlug,
940
+ inferTemplateSlugFromConfig(options.config),
941
+ options.namespace
942
+ ]);
943
+ }
944
+ async function resolveLocalTemplateView(ctx, slug) {
945
+ if (!slug) return null;
946
+ const localConfig = ctx.configDao.findByName(`tpl:${slug}`);
947
+ if (localConfig) {
948
+ return {
949
+ id: slug,
950
+ slug,
951
+ name: slug,
952
+ description: null,
953
+ source: localConfig.templateSlug?.startsWith("git:") ? "git" : "local",
954
+ reviewStatus: "draft",
955
+ updatedAt: localConfig.updatedAt ?? localConfig.createdAt ?? null,
956
+ ownedByUser: true,
957
+ editable: true,
958
+ contentHash: shortHash(localConfig.content)
959
+ };
960
+ }
961
+ const catalogContent = await ctx.container.template.getTemplate(slug).catch(() => null);
962
+ if (!catalogContent) return null;
963
+ const meta = (await ctx.container.template.discover().catch(() => [])).find(
964
+ (item) => item.name === slug
965
+ );
966
+ return {
967
+ id: slug,
968
+ slug,
969
+ name: meta?.title ?? slug,
970
+ description: meta?.description ?? null,
971
+ source: "catalog",
972
+ reviewStatus: "approved",
973
+ updatedAt: null,
974
+ ownedByUser: false,
975
+ editable: false,
976
+ contentHash: shortHash(catalogContent)
977
+ };
978
+ }
979
+ function parseTimestamp(value) {
980
+ const parsed = Date.parse(value);
981
+ return Number.isNaN(parsed) ? 0 : parsed;
982
+ }
983
+ function clamp(value, min, max) {
984
+ return Math.min(max, Math.max(min, value));
985
+ }
986
+ function createClusterHandler(ctx) {
987
+ const app = new Hono2();
988
+ function resolveNamespaces() {
989
+ let discovered = [];
990
+ try {
991
+ discovered = ctx.container.k8s.getManagedNamespaces();
992
+ } catch {
993
+ }
994
+ const dbNamespaces = ctx.deploymentDao.findAll().map((deployment) => deployment.namespace).filter(Boolean);
995
+ const all = /* @__PURE__ */ new Set([...ctx.namespaces, ...discovered, ...dbNamespaces]);
996
+ const namespaces = [...all].sort();
997
+ const discoveredNamespaces = discovered.filter(
998
+ (namespace) => !ctx.namespaces.includes(namespace)
999
+ );
1000
+ return { namespaces, discoveredNamespaces };
1001
+ }
1002
+ function resolvePods(namespace, agentId) {
1003
+ const allPods = ctx.container.k8s.getPods(namespace);
1004
+ return allPods.filter((pod) => agentId ? pod.name.includes(agentId) : true).sort((left, right) => {
1005
+ if (left.status === "Running" && right.status !== "Running") return -1;
1006
+ if (right.status === "Running" && left.status !== "Running") return 1;
1007
+ return parseTimestamp(right.age) - parseTimestamp(left.age);
1008
+ });
1009
+ }
1010
+ function resolvePrimaryPod(namespace, agentId) {
1011
+ return resolvePods(namespace, agentId)[0] ?? null;
1012
+ }
1013
+ function resolveDeploymentState(namespace, agentId) {
1014
+ return ctx.container.k8s.getDeployments(namespace).find((deployment) => deployment.name === agentId || deployment.sandboxName === agentId);
1015
+ }
1016
+ function newestDeploymentId(namespace) {
1017
+ return ctx.deploymentDao.findByNamespace(namespace)[0]?.id;
1018
+ }
1019
+ app.get("/namespaces", (c) => {
1020
+ const { namespaces, discoveredNamespaces } = resolveNamespaces();
1021
+ return c.json({
1022
+ configured: ctx.namespaces,
1023
+ discovered: discoveredNamespaces,
1024
+ all: namespaces
1025
+ });
1026
+ });
1027
+ app.get("/deployments", (c) => {
1028
+ const { namespaces } = resolveNamespaces();
1029
+ const result = [];
1030
+ for (const namespace of namespaces) {
1031
+ try {
1032
+ const deployments2 = ctx.container.k8s.getDeployments(namespace);
1033
+ for (const deployment of deployments2) {
1034
+ result.push({ ...deployment, namespace });
1035
+ }
1036
+ } catch {
1037
+ }
1038
+ }
1039
+ return c.json(result);
1040
+ });
1041
+ app.get("/deployments/costs", (c) => {
1042
+ const { namespaces } = resolveNamespaces();
1043
+ return c.json(ctx.container.usageCost.collectOverview(namespaces));
1044
+ });
1045
+ app.get("/deployments/:ns/costs", (c) => {
1046
+ const namespace = c.req.param("ns");
1047
+ return c.json(ctx.container.usageCost.collectNamespace(namespace));
1048
+ });
1049
+ app.get("/deployments/:ns/manifest", async (c) => {
1050
+ const namespace = c.req.param("ns");
1051
+ const task = ctx.deploymentDao.findByNamespace(namespace)[0];
1052
+ if (!task) {
1053
+ return c.json({
1054
+ deploymentId: null,
1055
+ namespace,
1056
+ name: namespace,
1057
+ templateSlug: null,
1058
+ template: null,
1059
+ manifest: null,
1060
+ drift: {
1061
+ status: "unlinked",
1062
+ templateAvailable: false,
1063
+ templateChanged: false,
1064
+ deployedTemplateHash: null,
1065
+ currentTemplateHash: null,
1066
+ configHash: null
1067
+ },
1068
+ configSnapshot: null
1069
+ });
1070
+ }
1071
+ const redactedConfig = task.config ? JSON.parse(redactSecrets(JSON.stringify(task.config))) : null;
1072
+ let linkedTemplateSlug = localTemplateSlugCandidates({
1073
+ namespace,
1074
+ templateSlug: task.templateSlug,
1075
+ config: task.config
1076
+ })[0] ?? null;
1077
+ let template = null;
1078
+ for (const candidate of localTemplateSlugCandidates({
1079
+ namespace,
1080
+ templateSlug: task.templateSlug,
1081
+ config: task.config
1082
+ })) {
1083
+ const found = await resolveLocalTemplateView(ctx, candidate);
1084
+ if (found) {
1085
+ linkedTemplateSlug = candidate;
1086
+ template = found;
1087
+ break;
1088
+ }
1089
+ }
1090
+ const configHash = task.config ? shortHash(task.config) : null;
1091
+ const templateContentHash = template?.contentHash ?? null;
1092
+ return c.json({
1093
+ deploymentId: task.id,
1094
+ namespace,
1095
+ name: namespace,
1096
+ templateSlug: linkedTemplateSlug,
1097
+ template,
1098
+ manifest: {
1099
+ schemaVersion: 1,
1100
+ revision: task.version ?? 1,
1101
+ manifestId: `local-${task.id}`,
1102
+ source: "snapshot-redeploy",
1103
+ generatedAt: task.updatedAt ?? task.createdAt ?? null,
1104
+ configHash,
1105
+ manifestHash: configHash,
1106
+ templateSlug: linkedTemplateSlug,
1107
+ templateId: linkedTemplateSlug,
1108
+ templateName: template?.name ?? linkedTemplateSlug,
1109
+ templateSource: template?.source ?? null,
1110
+ templateReviewStatus: template?.reviewStatus ?? null,
1111
+ templateUpdatedAt: task.updatedAt ?? task.createdAt ?? null,
1112
+ templateContentHash
1113
+ },
1114
+ drift: {
1115
+ status: linkedTemplateSlug ? template ? "unknown" : "missing-template" : "unlinked",
1116
+ templateAvailable: Boolean(template),
1117
+ templateChanged: false,
1118
+ deployedTemplateHash: templateContentHash,
1119
+ currentTemplateHash: templateContentHash,
1120
+ configHash
1121
+ },
1122
+ configSnapshot: redactedConfig
1123
+ });
1124
+ });
1125
+ app.post("/deployments/:ns/template", async (c) => {
1126
+ const namespace = c.req.param("ns");
1127
+ const task = ctx.deploymentDao.findByNamespace(namespace)[0];
1128
+ if (!task?.config) return c.json({ error: "No config stored for this namespace" }, 404);
1129
+ const body = await c.req.json().catch(() => ({}));
1130
+ const linkedTemplateSlug = localTemplateSlugCandidates({
1131
+ namespace,
1132
+ templateSlug: task.templateSlug,
1133
+ config: task.config
1134
+ })[0] ?? null;
1135
+ const name = body.name?.trim() || linkedTemplateSlug || namespace;
1136
+ ctx.configDao.upsert(
1137
+ `tpl:${name}`,
1138
+ body.content ?? task.config,
1139
+ linkedTemplateSlug ?? void 0
1140
+ );
1141
+ if (task.templateSlug !== name) {
1142
+ ctx.deploymentDao.update(task.id, { templateSlug: name });
1143
+ }
1144
+ return c.json({
1145
+ ok: true,
1146
+ action: linkedTemplateSlug ? "updated" : "forked",
1147
+ template: {
1148
+ id: name,
1149
+ slug: name,
1150
+ name,
1151
+ source: "local",
1152
+ reviewStatus: "draft",
1153
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1154
+ },
1155
+ manifest: {
1156
+ deploymentId: task.id,
1157
+ namespace,
1158
+ templateSlug: name
1159
+ }
1160
+ });
1161
+ });
1162
+ app.get("/deployments/:ns/env", (c) => {
1163
+ const namespace = c.req.param("ns");
1164
+ const scope = toDeploymentEnvScope(namespace);
1165
+ const mode = c.req.query("mode") === "scoped" ? "scoped" : "effective";
1166
+ const envVars2 = mode === "scoped" ? ctx.envVarDao.findMaskedByScope(scope) : ctx.envVarDao.findAllMaskedByScopes([GLOBAL_ENV_SCOPE, scope]);
1167
+ return c.json({ namespace, scope, mode, envVars: envVars2 });
1168
+ });
1169
+ app.get("/deployments/:ns/env/:key", (c) => {
1170
+ const namespace = c.req.param("ns");
1171
+ const key = c.req.param("key");
1172
+ const scope = toDeploymentEnvScope(namespace);
1173
+ const envVar = ctx.envVarDao.findOne(scope, key);
1174
+ if (!envVar) return c.json({ error: "Environment value not found" }, 404);
1175
+ return c.json({ envVar });
1176
+ });
1177
+ app.put("/deployments/:ns/env", async (c) => {
1178
+ const namespace = c.req.param("ns");
1179
+ const scope = toDeploymentEnvScope(namespace);
1180
+ try {
1181
+ const body = await c.req.json();
1182
+ if (!body.key || body.value === void 0) {
1183
+ return c.json({ error: "key and value are required" }, 400);
1184
+ }
1185
+ const groupName = normalizeGroupName(body.groupName);
1186
+ ctx.envGroupDao.ensure(groupName);
1187
+ ctx.envVarDao.upsert(scope, body.key, body.value, body.isSecret ?? true, groupName);
1188
+ return c.json({ ok: true, namespace, scope });
1189
+ } catch (err) {
1190
+ return c.json({ error: err.message }, 400);
1191
+ }
1192
+ });
1193
+ app.delete("/deployments/:ns/env/:key", (c) => {
1194
+ const namespace = c.req.param("ns");
1195
+ const key = c.req.param("key");
1196
+ ctx.envVarDao.delete(toDeploymentEnvScope(namespace), key);
1197
+ return c.json({ ok: true });
1198
+ });
1199
+ app.get("/deployments/:ns/logs", (c) => {
1200
+ const namespace = c.req.param("ns");
1201
+ const agent = c.req.query("agent");
1202
+ const page = clamp(Number.parseInt(c.req.query("page") ?? "1", 10) || 1, 1, 100);
1203
+ const limit = clamp(Number.parseInt(c.req.query("limit") ?? "200", 10) || 200, 20, 500);
1204
+ const pod = resolvePrimaryPod(namespace, agent);
1205
+ if (!pod) {
1206
+ const state = agent ? resolveDeploymentState(namespace, agent) : void 0;
1207
+ if (state?.runtimeState === "paused") {
1208
+ return c.json({
1209
+ namespace,
1210
+ agent: agent ?? state.name,
1211
+ podName: null,
1212
+ runtimeState: "paused",
1213
+ lines: [],
1214
+ page,
1215
+ limit,
1216
+ hasMore: false
1217
+ });
1218
+ }
1219
+ return c.json({ error: "No pod found for this agent" }, 404);
1220
+ }
1221
+ try {
1222
+ const requestedTail = page * limit;
1223
+ const allLines = ctx.container.k8s.readLogs(namespace, pod.name, { tail: requestedTail, timestamps: true }).split("\n").map((line) => line.trimEnd()).filter(Boolean).map((line) => redactSecrets(line));
1224
+ const start = Math.max(allLines.length - requestedTail, 0);
1225
+ const end = Math.max(allLines.length - (page - 1) * limit, 0);
1226
+ const lines = allLines.slice(start, end);
1227
+ return c.json({
1228
+ namespace,
1229
+ agent: agent ?? pod.name,
1230
+ podName: pod.name,
1231
+ page,
1232
+ limit,
1233
+ lines,
1234
+ hasMore: allLines.length >= requestedTail
1235
+ });
1236
+ } catch (err) {
1237
+ return c.json({ error: err.message }, 500);
1238
+ }
1239
+ });
1240
+ app.get("/deployments/:ns/:id/pods", (c) => {
1241
+ const namespace = c.req.param("ns");
1242
+ const agentId = c.req.param("id");
1243
+ try {
1244
+ return c.json(resolvePods(namespace, agentId));
1245
+ } catch (err) {
1246
+ return c.json({ error: String(err) }, 500);
1247
+ }
1248
+ });
1249
+ app.get("/deployments/:ns/:id/logs", (c) => {
1250
+ const namespace = c.req.param("ns");
1251
+ const agentId = c.req.param("id");
1252
+ const pod = resolvePrimaryPod(namespace, agentId);
1253
+ if (!pod) {
1254
+ const state = resolveDeploymentState(namespace, agentId);
1255
+ if (state?.runtimeState === "paused") {
1256
+ return c.json({ error: "Workload is paused", runtimeState: "paused" }, 409);
1257
+ }
1258
+ return c.json({ error: "No pod found for this agent" }, 404);
1259
+ }
1260
+ return streamSSE(c, async (stream) => {
1261
+ const child = ctx.container.k8s.streamLogs(namespace, pod.name, { follow: true, tail: 200 });
1262
+ stream.onAbort(() => {
1263
+ child.kill();
1264
+ });
1265
+ let buffer = "";
1266
+ child.stdout?.on("data", (chunk) => {
1267
+ buffer += chunk.toString();
1268
+ const lines = buffer.split("\n");
1269
+ buffer = lines.pop() ?? "";
1270
+ for (const line of lines) {
1271
+ void stream.writeSSE({ data: JSON.stringify(redactSecrets(line)) });
1272
+ }
1273
+ });
1274
+ child.stderr?.on("data", (chunk) => {
1275
+ for (const line of chunk.toString().split("\n").filter(Boolean)) {
1276
+ void stream.writeSSE({ data: JSON.stringify(redactSecrets(`[stderr] ${line}`)) });
1277
+ }
1278
+ });
1279
+ await new Promise((resolve7) => {
1280
+ child.on("close", () => {
1281
+ void stream.writeSSE({ event: "close", data: "{}" });
1282
+ resolve7();
1283
+ });
1284
+ });
1285
+ });
1286
+ });
1287
+ app.post("/deployments/:ns/:id/scale", async (c) => {
1288
+ const namespace = c.req.param("ns");
1289
+ const name = c.req.param("id");
1290
+ try {
1291
+ const body = await c.req.json();
1292
+ if (typeof body.replicas !== "number" || body.replicas < 0) {
1293
+ return c.json({ error: "Invalid replicas count" }, 400);
1294
+ }
1295
+ ctx.container.k8s.scaleDeployment(namespace, name, body.replicas);
1296
+ return c.json({ ok: true, name, namespace, replicas: body.replicas });
1297
+ } catch (err) {
1298
+ return c.json({ error: err.message }, 500);
1299
+ }
1300
+ });
1301
+ app.post("/deployments/:ns/:id/pause", (c) => {
1302
+ const namespace = c.req.param("ns");
1303
+ const name = c.req.param("id");
1304
+ try {
1305
+ ctx.container.k8s.pauseAgentSandbox(namespace, name);
1306
+ return c.json({ ok: true, name, namespace, runtimeState: "paused" });
1307
+ } catch (err) {
1308
+ return c.json({ error: err.message }, 500);
1309
+ }
1310
+ });
1311
+ app.post("/deployments/:ns/:id/resume", (c) => {
1312
+ const namespace = c.req.param("ns");
1313
+ const name = c.req.param("id");
1314
+ try {
1315
+ ctx.container.k8s.resumeAgentSandbox(namespace, name);
1316
+ return c.json({ ok: true, name, namespace, runtimeState: "resuming" });
1317
+ } catch (err) {
1318
+ return c.json({ error: err.message }, 500);
1319
+ }
1320
+ });
1321
+ app.get("/deployments/:ns/:id/backups", (c) => {
1322
+ const namespace = c.req.param("ns");
1323
+ const name = c.req.param("id");
1324
+ return c.json({
1325
+ namespace,
1326
+ agent: name,
1327
+ backups: ctx.deploymentBackupDao.findByAgent(namespace, name)
1328
+ });
1329
+ });
1330
+ app.post("/deployments/:ns/:id/backups", async (c) => {
1331
+ const namespace = c.req.param("ns");
1332
+ const name = c.req.param("id");
1333
+ const state = resolveDeploymentState(namespace, name);
1334
+ const body = await c.req.json().catch(() => ({}));
1335
+ const driver = body.driver ?? "volumeSnapshot";
1336
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1337
+ const backup = ctx.deploymentBackupDao.create({
1338
+ deploymentId: newestDeploymentId(namespace),
1339
+ namespace,
1340
+ agentId: name,
1341
+ sandboxName: state?.sandboxName ?? name,
1342
+ pvcName: state?.statePvc ?? runtimeStatePvcName(state?.sandboxName ?? name),
1343
+ driver,
1344
+ snapshotName: driver === "volumeSnapshot" ? `${name}-${stamp}` : void 0,
1345
+ objectKey: driver === "restic" ? `${namespace}/${name}/${stamp}` : void 0,
1346
+ status: "pending",
1347
+ expiresAt: body.retentionDays ? new Date(Date.now() + body.retentionDays * 24 * 60 * 60 * 1e3).toISOString() : void 0
1348
+ });
1349
+ if (driver !== "volumeSnapshot") {
1350
+ const failed = ctx.deploymentBackupDao.update(backup.id, {
1351
+ status: "failed",
1352
+ error: "restic backups require object storage repository configuration"
1353
+ });
1354
+ return c.json({ error: failed?.error, backup: failed ?? backup }, 501);
1355
+ }
1356
+ try {
1357
+ await ctx.container.k8s.createVolumeSnapshotBackupAndWait({
1358
+ namespace,
1359
+ snapshotName: backup.snapshotName ?? `${name}-${stamp}`,
1360
+ pvcName: backup.pvcName,
1361
+ timeoutMs: 18e4
1362
+ });
1363
+ const updated = ctx.deploymentBackupDao.update(backup.id, { status: "succeeded" });
1364
+ return c.json({ ok: true, backup: updated ?? backup }, 201);
1365
+ } catch (err) {
1366
+ const failed = ctx.deploymentBackupDao.update(backup.id, {
1367
+ status: "failed",
1368
+ error: err.message
1369
+ });
1370
+ return c.json({ error: err.message, backup: failed ?? backup }, 500);
1371
+ }
1372
+ });
1373
+ app.post("/deployments/:ns/:id/restore", async (c) => {
1374
+ const namespace = c.req.param("ns");
1375
+ const name = c.req.param("id");
1376
+ const body = await c.req.json().catch(() => ({}));
1377
+ const backups = ctx.deploymentBackupDao.findByAgent(namespace, name);
1378
+ const backup = body.backupId ? backups.find((item) => item.id === body.backupId) : backups.find((item) => item.status === "succeeded");
1379
+ if (!backup) {
1380
+ return c.json({ error: "No backup found for this agent" }, 404);
1381
+ }
1382
+ if (backup.status !== "succeeded") {
1383
+ return c.json({ error: `Cannot restore backup in status "${backup.status}"` }, 422);
1384
+ }
1385
+ if (backup.driver !== "volumeSnapshot" || !backup.snapshotName) {
1386
+ return c.json({ error: "Only VolumeSnapshot backups can be restored locally today" }, 422);
1387
+ }
1388
+ try {
1389
+ ctx.container.k8s.pauseAgentSandbox(namespace, name);
1390
+ await ctx.container.k8s.waitForAgentSandboxPaused({
1391
+ namespace,
1392
+ agentName: name,
1393
+ timeoutMs: 12e4
1394
+ });
1395
+ await ctx.container.k8s.restorePvcFromVolumeSnapshot({
1396
+ namespace,
1397
+ pvcName: backup.pvcName,
1398
+ snapshotName: backup.snapshotName,
1399
+ timeoutMs: 18e4
1400
+ });
1401
+ ctx.container.k8s.resumeAgentSandbox(namespace, name);
1402
+ await ctx.container.k8s.waitForAgentSandboxReady({
1403
+ namespace,
1404
+ agentName: name,
1405
+ timeoutMs: 18e4
1406
+ });
1407
+ return c.json({ ok: true, backup, runtimeState: "running" }, 202);
1408
+ } catch (err) {
1409
+ return c.json({ error: err.message }, 500);
1410
+ }
1411
+ });
1412
+ return app;
1413
+ }
1414
+
1415
+ // src/interfaces/http/handlers/community.handler.ts
1416
+ import { existsSync, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
1417
+ import { homedir as homedir2 } from "os";
1418
+ import { join as join2 } from "path";
1419
+ import { Hono as Hono3 } from "hono";
1420
+ var DEFAULT_COMMUNITY_BASE_URL = "https://shadowob.com";
1421
+ function settingsPath() {
1422
+ return join2(homedir2(), ".shadowob", "settings.json");
1423
+ }
1424
+ function readSettings() {
1425
+ const p = settingsPath();
1426
+ if (!existsSync(p)) return {};
1427
+ try {
1428
+ return JSON.parse(readFileSync(p, "utf-8"));
1429
+ } catch {
1430
+ return {};
1431
+ }
1432
+ }
1433
+ function writeSettings(data) {
1434
+ const p = settingsPath();
1435
+ mkdirSync2(join2(homedir2(), ".shadowob"), { recursive: true });
1436
+ writeFileSync(p, JSON.stringify(data, null, 2), "utf-8");
1437
+ }
1438
+ function normalizeCatalogTemplate(template) {
1439
+ if (!template || typeof template !== "object" || Array.isArray(template)) return template;
1440
+ const { teamName: legacyTitle, ...rest } = template;
1441
+ const title = typeof rest.title === "string" ? rest.title : typeof legacyTitle === "string" ? legacyTitle : typeof rest.name === "string" ? rest.name : "";
1442
+ return { ...rest, title };
1443
+ }
1444
+ function getCommunitySettings() {
1445
+ const settings = readSettings();
1446
+ const raw = settings.community;
1447
+ return {
1448
+ baseUrl: raw?.baseUrl ?? DEFAULT_COMMUNITY_BASE_URL,
1449
+ token: raw?.token,
1450
+ oauthConnected: raw?.oauthConnected ?? false
1451
+ };
1452
+ }
1453
+ function saveCommunitySettings(community) {
1454
+ const settings = readSettings();
1455
+ writeSettings({ ...settings, community });
1456
+ }
1457
+ function createCommunityHandler(ctx) {
1458
+ const app = new Hono3();
1459
+ app.get("/community/settings", (c) => {
1460
+ const cs = getCommunitySettings();
1461
+ return c.json({
1462
+ baseUrl: cs.baseUrl,
1463
+ oauthConnected: cs.oauthConnected,
1464
+ hasToken: Boolean(cs.token)
1465
+ });
1466
+ });
1467
+ app.put("/community/settings", async (c) => {
1468
+ const body = await c.req.json();
1469
+ const current = getCommunitySettings();
1470
+ const updated = {
1471
+ baseUrl: typeof body.baseUrl === "string" && body.baseUrl.trim() ? body.baseUrl.trim().replace(/\/$/, "") : current.baseUrl,
1472
+ token: typeof body.token === "string" ? body.token.trim() || void 0 : current.token,
1473
+ oauthConnected: current.oauthConnected
1474
+ };
1475
+ if (typeof body.token === "string") {
1476
+ updated.oauthConnected = false;
1477
+ }
1478
+ saveCommunitySettings(updated);
1479
+ return c.json({ ok: true });
1480
+ });
1481
+ app.get("/community/templates/catalog", async (c) => {
1482
+ const cs = getCommunitySettings();
1483
+ const locale = c.req.query("locale") ?? "en";
1484
+ const headers = { Accept: "application/json" };
1485
+ if (cs.token) {
1486
+ headers["Authorization"] = `Bearer ${cs.token}`;
1487
+ }
1488
+ try {
1489
+ const res = await fetch(
1490
+ `${cs.baseUrl}/api/templates/catalog?locale=${encodeURIComponent(locale)}`,
1491
+ {
1492
+ headers,
1493
+ signal: AbortSignal.timeout(8e3)
1494
+ }
1495
+ );
1496
+ if (res.ok) {
1497
+ const data = await res.json();
1498
+ const templates = Array.isArray(data.templates) ? data.templates.map(normalizeCatalogTemplate) : data.templates;
1499
+ return c.json({ ...data, templates, source: "community" });
1500
+ }
1501
+ } catch {
1502
+ }
1503
+ const localCatalog = await ctx.container.templateI18n.listCatalog(locale);
1504
+ return c.json({ ...localCatalog, source: "local" });
1505
+ });
1506
+ app.post("/community/templates/publish", async (c) => {
1507
+ const body = await c.req.json();
1508
+ const { name } = body;
1509
+ if (!name) {
1510
+ return c.json({ error: "name is required" }, 400);
1511
+ }
1512
+ const cfg = ctx.configDao.findByName(`tpl:${name}`);
1513
+ if (!cfg) {
1514
+ return c.json({ error: `Template not found: ${name}` }, 404);
1515
+ }
1516
+ const cs = getCommunitySettings();
1517
+ if (!cs.token) {
1518
+ return c.json(
1519
+ { error: "Not connected to community. Please set a token or authorize via OAuth." },
1520
+ 401
1521
+ );
1522
+ }
1523
+ try {
1524
+ const res = await fetch(`${cs.baseUrl}/api/templates`, {
1525
+ method: "POST",
1526
+ headers: {
1527
+ "Content-Type": "application/json",
1528
+ Authorization: `Bearer ${cs.token}`
1529
+ },
1530
+ body: JSON.stringify({
1531
+ slug: name,
1532
+ name: body.description ? name : name,
1533
+ description: body.description ?? "",
1534
+ visibility: body.visibility ?? "public",
1535
+ content: cfg.content
1536
+ })
1537
+ });
1538
+ if (!res.ok) {
1539
+ const errText = await res.text();
1540
+ return c.json({ error: `Community server error: ${res.status} ${errText}` }, 502);
1541
+ }
1542
+ const result = await res.json();
1543
+ return c.json({ ok: true, result });
1544
+ } catch (err) {
1545
+ const message = err instanceof Error ? err.message : String(err);
1546
+ return c.json({ error: `Failed to reach community server: ${message}` }, 502);
1547
+ }
1548
+ });
1549
+ app.get("/community/oauth/init", (c) => {
1550
+ const cs = getCommunitySettings();
1551
+ const authUrl = new URL("/oauth/authorize", cs.baseUrl);
1552
+ authUrl.searchParams.set("client_id", "shadowob-cloud-cli");
1553
+ authUrl.searchParams.set("response_type", "token");
1554
+ authUrl.searchParams.set("scope", "templates:read templates:write");
1555
+ return c.json({ url: authUrl.toString() });
1556
+ });
1557
+ app.get("/community/oauth/callback", (c) => {
1558
+ const token = c.req.query("access_token");
1559
+ if (!token) {
1560
+ return c.html("<p>Missing access_token. Please try again.</p>", 400);
1561
+ }
1562
+ const cs = getCommunitySettings();
1563
+ saveCommunitySettings({ ...cs, token, oauthConnected: true });
1564
+ return c.html(`<!DOCTYPE html>
1565
+ <html>
1566
+ <head><title>Connected</title></head>
1567
+ <body>
1568
+ <p>Connected to community. You may close this window.</p>
1569
+ <script>
1570
+ if (window.opener) {
1571
+ window.opener.postMessage({ type: 'community-oauth-success' }, '*');
1572
+ window.close();
1573
+ }
1574
+ </script>
1575
+ </body>
1576
+ </html>`);
1577
+ });
1578
+ return app;
1579
+ }
1580
+
1581
+ // src/interfaces/http/handlers/config.handler.ts
1582
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1583
+ import { resolve as resolve3 } from "path";
1584
+ import { Hono as Hono4 } from "hono";
1585
+
1586
+ // src/application/config-schema.ts
1587
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
1588
+ import { resolve as resolve2 } from "path";
1589
+
1590
+ // src/utils/package-asset-path.ts
1591
+ import { existsSync as existsSync2 } from "fs";
1592
+ import { dirname, resolve } from "path";
1593
+ function uniqueCandidates(candidates) {
1594
+ const seen = /* @__PURE__ */ new Set();
1595
+ const resolved = [];
1596
+ for (const candidate of candidates) {
1597
+ if (!candidate) continue;
1598
+ const normalized = resolve(candidate);
1599
+ if (seen.has(normalized)) continue;
1600
+ seen.add(normalized);
1601
+ resolved.push(normalized);
1602
+ }
1603
+ return resolved;
1604
+ }
1605
+ function resolveCloudPackageAssetDir(assetName) {
1606
+ const entryFile = typeof process.argv[1] === "string" && process.argv[1].length > 0 ? resolve(process.argv[1]) : void 0;
1607
+ const entryDir = entryFile ? dirname(entryFile) : void 0;
1608
+ const candidates = uniqueCandidates([
1609
+ entryDir ? resolve(entryDir, "..", assetName) : void 0,
1610
+ entryDir ? resolve(entryDir, assetName) : void 0,
1611
+ resolve(process.cwd(), assetName),
1612
+ resolve(process.cwd(), "apps/cloud", assetName),
1613
+ resolve(process.cwd(), "node_modules", "@shadowob", "cloud", assetName)
1614
+ ]);
1615
+ for (const candidate of candidates) {
1616
+ if (existsSync2(candidate)) {
1617
+ return candidate;
1618
+ }
1619
+ }
1620
+ return candidates[0] ?? resolve(process.cwd(), "apps/cloud", assetName);
1621
+ }
1622
+
1623
+ // src/application/config-schema.ts
1624
+ function loadCloudConfigSchema() {
1625
+ const schemaPath = resolve2(resolveCloudPackageAssetDir("schemas"), "config.schema.json");
1626
+ if (!existsSync3(schemaPath)) {
1627
+ return {};
1628
+ }
1629
+ return JSON.parse(readFileSync2(schemaPath, "utf-8"));
1630
+ }
1631
+
1632
+ // src/interfaces/http/handlers/config.handler.ts
1633
+ function createConfigHandler(ctx) {
1634
+ const app = new Hono4();
1635
+ app.get("/config", (c) => {
1636
+ const pathParam = c.req.query("path");
1637
+ if (pathParam) {
1638
+ const absPath = resolve3(pathParam);
1639
+ if (!existsSync4(absPath)) {
1640
+ return c.json({ error: `Config file not found: ${absPath}` }, 404);
1641
+ }
1642
+ try {
1643
+ const raw = readFileSync3(absPath, "utf-8");
1644
+ return c.json({ path: absPath, content: raw });
1645
+ } catch (err) {
1646
+ return c.json({ error: err.message }, 500);
1647
+ }
1648
+ }
1649
+ const configRow = ctx.configDao.findByName("current");
1650
+ if (configRow) {
1651
+ return c.json({
1652
+ path: `db://configs/${configRow.id}`,
1653
+ content: JSON.stringify(configRow.content, null, 2)
1654
+ });
1655
+ }
1656
+ const defaultPath = resolve3("shadowob-cloud.json");
1657
+ if (existsSync4(defaultPath)) {
1658
+ const raw = readFileSync3(defaultPath, "utf-8");
1659
+ return c.json({ path: defaultPath, content: raw });
1660
+ }
1661
+ return c.json({ error: "No config found. Initialize from a template first." }, 404);
1662
+ });
1663
+ app.put("/config", async (c) => {
1664
+ try {
1665
+ const body = await c.req.json();
1666
+ const parsed = JSON.parse(body.content);
1667
+ if (body.path && !body.path.startsWith("db://")) {
1668
+ const filePath = resolve3(body.path);
1669
+ mkdirSync3(resolve3(filePath, ".."), { recursive: true });
1670
+ writeFileSync2(filePath, body.content, "utf-8");
1671
+ return c.json({ ok: true, path: filePath });
1672
+ }
1673
+ const templateSlug = parsed.templateSlug ?? parsed.metadata?.template ?? void 0;
1674
+ ctx.configDao.upsert("current", parsed, templateSlug);
1675
+ return c.json({ ok: true, path: "db://configs/current" });
1676
+ } catch (err) {
1677
+ return c.json({ error: err.message }, 400);
1678
+ }
1679
+ });
1680
+ app.get("/schema", (c) => {
1681
+ const schema = loadCloudConfigSchema();
1682
+ if (Object.keys(schema).length === 0) {
1683
+ return c.json({ error: "Schema file not found. Run pnpm generate:schema first." }, 404);
1684
+ }
1685
+ return c.json(schema);
1686
+ });
1687
+ return app;
1688
+ }
1689
+
1690
+ // src/interfaces/http/handlers/deploy.handler.ts
1691
+ import { writeFileSync as writeFileSync3 } from "fs";
1692
+ import { tmpdir } from "os";
1693
+ import { join as join3 } from "path";
1694
+ import { Hono as Hono5 } from "hono";
1695
+ import { streamSSE as streamSSE2 } from "hono/streaming";
1696
+
1697
+ // src/interfaces/http/deploy-log-format.ts
1698
+ function formatDeploymentLogTimestamp(value) {
1699
+ if (!value) {
1700
+ return null;
1701
+ }
1702
+ const date = new Date(value);
1703
+ if (Number.isNaN(date.getTime())) {
1704
+ return null;
1705
+ }
1706
+ return date.toLocaleString(void 0, {
1707
+ year: "numeric",
1708
+ month: "2-digit",
1709
+ day: "2-digit",
1710
+ hour: "2-digit",
1711
+ minute: "2-digit",
1712
+ second: "2-digit",
1713
+ hour12: false
1714
+ });
1715
+ }
1716
+ function formatDeploymentLogLine(message, createdAt) {
1717
+ const timestamp = formatDeploymentLogTimestamp(createdAt);
1718
+ return timestamp ? `[${timestamp}] ${message}` : message;
1719
+ }
1720
+
1721
+ // src/interfaces/http/handlers/deploy.handler.ts
1722
+ function cleanupTmpFile(path) {
1723
+ try {
1724
+ const { unlinkSync } = __require("fs");
1725
+ unlinkSync(path);
1726
+ } catch {
1727
+ }
1728
+ }
1729
+ function parseTaskId(raw) {
1730
+ const taskId = Number(raw);
1731
+ return Number.isInteger(taskId) && taskId > 0 ? taskId : null;
1732
+ }
1733
+ function buildTaskUrl(taskId) {
1734
+ return `/deploy-tasks/${taskId}`;
1735
+ }
1736
+ function buildDonePayload(task) {
1737
+ return {
1738
+ exitCode: task?.status === "deployed" ? 0 : 1,
1739
+ ...task?.error ? { error: task.error } : {},
1740
+ result: {
1741
+ namespace: task?.namespace,
1742
+ agentCount: task?.agentCount ?? 0
1743
+ }
1744
+ };
1745
+ }
1746
+ function createDeployHandler(ctx) {
1747
+ const app = new Hono5();
1748
+ app.post("/deploy", async (c) => {
1749
+ let config;
1750
+ try {
1751
+ config = await c.req.json();
1752
+ } catch {
1753
+ return c.json({ error: "Invalid JSON body" }, 400);
1754
+ }
1755
+ const task = await ctx.deployTaskManager.start(config);
1756
+ return streamSSE2(c, async (stream) => {
1757
+ let lastLogId = 0;
1758
+ const sendLog = async (message) => {
1759
+ await stream.writeSSE({ event: "log", data: JSON.stringify(message) });
1760
+ };
1761
+ const flushLogs = async () => {
1762
+ const logs = ctx.deploymentLogDao.findByDeploymentIdSince(task.id, lastLogId);
1763
+ for (const log of logs) {
1764
+ lastLogId = log.id;
1765
+ await sendLog(formatDeploymentLogLine(log.message, log.createdAt));
1766
+ }
1767
+ };
1768
+ await stream.writeSSE({
1769
+ event: "task",
1770
+ data: JSON.stringify({ id: task.id, url: buildTaskUrl(task.id) })
1771
+ });
1772
+ await flushLogs();
1773
+ const existingTask = ctx.deploymentDao.findById(task.id);
1774
+ if (!ctx.deployTaskManager.isActive(task.id)) {
1775
+ await stream.writeSSE({
1776
+ event: "done",
1777
+ data: JSON.stringify(buildDonePayload(existingTask))
1778
+ });
1779
+ return;
1780
+ }
1781
+ await new Promise((resolve7) => {
1782
+ let finished = false;
1783
+ const finish = async (payload) => {
1784
+ if (finished) return;
1785
+ finished = true;
1786
+ await flushLogs();
1787
+ await stream.writeSSE({
1788
+ event: "done",
1789
+ data: JSON.stringify(payload ?? buildDonePayload(ctx.deploymentDao.findById(task.id)))
1790
+ });
1791
+ resolve7();
1792
+ };
1793
+ const unsubscribe = ctx.deployTaskManager.subscribe(task.id, async (event) => {
1794
+ if (event.type === "log") {
1795
+ if (event.data.id <= lastLogId) return;
1796
+ lastLogId = event.data.id;
1797
+ await sendLog(formatDeploymentLogLine(event.data.message, event.data.createdAt));
1798
+ return;
1799
+ }
1800
+ unsubscribe();
1801
+ await finish(event.data);
1802
+ });
1803
+ void flushLogs().then(async () => {
1804
+ if (!ctx.deployTaskManager.isActive(task.id)) {
1805
+ unsubscribe();
1806
+ await finish();
1807
+ }
1808
+ });
1809
+ stream.onAbort(() => {
1810
+ unsubscribe();
1811
+ resolve7();
1812
+ });
1813
+ });
1814
+ });
1815
+ });
1816
+ app.get("/deploy-tasks", (c) => {
1817
+ const tasks = ctx.deploymentDao.findAll().map((task) => ({
1818
+ task,
1819
+ url: buildTaskUrl(task.id),
1820
+ active: ctx.deployTaskManager.isActive(task.id)
1821
+ }));
1822
+ return c.json({ tasks });
1823
+ });
1824
+ app.get("/deploy-tasks/:id", (c) => {
1825
+ const taskId = parseTaskId(c.req.param("id"));
1826
+ if (!taskId) {
1827
+ return c.json({ error: "Invalid task id" }, 400);
1828
+ }
1829
+ const task = ctx.deploymentDao.findById(taskId);
1830
+ if (!task) {
1831
+ return c.json({ error: "Deployment task not found" }, 404);
1832
+ }
1833
+ return c.json({
1834
+ task,
1835
+ url: buildTaskUrl(task.id),
1836
+ active: ctx.deployTaskManager.isActive(task.id)
1837
+ });
1838
+ });
1839
+ app.get("/deploy-tasks/:id/stream", (c) => {
1840
+ const taskId = parseTaskId(c.req.param("id"));
1841
+ if (!taskId) {
1842
+ return c.json({ error: "Invalid task id" }, 400);
1843
+ }
1844
+ const task = ctx.deploymentDao.findById(taskId);
1845
+ if (!task) {
1846
+ return c.json({ error: "Deployment task not found" }, 404);
1847
+ }
1848
+ return streamSSE2(c, async (stream) => {
1849
+ let lastLogId = 0;
1850
+ const writeLog = async (message) => {
1851
+ await stream.writeSSE({ data: JSON.stringify(message) });
1852
+ };
1853
+ const flushLogs = async () => {
1854
+ const logs = ctx.deploymentLogDao.findByDeploymentIdSince(taskId, lastLogId);
1855
+ for (const log of logs) {
1856
+ lastLogId = log.id;
1857
+ await writeLog(formatDeploymentLogLine(log.message, log.createdAt));
1858
+ }
1859
+ };
1860
+ await flushLogs();
1861
+ if (!ctx.deployTaskManager.isActive(taskId)) {
1862
+ await stream.writeSSE({ event: "close", data: "{}" });
1863
+ return;
1864
+ }
1865
+ await new Promise((resolve7) => {
1866
+ let closed = false;
1867
+ const close = async () => {
1868
+ if (closed) return;
1869
+ closed = true;
1870
+ await flushLogs();
1871
+ await stream.writeSSE({ event: "close", data: "{}" });
1872
+ resolve7();
1873
+ };
1874
+ const unsubscribe = ctx.deployTaskManager.subscribe(taskId, async (event) => {
1875
+ if (event.type === "log") {
1876
+ if (event.data.id <= lastLogId) return;
1877
+ lastLogId = event.data.id;
1878
+ await writeLog(formatDeploymentLogLine(event.data.message, event.data.createdAt));
1879
+ return;
1880
+ }
1881
+ unsubscribe();
1882
+ await close();
1883
+ });
1884
+ void flushLogs().then(async () => {
1885
+ if (!ctx.deployTaskManager.isActive(taskId)) {
1886
+ unsubscribe();
1887
+ await close();
1888
+ }
1889
+ });
1890
+ stream.onAbort(() => {
1891
+ unsubscribe();
1892
+ resolve7();
1893
+ });
1894
+ });
1895
+ });
1896
+ });
1897
+ app.post("/deploy-tasks/:id/cancel", async (c) => {
1898
+ const taskId = parseTaskId(c.req.param("id"));
1899
+ if (!taskId) return c.json({ error: "Invalid task id" }, 400);
1900
+ const result = await ctx.deployTaskManager.cancel(taskId);
1901
+ if (!result.ok) {
1902
+ return c.json({ ok: false, error: result.error ?? "Unable to cancel deployment task" }, 422);
1903
+ }
1904
+ return c.json({ ok: true, status: result.status });
1905
+ });
1906
+ app.post("/deploy-tasks/:id/redeploy", async (c) => {
1907
+ const taskId = parseTaskId(c.req.param("id"));
1908
+ if (!taskId) return c.json({ error: "Invalid task id" }, 400);
1909
+ const original = ctx.deploymentDao.findById(taskId);
1910
+ if (!original) return c.json({ error: "Deployment task not found" }, 404);
1911
+ if (!original.config) return c.json({ error: "No config stored for this task" }, 400);
1912
+ let envOverrides;
1913
+ try {
1914
+ const body = await c.req.json().catch(() => ({}));
1915
+ envOverrides = body.envVars;
1916
+ } catch {
1917
+ }
1918
+ const config = {
1919
+ ...original.config,
1920
+ ...envOverrides ? {
1921
+ envVars: {
1922
+ ...original.config.envVars,
1923
+ ...envOverrides
1924
+ }
1925
+ } : {}
1926
+ };
1927
+ const newTask = await ctx.deployTaskManager.start(config);
1928
+ return streamSSE2(c, async (stream) => {
1929
+ let lastLogId = 0;
1930
+ const sendLog = async (message) => {
1931
+ await stream.writeSSE({ event: "log", data: JSON.stringify(message) });
1932
+ };
1933
+ const flushLogs = async () => {
1934
+ const logs = ctx.deploymentLogDao.findByDeploymentIdSince(newTask.id, lastLogId);
1935
+ for (const log of logs) {
1936
+ lastLogId = log.id;
1937
+ await sendLog(formatDeploymentLogLine(log.message, log.createdAt));
1938
+ }
1939
+ };
1940
+ await stream.writeSSE({
1941
+ event: "task",
1942
+ data: JSON.stringify({
1943
+ id: newTask.id,
1944
+ url: buildTaskUrl(newTask.id),
1945
+ redeployFrom: taskId
1946
+ })
1947
+ });
1948
+ await flushLogs();
1949
+ if (!ctx.deployTaskManager.isActive(newTask.id)) {
1950
+ await stream.writeSSE({
1951
+ event: "done",
1952
+ data: JSON.stringify(buildDonePayload(ctx.deploymentDao.findById(newTask.id)))
1953
+ });
1954
+ return;
1955
+ }
1956
+ await new Promise((resolve7) => {
1957
+ let finished = false;
1958
+ const finish = async (payload) => {
1959
+ if (finished) return;
1960
+ finished = true;
1961
+ await flushLogs();
1962
+ await stream.writeSSE({
1963
+ event: "done",
1964
+ data: JSON.stringify(
1965
+ payload ?? buildDonePayload(ctx.deploymentDao.findById(newTask.id))
1966
+ )
1967
+ });
1968
+ resolve7();
1969
+ };
1970
+ const unsubscribe = ctx.deployTaskManager.subscribe(newTask.id, async (event) => {
1971
+ if (event.type === "log") {
1972
+ if (event.data.id <= lastLogId) return;
1973
+ lastLogId = event.data.id;
1974
+ await sendLog(formatDeploymentLogLine(event.data.message, event.data.createdAt));
1975
+ return;
1976
+ }
1977
+ unsubscribe();
1978
+ await finish(event.data);
1979
+ });
1980
+ void flushLogs().then(async () => {
1981
+ if (!ctx.deployTaskManager.isActive(newTask.id)) {
1982
+ unsubscribe();
1983
+ await finish();
1984
+ }
1985
+ });
1986
+ stream.onAbort(() => {
1987
+ unsubscribe();
1988
+ resolve7();
1989
+ });
1990
+ });
1991
+ });
1992
+ });
1993
+ app.post("/rollback", async (c) => {
1994
+ try {
1995
+ const body = await c.req.json();
1996
+ const ns = body.namespace;
1997
+ if (!ns) return c.json({ error: "namespace is required" }, 400);
1998
+ ctx.container.k8s.rolloutUndoAll(ns);
1999
+ return c.json({ ok: true, namespace: ns });
2000
+ } catch (err) {
2001
+ return c.json({ error: err.message }, 500);
2002
+ }
2003
+ });
2004
+ app.post("/destroy", async (c) => {
2005
+ try {
2006
+ const body = await c.req.json();
2007
+ const ns = body.namespace ?? ctx.namespaces[0] ?? "shadowob-cloud";
2008
+ const storedConfig = ctx.configDao.findByName("current")?.content;
2009
+ await ctx.container.deploymentRuntime.destroy({
2010
+ namespace: ns,
2011
+ stack: body.stack,
2012
+ configSnapshot: body.config ?? storedConfig
2013
+ });
2014
+ return c.json({ ok: true, namespace: ns });
2015
+ } catch (err) {
2016
+ return c.json({ error: err.message }, 500);
2017
+ }
2018
+ });
2019
+ app.post("/validate", async (c) => {
2020
+ try {
2021
+ const configData = await c.req.json();
2022
+ const tmpFile = join3(tmpdir(), `shadowob-validate-${Date.now()}.json`);
2023
+ writeFileSync3(tmpFile, JSON.stringify(configData, null, 2), "utf-8");
2024
+ try {
2025
+ const { config, violations } = await ctx.container.config.validate(tmpFile);
2026
+ const refs = ctx.container.config.collectTemplateRefs(config);
2027
+ const agents = config.deployments?.agents ?? [];
2028
+ const configurations = config.registry?.configurations ?? [];
2029
+ const configIds = new Set(configurations.map((cfg) => cfg.id));
2030
+ const extendsErrors = [];
2031
+ for (const agent of agents) {
2032
+ if (agent.configuration.extends && !configIds.has(agent.configuration.extends)) {
2033
+ extendsErrors.push(
2034
+ `Agent "${agent.id}" extends "${agent.configuration.extends}" not in registry.configurations`
2035
+ );
2036
+ }
2037
+ }
2038
+ return c.json({
2039
+ valid: violations.length === 0 && extendsErrors.length === 0,
2040
+ agents: agents.length,
2041
+ configurations: configurations.length,
2042
+ violations: violations.map((v) => ({ path: v.path, prefix: v.prefix })),
2043
+ extendsErrors,
2044
+ templateRefs: {
2045
+ env: refs.filter((r) => r.type === "env").length,
2046
+ secret: refs.filter((r) => r.type === "secret").length,
2047
+ file: refs.filter((r) => r.type === "file").length
2048
+ }
2049
+ });
2050
+ } finally {
2051
+ cleanupTmpFile(tmpFile);
2052
+ }
2053
+ } catch (err) {
2054
+ return c.json({ error: err.message }, 400);
2055
+ }
2056
+ });
2057
+ app.post("/init", async (c) => {
2058
+ try {
2059
+ const body = await c.req.json();
2060
+ const templateName = body.template ?? "gstack-buddy";
2061
+ const content = await ctx.container.template.getTemplate(templateName);
2062
+ if (!content) {
2063
+ return c.json({ error: `Template not found: ${templateName}` }, 404);
2064
+ }
2065
+ ctx.configDao.upsert("current", content, templateName);
2066
+ return c.json(content);
2067
+ } catch (err) {
2068
+ return c.json({ error: err.message }, 400);
2069
+ }
2070
+ });
2071
+ app.post("/provision", async (c) => {
2072
+ try {
2073
+ const body = await c.req.json();
2074
+ const shadowUrl = body.shadowUrl;
2075
+ const shadowToken = body.shadowToken;
2076
+ if (!shadowUrl || !shadowToken) {
2077
+ return c.json({ error: "shadowUrl and shadowToken are required" }, 400);
2078
+ }
2079
+ const tmpFile = join3(tmpdir(), `shadowob-provision-${Date.now()}.json`);
2080
+ writeFileSync3(tmpFile, JSON.stringify(body.config, null, 2), "utf-8");
2081
+ try {
2082
+ const config = await ctx.container.config.parseFile(tmpFile);
2083
+ const resolved = await ctx.container.config.resolve(config, tmpFile);
2084
+ const namespace = resolved.deployments?.namespace ?? "shadowob-cloud";
2085
+ const agents = resolved.deployments?.agents ?? [];
2086
+ const extraSecrets = {
2087
+ SHADOW_SERVER_URL: shadowUrl,
2088
+ SHADOW_USER_TOKEN: shadowToken
2089
+ };
2090
+ const { executePluginProvisions, loadAllPlugins: loadAllPlugins2, getPluginRegistry: getPluginRegistry2 } = await import("./plugins-HZBWK3WQ.js");
2091
+ try {
2092
+ await loadAllPlugins2(getPluginRegistry2());
2093
+ } catch {
2094
+ }
2095
+ const mergedStates = {};
2096
+ for (const agent of agents) {
2097
+ const provisionResults = await executePluginProvisions(
2098
+ agent,
2099
+ resolved,
2100
+ namespace,
2101
+ ctx.container.logger,
2102
+ body.dryRun,
2103
+ extraSecrets,
2104
+ null
2105
+ );
2106
+ for (const [pluginId, state] of Object.entries(provisionResults.states)) {
2107
+ mergedStates[pluginId] = { ...mergedStates[pluginId] ?? {}, ...state };
2108
+ }
2109
+ }
2110
+ const shadowobState = mergedStates.shadowob ?? {};
2111
+ return c.json({
2112
+ ok: true,
2113
+ servers: shadowobState.servers ?? {},
2114
+ channels: shadowobState.channels ?? {},
2115
+ buddies: Object.fromEntries(
2116
+ Object.entries(shadowobState.buddies ?? {}).map(([id, info]) => [
2117
+ id,
2118
+ { agentId: info.agentId, userId: info.userId }
2119
+ ])
2120
+ )
2121
+ });
2122
+ } finally {
2123
+ cleanupTmpFile(tmpFile);
2124
+ }
2125
+ } catch (err) {
2126
+ return c.json({ error: err.message }, 500);
2127
+ }
2128
+ });
2129
+ app.post("/generate/manifests", async (c) => {
2130
+ try {
2131
+ const body = await c.req.json();
2132
+ const tmpFile = join3(tmpdir(), `shadowob-gen-${Date.now()}.json`);
2133
+ writeFileSync3(tmpFile, JSON.stringify(body.config, null, 2), "utf-8");
2134
+ try {
2135
+ const config = await ctx.container.config.parseFile(tmpFile);
2136
+ const resolved = await ctx.container.config.resolve(config);
2137
+ const ns = body.namespace ?? config.deployments?.namespace ?? "shadowob-cloud";
2138
+ const manifests = ctx.container.manifest.build({
2139
+ config: resolved,
2140
+ namespace: ns,
2141
+ shadowServerUrl: body.shadowUrl
2142
+ });
2143
+ return c.json({ manifests, count: manifests.length });
2144
+ } finally {
2145
+ cleanupTmpFile(tmpFile);
2146
+ }
2147
+ } catch (err) {
2148
+ return c.json({ error: err.message }, 400);
2149
+ }
2150
+ });
2151
+ app.post("/generate/openclaw-config", async (c) => {
2152
+ try {
2153
+ const body = await c.req.json();
2154
+ const tmpFile = join3(tmpdir(), `shadowob-oc-${Date.now()}.json`);
2155
+ writeFileSync3(tmpFile, JSON.stringify(body.config, null, 2), "utf-8");
2156
+ try {
2157
+ const config = await ctx.container.config.parseFile(tmpFile);
2158
+ const resolved = await ctx.container.config.resolve(config);
2159
+ const agent = resolved.deployments?.agents?.find((a) => a.id === body.agentId);
2160
+ if (!agent) {
2161
+ return c.json({ error: `Agent "${body.agentId}" not found` }, 404);
2162
+ }
2163
+ const openclawConfig = ctx.container.config.buildOpenClawConfig(agent, resolved);
2164
+ delete openclawConfig._workspaceFiles;
2165
+ return c.json(openclawConfig);
2166
+ } finally {
2167
+ cleanupTmpFile(tmpFile);
2168
+ }
2169
+ } catch (err) {
2170
+ return c.json({ error: err.message }, 400);
2171
+ }
2172
+ });
2173
+ return app;
2174
+ }
2175
+
2176
+ // src/interfaces/http/handlers/health.handler.ts
2177
+ import { execSync } from "child_process";
2178
+ import { Hono as Hono6 } from "hono";
2179
+ function getVersion(cmd, versionFlag = "--version") {
2180
+ try {
2181
+ return execSync(`${cmd} ${versionFlag}`, {
2182
+ encoding: "utf-8",
2183
+ timeout: 1e4,
2184
+ stdio: ["ignore", "pipe", "pipe"]
2185
+ }).trim();
2186
+ } catch {
2187
+ return null;
2188
+ }
2189
+ }
2190
+ function createHealthHandler(ctx) {
2191
+ const app = new Hono6();
2192
+ app.get("/health", (c) => c.json({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
2193
+ app.get("/doctor", async (c) => {
2194
+ const { container } = ctx;
2195
+ const checks = [];
2196
+ const nodeVersion = process.version;
2197
+ const major = Number.parseInt(nodeVersion.slice(1), 10);
2198
+ checks.push(
2199
+ major >= 22 ? { name: "Node.js", status: "pass", message: nodeVersion } : {
2200
+ name: "Node.js",
2201
+ status: major >= 20 ? "warn" : "fail",
2202
+ message: `${nodeVersion} (22+ recommended)`
2203
+ }
2204
+ );
2205
+ if (container.k8s.isToolInstalled("docker")) {
2206
+ checks.push({ name: "Docker", status: "pass", message: getVersion("docker") ?? "installed" });
2207
+ } else {
2208
+ checks.push({ name: "Docker", status: "fail", message: "not found" });
2209
+ }
2210
+ if (container.k8s.isToolInstalled("kubectl")) {
2211
+ const reachable = container.k8s.isKubeReachable();
2212
+ checks.push({
2213
+ name: "kubectl",
2214
+ status: reachable ? "pass" : "warn",
2215
+ message: reachable ? "connected" : "installed but unreachable"
2216
+ });
2217
+ } else {
2218
+ checks.push({ name: "kubectl", status: "fail", message: "not found" });
2219
+ }
2220
+ if (container.k8s.isToolInstalled("pulumi")) {
2221
+ checks.push({
2222
+ name: "Pulumi",
2223
+ status: "pass",
2224
+ message: getVersion("pulumi", "version") ?? "installed"
2225
+ });
2226
+ } else {
2227
+ checks.push({ name: "Pulumi", status: "fail", message: "not found" });
2228
+ }
2229
+ if (container.k8s.isToolInstalled("kind")) {
2230
+ const hasCluster = container.k8s.kindClusterExists();
2231
+ checks.push({
2232
+ name: "kind",
2233
+ status: "pass",
2234
+ message: hasCluster ? "installed + cluster exists" : "installed"
2235
+ });
2236
+ } else {
2237
+ checks.push({ name: "kind", status: "warn", message: "not found (optional)" });
2238
+ }
2239
+ try {
2240
+ const { checkPluginHealth, loadAllPlugins: loadAllPlugins2, getPluginRegistry: getPluginRegistry2 } = await import("./plugins-HZBWK3WQ.js");
2241
+ try {
2242
+ await loadAllPlugins2(getPluginRegistry2());
2243
+ } catch {
2244
+ }
2245
+ const configRow = ctx.configDao.findByName("current");
2246
+ let cloudConfig = {};
2247
+ if (configRow) {
2248
+ cloudConfig = configRow.content;
2249
+ }
2250
+ const pluginHealthResults = await checkPluginHealth(cloudConfig);
2251
+ for (const result of pluginHealthResults) {
2252
+ checks.push({
2253
+ name: `Plugin: ${result.name}`,
2254
+ status: result.healthy ? "pass" : "warn",
2255
+ message: result.message
2256
+ });
2257
+ }
2258
+ } catch {
2259
+ }
2260
+ return c.json({
2261
+ checks,
2262
+ summary: {
2263
+ pass: checks.filter((ch) => ch.status === "pass").length,
2264
+ warn: checks.filter((ch) => ch.status === "warn").length,
2265
+ fail: checks.filter((ch) => ch.status === "fail").length
2266
+ }
2267
+ });
2268
+ });
2269
+ return app;
2270
+ }
2271
+
2272
+ // src/interfaces/http/handlers/my-templates.handler.ts
2273
+ import { Hono as Hono7 } from "hono";
2274
+ var PREFIX = "tpl:";
2275
+ function createMyTemplatesHandler(ctx) {
2276
+ const app = new Hono7();
2277
+ app.get("/my-templates", (c) => {
2278
+ const all = ctx.configDao.findAll();
2279
+ const userTemplates = all.filter((cfg) => cfg.name.startsWith(PREFIX)).map((cfg) => ({
2280
+ name: cfg.name.slice(PREFIX.length),
2281
+ slug: cfg.name.slice(PREFIX.length),
2282
+ templateSlug: cfg.templateSlug,
2283
+ content: cfg.content,
2284
+ version: cfg.version ?? 1,
2285
+ updatedAt: cfg.updatedAt ?? cfg.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
2286
+ }));
2287
+ return c.json(userTemplates);
2288
+ });
2289
+ app.get("/my-templates/:name", (c) => {
2290
+ const name = c.req.param("name");
2291
+ const cfg = ctx.configDao.findByName(`${PREFIX}${name}`);
2292
+ if (!cfg) return c.json({ error: `Template not found: ${name}` }, 404);
2293
+ return c.json({
2294
+ name: cfg.name.slice(PREFIX.length),
2295
+ slug: cfg.name.slice(PREFIX.length),
2296
+ templateSlug: cfg.templateSlug,
2297
+ content: cfg.content,
2298
+ version: cfg.version ?? 1
2299
+ });
2300
+ });
2301
+ app.get("/my-templates/:name/versions", (c) => {
2302
+ const name = c.req.param("name");
2303
+ const cfg = ctx.configDao.findByName(`${PREFIX}${name}`);
2304
+ if (!cfg) return c.json({ error: `Template not found: ${name}` }, 404);
2305
+ const history = ctx.configDao.getVersionHistory(`${PREFIX}${name}`);
2306
+ return c.json({
2307
+ current: cfg.version ?? 1,
2308
+ versions: [
2309
+ { version: cfg.version ?? 1, createdAt: cfg.updatedAt ?? cfg.createdAt, current: true },
2310
+ ...history.map((v) => ({ version: v.version, createdAt: v.createdAt, current: false }))
2311
+ ]
2312
+ });
2313
+ });
2314
+ app.post("/my-templates/:name/versions/:version", (c) => {
2315
+ const name = c.req.param("name");
2316
+ const version = Number.parseInt(c.req.param("version"), 10);
2317
+ const versionData = ctx.configDao.getVersion(`${PREFIX}${name}`, version);
2318
+ if (!versionData) return c.json({ error: `Version ${version} not found` }, 404);
2319
+ ctx.configDao.upsert(`${PREFIX}${name}`, versionData.content);
2320
+ return c.json({ ok: true, restoredVersion: version });
2321
+ });
2322
+ app.put("/my-templates/:name", async (c) => {
2323
+ const name = c.req.param("name");
2324
+ try {
2325
+ const body = await c.req.json();
2326
+ ctx.configDao.upsert(`${PREFIX}${name}`, body.content, body.templateSlug);
2327
+ return c.json({ ok: true });
2328
+ } catch (err) {
2329
+ return c.json({ error: err.message }, 400);
2330
+ }
2331
+ });
2332
+ app.post("/my-templates/fork", async (c) => {
2333
+ try {
2334
+ const body = await c.req.json();
2335
+ const sourceContent = await ctx.container.template.getTemplate(body.source);
2336
+ if (!sourceContent) {
2337
+ return c.json({ error: `Source template not found: ${body.source}` }, 404);
2338
+ }
2339
+ let newName = body.name ?? `my-${body.source}`;
2340
+ const existing = ctx.configDao.findByName(`${PREFIX}${newName}`);
2341
+ if (existing) {
2342
+ let suffix = 2;
2343
+ while (ctx.configDao.findByName(`${PREFIX}${newName}-${suffix}`)) {
2344
+ suffix++;
2345
+ }
2346
+ newName = `${newName}-${suffix}`;
2347
+ }
2348
+ ctx.configDao.upsert(`${PREFIX}${newName}`, sourceContent, body.source);
2349
+ return c.json({ name: newName, slug: newName });
2350
+ } catch (err) {
2351
+ return c.json({ error: err.message }, 400);
2352
+ }
2353
+ });
2354
+ app.delete("/my-templates/:name", (c) => {
2355
+ const name = c.req.param("name");
2356
+ ctx.configDao.delete(`${PREFIX}${name}`);
2357
+ return c.json({ ok: true });
2358
+ });
2359
+ app.get("/my-templates/:name/share", (c) => {
2360
+ const name = c.req.param("name");
2361
+ const cfg = ctx.configDao.findByName(`${PREFIX}${name}`);
2362
+ if (!cfg) return c.json({ error: `Template not found: ${name}` }, 404);
2363
+ return c.json({
2364
+ name: cfg.name.slice(PREFIX.length),
2365
+ templateSlug: cfg.templateSlug,
2366
+ version: cfg.version ?? 1,
2367
+ content: cfg.content,
2368
+ sharedAt: (/* @__PURE__ */ new Date()).toISOString()
2369
+ });
2370
+ });
2371
+ app.post("/my-templates/import", async (c) => {
2372
+ try {
2373
+ const body = await c.req.json();
2374
+ if (!body.name || !body.content) {
2375
+ return c.json({ error: "name and content are required" }, 400);
2376
+ }
2377
+ const existing = ctx.configDao.findByName(`${PREFIX}${body.name}`);
2378
+ const importName = existing ? `${body.name}-${Date.now()}` : body.name;
2379
+ ctx.configDao.upsert(`${PREFIX}${importName}`, body.content, body.templateSlug);
2380
+ return c.json({ ok: true, name: importName });
2381
+ } catch (err) {
2382
+ return c.json({ error: err.message }, 400);
2383
+ }
2384
+ });
2385
+ app.post("/my-templates/import-git", async (c) => {
2386
+ try {
2387
+ const body = await c.req.json();
2388
+ if (!body.url) {
2389
+ return c.json({ error: "url is required" }, 400);
2390
+ }
2391
+ const url = body.url.trim();
2392
+ if (!url.startsWith("https://") && !url.startsWith("git@")) {
2393
+ return c.json({ error: "Only HTTPS and SSH git URLs are supported" }, 400);
2394
+ }
2395
+ const { execSync: execSync2 } = await import("child_process");
2396
+ const { mkdtempSync, readFileSync: readFileSync6, readdirSync, rmSync, existsSync: existsSync8 } = await import("fs");
2397
+ const { tmpdir: tmpdir2 } = await import("os");
2398
+ const { join: join6, basename } = await import("path");
2399
+ const tmpDir = mkdtempSync(join6(tmpdir2(), "sc-git-"));
2400
+ try {
2401
+ const branch = body.branch ? `--branch ${body.branch}` : "";
2402
+ execSync2(`git clone --depth 1 ${branch} ${url} ${tmpDir}/repo`, {
2403
+ timeout: 3e4,
2404
+ stdio: "pipe"
2405
+ });
2406
+ const repoDir = join6(tmpDir, "repo");
2407
+ const configPath = body.path ? join6(repoDir, body.path) : findConfigFile(repoDir, readdirSync, existsSync8, join6);
2408
+ if (!configPath || !existsSync8(configPath)) {
2409
+ return c.json(
2410
+ {
2411
+ error: `No config file found. Specify path or ensure the repo contains shadowob.json, *.template.json, or cloud.json`
2412
+ },
2413
+ 404
2414
+ );
2415
+ }
2416
+ const content = parseJsonc(readFileSync6(configPath, "utf-8"), configPath);
2417
+ const repoName = basename(url.replace(/\.git$/, "").replace(/\/$/, ""));
2418
+ let newName = body.name ?? repoName;
2419
+ const existing = ctx.configDao.findByName(`${PREFIX}${newName}`);
2420
+ if (existing) {
2421
+ let suffix = 2;
2422
+ while (ctx.configDao.findByName(`${PREFIX}${newName}-${suffix}`)) suffix++;
2423
+ newName = `${newName}-${suffix}`;
2424
+ }
2425
+ ctx.configDao.upsert(`${PREFIX}${newName}`, content, `git:${url}`);
2426
+ return c.json({ ok: true, name: newName, source: url });
2427
+ } finally {
2428
+ rmSync(tmpDir, { recursive: true, force: true });
2429
+ }
2430
+ } catch (err) {
2431
+ const msg = err.message ?? String(err);
2432
+ if (msg.includes("git clone")) {
2433
+ return c.json({ error: `Git clone failed: ${msg}` }, 400);
2434
+ }
2435
+ return c.json({ error: msg }, 400);
2436
+ }
2437
+ });
2438
+ return app;
2439
+ }
2440
+ function findConfigFile(dir, readdirSync, existsSync8, join6) {
2441
+ const candidates = ["shadowob.json", "shadowob-cloud.json", "cloud.json"];
2442
+ for (const f of candidates) {
2443
+ const p = join6(dir, f);
2444
+ if (existsSync8(p)) return p;
2445
+ }
2446
+ try {
2447
+ const files = readdirSync(dir);
2448
+ const tpl = files.find((f) => f.endsWith(".template.json"));
2449
+ if (tpl) return join6(dir, tpl);
2450
+ } catch {
2451
+ }
2452
+ return null;
2453
+ }
2454
+
2455
+ // src/interfaces/http/handlers/provider-profile.handler.ts
2456
+ import { randomUUID } from "crypto";
2457
+ import { Hono as Hono8 } from "hono";
2458
+
2459
+ // src/application/llm-provider-platform.ts
2460
+ var LLM_PROVIDER_AUTH_TYPES = ["api_key"];
2461
+ var LLM_PROVIDER_API_FORMATS = ["openai", "anthropic", "gemini"];
2462
+ function isRecord2(value) {
2463
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
2464
+ }
2465
+ function stringValue(value) {
2466
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
2467
+ }
2468
+ function positiveNumber(value) {
2469
+ const parsed = typeof value === "number" ? value : Number(value);
2470
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
2471
+ }
2472
+ function booleanValue(value) {
2473
+ return typeof value === "boolean" ? value : void 0;
2474
+ }
2475
+ function normalizeTags(value) {
2476
+ if (!Array.isArray(value)) return [];
2477
+ const tags = value.map((tag) => stringValue(tag)?.toLowerCase()).filter((tag) => Boolean(tag));
2478
+ return [...new Set(tags)];
2479
+ }
2480
+ function normalizeLlmProviderModels(value) {
2481
+ if (!Array.isArray(value)) return [];
2482
+ const models = [];
2483
+ const seen = /* @__PURE__ */ new Set();
2484
+ for (const item of value) {
2485
+ if (!isRecord2(item)) continue;
2486
+ const id = stringValue(item.id);
2487
+ if (!id || seen.has(id)) continue;
2488
+ seen.add(id);
2489
+ const cost = isRecord2(item.cost) ? {
2490
+ ...positiveNumber(item.cost.input) ? { input: positiveNumber(item.cost.input) } : {},
2491
+ ...positiveNumber(item.cost.output) ? { output: positiveNumber(item.cost.output) } : {}
2492
+ } : void 0;
2493
+ const capabilities = isRecord2(item.capabilities) ? {
2494
+ ...booleanValue(item.capabilities.vision) !== void 0 ? { vision: booleanValue(item.capabilities.vision) } : {},
2495
+ ...booleanValue(item.capabilities.tools) !== void 0 ? { tools: booleanValue(item.capabilities.tools) } : {},
2496
+ ...booleanValue(item.capabilities.reasoning) !== void 0 ? { reasoning: booleanValue(item.capabilities.reasoning) } : {}
2497
+ } : void 0;
2498
+ models.push({
2499
+ id,
2500
+ ...stringValue(item.name) ? { name: stringValue(item.name) } : {},
2501
+ ...normalizeTags(item.tags).length > 0 ? { tags: normalizeTags(item.tags) } : {},
2502
+ ...positiveNumber(item.contextWindow) ? { contextWindow: positiveNumber(item.contextWindow) } : {},
2503
+ ...positiveNumber(item.maxTokens) ? { maxTokens: positiveNumber(item.maxTokens) } : {},
2504
+ ...cost && Object.keys(cost).length > 0 ? { cost } : {},
2505
+ ...capabilities && Object.keys(capabilities).length > 0 ? { capabilities } : {}
2506
+ });
2507
+ }
2508
+ return models;
2509
+ }
2510
+ function normalizeLlmProviderConfig(config) {
2511
+ const apiFormat = stringValue(config.apiFormat);
2512
+ const authType = stringValue(config.authType);
2513
+ return {
2514
+ ...stringValue(config.baseUrl) ? { baseUrl: stringValue(config.baseUrl) } : {},
2515
+ ...apiFormat && LLM_PROVIDER_API_FORMATS.includes(apiFormat) ? { apiFormat } : {},
2516
+ ...authType && LLM_PROVIDER_AUTH_TYPES.includes(authType) ? { authType } : {},
2517
+ ...stringValue(config.discoveredAt) ? { discoveredAt: stringValue(config.discoveredAt) } : {},
2518
+ models: normalizeLlmProviderModels(config.models)
2519
+ };
2520
+ }
2521
+
2522
+ // src/application/provider-catalogs.ts
2523
+ async function ensurePluginsLoaded() {
2524
+ const registry = getPluginRegistry();
2525
+ if (registry.size === 0) await loadAllPlugins(registry);
2526
+ }
2527
+ function providerSecretFields(provider) {
2528
+ const fields = [
2529
+ {
2530
+ key: provider.envKey,
2531
+ label: `${provider.id} API Key`,
2532
+ required: false,
2533
+ sensitive: true
2534
+ }
2535
+ ];
2536
+ for (const alias of provider.envKeyAliases ?? []) {
2537
+ fields.push({
2538
+ key: alias,
2539
+ label: `${provider.id} API Key Alias`,
2540
+ required: false,
2541
+ sensitive: true
2542
+ });
2543
+ }
2544
+ if (provider.baseUrlEnvKey) {
2545
+ fields.push({
2546
+ key: provider.baseUrlEnvKey,
2547
+ label: `${provider.id} Base URL`,
2548
+ required: false,
2549
+ sensitive: false
2550
+ });
2551
+ }
2552
+ if (provider.modelEnvKey) {
2553
+ fields.push({
2554
+ key: provider.modelEnvKey,
2555
+ label: `${provider.id} Model`,
2556
+ required: false,
2557
+ sensitive: false
2558
+ });
2559
+ }
2560
+ return fields;
2561
+ }
2562
+ function dedupeFields(fields) {
2563
+ const seen = /* @__PURE__ */ new Set();
2564
+ const out = [];
2565
+ for (const field of fields) {
2566
+ if (seen.has(field.key)) continue;
2567
+ seen.add(field.key);
2568
+ out.push(field);
2569
+ }
2570
+ return out;
2571
+ }
2572
+ async function listProviderCatalogs() {
2573
+ await ensurePluginsLoaded();
2574
+ return getPluginRegistry().getAll().flatMap(
2575
+ (plugin) => (plugin.providerCatalogs ?? []).map((provider) => ({
2576
+ pluginId: plugin.manifest.id,
2577
+ pluginName: plugin.manifest.name,
2578
+ provider,
2579
+ secretFields: dedupeFields([
2580
+ ...plugin.secretFields ?? [],
2581
+ ...providerSecretFields(provider)
2582
+ ])
2583
+ }))
2584
+ ).sort(
2585
+ (a, b) => (a.provider.priority ?? 1e3) - (b.provider.priority ?? 1e3) || a.provider.id.localeCompare(b.provider.id)
2586
+ );
2587
+ }
2588
+
2589
+ // src/interfaces/http/handlers/provider-profile.handler.ts
2590
+ var PROVIDER_PROFILE_SCOPE_PREFIX = "provider:";
2591
+ var META_KEYS = {
2592
+ id: "SHADOW_PROVIDER_PROFILE_ID",
2593
+ providerId: "SHADOW_PROVIDER_ID",
2594
+ name: "SHADOW_PROVIDER_PROFILE_NAME",
2595
+ configJson: "SHADOW_PROVIDER_CONFIG_JSON",
2596
+ enabled: "SHADOW_PROVIDER_ENABLED"
2597
+ };
2598
+ var META_KEY_SET = new Set(Object.values(META_KEYS));
2599
+ function normalizeProviderProfileId(input) {
2600
+ return input.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
2601
+ }
2602
+ function providerProfileScope(profileId) {
2603
+ return `${PROVIDER_PROFILE_SCOPE_PREFIX}${profileId}`;
2604
+ }
2605
+ function parseConfig(value) {
2606
+ if (!value) return {};
2607
+ try {
2608
+ const parsed = JSON.parse(value);
2609
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
2610
+ } catch {
2611
+ return {};
2612
+ }
2613
+ }
2614
+ function readProfiles(ctx) {
2615
+ const masked = ctx.envVarDao.findAllMasked().filter((entry) => entry.scope.startsWith(PROVIDER_PROFILE_SCOPE_PREFIX));
2616
+ const scopes = [...new Set(masked.map((entry) => entry.scope))];
2617
+ return scopes.map((scope) => {
2618
+ const values = new Map(
2619
+ ctx.envVarDao.findByScope(scope).map((entry) => [entry.key, entry.value])
2620
+ );
2621
+ const fallbackId = scope.slice(PROVIDER_PROFILE_SCOPE_PREFIX.length);
2622
+ const id = values.get(META_KEYS.id) ?? fallbackId;
2623
+ const providerId = values.get(META_KEYS.providerId) ?? "";
2624
+ if (!id || !providerId) return null;
2625
+ return {
2626
+ id,
2627
+ providerId,
2628
+ name: values.get(META_KEYS.name) ?? providerId,
2629
+ scope,
2630
+ enabled: values.get(META_KEYS.enabled) !== "false",
2631
+ config: parseConfig(values.get(META_KEYS.configJson)),
2632
+ envVars: masked.filter((entry) => entry.scope === scope && !META_KEY_SET.has(entry.key)).map((entry) => ({
2633
+ key: entry.key,
2634
+ maskedValue: entry.maskedValue,
2635
+ isSecret: entry.isSecret
2636
+ }))
2637
+ };
2638
+ }).filter((profile) => Boolean(profile)).sort((left, right) => left.name.localeCompare(right.name));
2639
+ }
2640
+ function createProviderProfileHandler(ctx) {
2641
+ const app = new Hono8();
2642
+ app.get("/provider-catalogs", async (c) => {
2643
+ const providers = (await listProviderCatalogs()).map((entry) => ({
2644
+ pluginId: entry.pluginId,
2645
+ pluginName: entry.pluginName,
2646
+ provider: entry.provider,
2647
+ secretFields: entry.secretFields
2648
+ }));
2649
+ return c.json({ providers });
2650
+ });
2651
+ app.get("/provider-profiles", (c) => c.json({ profiles: readProfiles(ctx) }));
2652
+ app.put("/provider-profiles", async (c) => {
2653
+ const body = await c.req.json();
2654
+ const profileId = normalizeProviderProfileId(body.id ?? `${body.providerId}-${randomUUID().slice(0, 8)}`) || `${body.providerId}-${randomUUID().slice(0, 8)}`;
2655
+ const scope = providerProfileScope(profileId);
2656
+ ctx.envVarDao.upsert(scope, META_KEYS.id, profileId, true);
2657
+ ctx.envVarDao.upsert(scope, META_KEYS.providerId, body.providerId, true);
2658
+ ctx.envVarDao.upsert(scope, META_KEYS.name, body.name, true);
2659
+ ctx.envVarDao.upsert(scope, META_KEYS.configJson, JSON.stringify(body.config ?? {}), true);
2660
+ ctx.envVarDao.upsert(scope, META_KEYS.enabled, String(body.enabled ?? true), true);
2661
+ for (const [key, value] of Object.entries(body.envVars ?? {})) {
2662
+ if (value.trim()) ctx.envVarDao.upsert(scope, key, value, true);
2663
+ }
2664
+ return c.json({
2665
+ ok: true,
2666
+ profile: readProfiles(ctx).find((profile) => profile.id === profileId)
2667
+ });
2668
+ });
2669
+ app.post("/provider-profiles/:id/test", (c) => {
2670
+ const profileId = normalizeProviderProfileId(c.req.param("id"));
2671
+ const scope = providerProfileScope(profileId);
2672
+ const values = new Map(
2673
+ ctx.envVarDao.findByScope(scope).map((entry) => [entry.key, entry.value])
2674
+ );
2675
+ const enabled = values.get(META_KEYS.enabled) !== "false";
2676
+ const providerId = values.get(META_KEYS.providerId);
2677
+ const hasKey = [...values.keys()].some(
2678
+ (key) => key !== META_KEYS.configJson && /KEY|TOKEN/i.test(key)
2679
+ );
2680
+ return c.json({
2681
+ ok: Boolean(enabled && providerId && hasKey),
2682
+ status: null,
2683
+ message: enabled && providerId && hasKey ? "Connection check ready" : "Missing provider credentials",
2684
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString()
2685
+ });
2686
+ });
2687
+ app.post("/provider-profiles/:id/models/refresh", async (c) => {
2688
+ const profileId = normalizeProviderProfileId(c.req.param("id"));
2689
+ const scope = providerProfileScope(profileId);
2690
+ const values = new Map(
2691
+ ctx.envVarDao.findByScope(scope).map((entry) => [entry.key, entry.value])
2692
+ );
2693
+ const providerId = values.get(META_KEYS.providerId);
2694
+ const config = parseConfig(values.get(META_KEYS.configJson));
2695
+ const catalogs = await listProviderCatalogs();
2696
+ const catalog = catalogs.find((entry) => entry.provider.id === providerId);
2697
+ const configuredModels = normalizeLlmProviderConfig(config).models ?? [];
2698
+ const catalogModels = catalog?.provider.models.map((model) => ({
2699
+ id: model.id,
2700
+ name: model.name,
2701
+ tags: model.tags,
2702
+ contextWindow: model.contextWindow,
2703
+ maxTokens: model.maxTokens
2704
+ })) ?? [];
2705
+ const models = configuredModels.length > 0 ? configuredModels : catalogModels;
2706
+ const nextConfig = {
2707
+ ...config,
2708
+ models,
2709
+ discoveredAt: (/* @__PURE__ */ new Date()).toISOString()
2710
+ };
2711
+ ctx.envVarDao.upsert(scope, META_KEYS.configJson, JSON.stringify(nextConfig), true);
2712
+ return c.json({
2713
+ ok: true,
2714
+ status: null,
2715
+ message: `Discovered ${models.length} model(s)`,
2716
+ models,
2717
+ profile: readProfiles(ctx).find((profile) => profile.id === profileId)
2718
+ });
2719
+ });
2720
+ app.delete("/provider-profiles/:id", (c) => {
2721
+ const profileId = normalizeProviderProfileId(c.req.param("id"));
2722
+ const scope = providerProfileScope(profileId);
2723
+ for (const entry of ctx.envVarDao.findByScope(scope)) {
2724
+ ctx.envVarDao.delete(scope, entry.key);
2725
+ }
2726
+ return c.json({ ok: true });
2727
+ });
2728
+ return app;
2729
+ }
2730
+
2731
+ // src/interfaces/http/handlers/secret.handler.ts
2732
+ import { Hono as Hono9 } from "hono";
2733
+ function createSecretHandler(ctx) {
2734
+ const app = new Hono9();
2735
+ app.get("/secrets", (c) => {
2736
+ const secrets2 = ctx.secretDao.findAll();
2737
+ return c.json({ secrets: secrets2 });
2738
+ });
2739
+ app.get("/secrets/:providerId", (c) => {
2740
+ const providerId = c.req.param("providerId");
2741
+ const secrets2 = ctx.secretDao.findByProvider(providerId);
2742
+ return c.json({ secrets: secrets2 });
2743
+ });
2744
+ app.put("/secrets/:providerId", async (c) => {
2745
+ const providerId = c.req.param("providerId");
2746
+ try {
2747
+ const body = await c.req.json();
2748
+ if (!body.key || !body.value) {
2749
+ return c.json({ error: "key and value are required" }, 400);
2750
+ }
2751
+ ctx.secretDao.upsert(providerId, body.key, body.value, body.groupName);
2752
+ return c.json({ ok: true });
2753
+ } catch (err) {
2754
+ return c.json({ error: err.message }, 400);
2755
+ }
2756
+ });
2757
+ app.delete("/secrets/:providerId/:key", (c) => {
2758
+ const providerId = c.req.param("providerId");
2759
+ const key = c.req.param("key");
2760
+ ctx.secretDao.delete(providerId, key);
2761
+ return c.json({ ok: true });
2762
+ });
2763
+ app.delete("/secrets/:providerId", (c) => {
2764
+ const providerId = c.req.param("providerId");
2765
+ ctx.secretDao.deleteProvider(providerId);
2766
+ return c.json({ ok: true });
2767
+ });
2768
+ app.get("/env/groups", (c) => {
2769
+ const groups = new Set(ctx.envGroupDao.findAll());
2770
+ for (const envVar of ctx.envVarDao.findAllMasked()) {
2771
+ groups.add(normalizeGroupName(envVar.groupName));
2772
+ }
2773
+ return c.json({
2774
+ groups: [...groups].sort(
2775
+ (a, b) => a === "default" ? -1 : b === "default" ? 1 : a.localeCompare(b)
2776
+ )
2777
+ });
2778
+ });
2779
+ app.post("/env/groups", async (c) => {
2780
+ try {
2781
+ const body = await c.req.json();
2782
+ const name = normalizeGroupName(body.name);
2783
+ ctx.envGroupDao.create(name);
2784
+ return c.json({ ok: true, name });
2785
+ } catch (err) {
2786
+ return c.json({ error: err.message }, 400);
2787
+ }
2788
+ });
2789
+ app.delete("/env/groups/:name", (c) => {
2790
+ const name = c.req.param("name");
2791
+ if (!name || name === "default") {
2792
+ return c.json({ error: "Cannot delete the default group" }, 400);
2793
+ }
2794
+ ctx.envGroupDao.delete(name);
2795
+ return c.json({ ok: true });
2796
+ });
2797
+ app.get("/env", (c) => {
2798
+ const envVars2 = ctx.envVarDao.findAllMasked();
2799
+ const groups = new Set(ctx.envGroupDao.findAll());
2800
+ for (const envVar of envVars2) {
2801
+ groups.add(normalizeGroupName(envVar.groupName));
2802
+ }
2803
+ return c.json({
2804
+ envVars: envVars2,
2805
+ groups: [...groups].sort(
2806
+ (a, b) => a === "default" ? -1 : b === "default" ? 1 : a.localeCompare(b)
2807
+ )
2808
+ });
2809
+ });
2810
+ app.get("/env/:scope", (c) => {
2811
+ const scope = c.req.param("scope");
2812
+ const envVars2 = ctx.envVarDao.findByScope(scope);
2813
+ return c.json({ envVars: envVars2 });
2814
+ });
2815
+ app.get("/env/:scope/:key", (c) => {
2816
+ const scope = c.req.param("scope");
2817
+ const key = c.req.param("key");
2818
+ const envVar = ctx.envVarDao.findOne(scope, key);
2819
+ if (!envVar) {
2820
+ return c.json({ error: "Environment value not found" }, 404);
2821
+ }
2822
+ return c.json({ envVar });
2823
+ });
2824
+ app.put("/env/:scope", async (c) => {
2825
+ const scope = c.req.param("scope");
2826
+ try {
2827
+ const body = await c.req.json();
2828
+ if (!body.key || body.value === void 0) {
2829
+ return c.json({ error: "key and value are required" }, 400);
2830
+ }
2831
+ const groupName = normalizeGroupName(body.groupName);
2832
+ ctx.envGroupDao.ensure(groupName);
2833
+ ctx.envVarDao.upsert(scope, body.key, body.value, body.isSecret ?? true, groupName);
2834
+ return c.json({ ok: true });
2835
+ } catch (err) {
2836
+ return c.json({ error: err.message }, 400);
2837
+ }
2838
+ });
2839
+ app.delete("/env/:scope/:key", (c) => {
2840
+ const scope = c.req.param("scope");
2841
+ const key = c.req.param("key");
2842
+ ctx.envVarDao.delete(scope, key);
2843
+ return c.json({ ok: true });
2844
+ });
2845
+ return app;
2846
+ }
2847
+
2848
+ // src/interfaces/http/handlers/settings.handler.ts
2849
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
2850
+ import { homedir as homedir3 } from "os";
2851
+ import { join as join4, resolve as resolve4 } from "path";
2852
+ import { Hono as Hono10 } from "hono";
2853
+ function settingsPath2() {
2854
+ return join4(homedir3(), ".shadowob", "settings.json");
2855
+ }
2856
+ function readSettings2() {
2857
+ const p = settingsPath2();
2858
+ if (!existsSync5(p)) return {};
2859
+ try {
2860
+ return JSON.parse(readFileSync4(p, "utf-8"));
2861
+ } catch {
2862
+ return {};
2863
+ }
2864
+ }
2865
+ function writeSettings2(data) {
2866
+ const p = settingsPath2();
2867
+ mkdirSync4(join4(homedir3(), ".shadowob"), { recursive: true });
2868
+ writeFileSync4(p, JSON.stringify(data, null, 2), "utf-8");
2869
+ }
2870
+ function normalizeSettings(ctx, settings) {
2871
+ const normalized = JSON.parse(JSON.stringify(settings));
2872
+ let changed = false;
2873
+ if (Array.isArray(normalized.providers)) {
2874
+ ctx.envGroupDao.ensure("default");
2875
+ for (const provider of normalized.providers) {
2876
+ if (!provider || typeof provider !== "object") continue;
2877
+ const providerRecord = provider;
2878
+ const providerId = typeof providerRecord.id === "string" ? providerRecord.id : null;
2879
+ const apiKey = typeof providerRecord.apiKey === "string" ? providerRecord.apiKey : null;
2880
+ if (providerId && apiKey && apiKey.trim()) {
2881
+ const envKey = toProviderSecretEnvKey(providerId, "apiKey");
2882
+ if (!ctx.envVarDao.getValue("global", envKey)) {
2883
+ ctx.envVarDao.upsert("global", envKey, apiKey, true, "default");
2884
+ }
2885
+ delete providerRecord.apiKey;
2886
+ changed = true;
2887
+ }
2888
+ }
2889
+ }
2890
+ return { normalized, changed };
2891
+ }
2892
+ function createSettingsHandler(ctx) {
2893
+ const app = new Hono10();
2894
+ app.get("/settings", (c) => {
2895
+ const { normalized, changed } = normalizeSettings(ctx, readSettings2());
2896
+ if (changed) {
2897
+ writeSettings2(normalized);
2898
+ }
2899
+ return c.json(normalized);
2900
+ });
2901
+ app.put("/settings", async (c) => {
2902
+ try {
2903
+ const data = await c.req.json();
2904
+ const { normalized } = normalizeSettings(ctx, data);
2905
+ writeSettings2(normalized);
2906
+ return c.json({ ok: true });
2907
+ } catch (err) {
2908
+ return c.json({ error: err.message }, 400);
2909
+ }
2910
+ });
2911
+ app.get("/images", (c) => c.json(ctx.container.image.list()));
2912
+ app.get("/runtimes", (c) => {
2913
+ const runtimes = ctx.container.runtime.getAll();
2914
+ return c.json(
2915
+ runtimes.map((r) => ({ id: r.id, name: r.name, defaultImage: r.defaultImage }))
2916
+ );
2917
+ });
2918
+ app.get("/plugins", async (c) => {
2919
+ let enabledPlugins = {};
2920
+ const configRow = ctx.configDao.findByName("current");
2921
+ if (configRow) {
2922
+ const content = configRow.content;
2923
+ enabledPlugins = content.plugins ?? {};
2924
+ } else {
2925
+ const configPath = resolve4("shadowob-cloud.json");
2926
+ try {
2927
+ if (existsSync5(configPath)) {
2928
+ const config = JSON.parse(readFileSync4(configPath, "utf-8"));
2929
+ enabledPlugins = config.plugins ?? {};
2930
+ }
2931
+ } catch {
2932
+ }
2933
+ }
2934
+ const { getPluginRegistry: getPluginRegistry2, loadAllPlugins: loadAllPlugins2 } = await import("./plugins-HZBWK3WQ.js");
2935
+ try {
2936
+ await loadAllPlugins2(getPluginRegistry2());
2937
+ } catch {
2938
+ }
2939
+ const registry = getPluginRegistry2();
2940
+ const plugins = registry.getAll().map((p) => ({
2941
+ id: p.manifest.id,
2942
+ name: p.manifest.name,
2943
+ description: p.manifest.description,
2944
+ category: p.manifest.category,
2945
+ icon: p.manifest.icon,
2946
+ version: p.manifest.version,
2947
+ capabilities: p.manifest.capabilities,
2948
+ tags: p.manifest.tags,
2949
+ auth: {
2950
+ type: p.manifest.auth.type,
2951
+ fields: p.manifest.auth.fields.map((f) => ({
2952
+ key: f.key,
2953
+ label: f.label,
2954
+ description: f.description,
2955
+ required: f.required,
2956
+ placeholder: f.placeholder
2957
+ }))
2958
+ },
2959
+ enabled: !!enabledPlugins[p.manifest.id],
2960
+ hasSkills: !!p.skills,
2961
+ hasCli: !!p.cli,
2962
+ hasMcp: !!p.mcp,
2963
+ hasChannel: !!p.channel
2964
+ }));
2965
+ return c.json({ plugins });
2966
+ });
2967
+ app.put("/plugins/:id", async (c) => {
2968
+ const pluginId = c.req.param("id");
2969
+ const update = await c.req.json();
2970
+ const configRow = ctx.configDao.findByName("current");
2971
+ let config = configRow ? configRow.content : {};
2972
+ if (!config.plugins) config.plugins = {};
2973
+ const plugins = config.plugins;
2974
+ if (!plugins[pluginId]) plugins[pluginId] = {};
2975
+ if (update.enabled !== void 0) {
2976
+ plugins[pluginId].enabled = update.enabled;
2977
+ }
2978
+ if (update.secrets) {
2979
+ plugins[pluginId].secrets = update.secrets;
2980
+ }
2981
+ ctx.configDao.upsert("current", config);
2982
+ return c.json({ success: true });
2983
+ });
2984
+ return app;
2985
+ }
2986
+
2987
+ // src/interfaces/http/handlers/template.handler.ts
2988
+ import { Hono as Hono11 } from "hono";
2989
+ function extractEnvRefs(obj, policy) {
2990
+ const refs = /* @__PURE__ */ new Set();
2991
+ const pattern = /\$\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g;
2992
+ function walk(val) {
2993
+ if (typeof val === "string") {
2994
+ for (const match of val.matchAll(pattern)) {
2995
+ const envKey = match[1];
2996
+ if (!envKey || policy?.ignoredKeys?.includes(envKey)) continue;
2997
+ refs.add(policy?.aliases?.[envKey] ?? envKey);
2998
+ }
2999
+ } else if (Array.isArray(val)) {
3000
+ for (const item of val) walk(item);
3001
+ } else if (val && typeof val === "object") {
3002
+ for (const v of Object.values(val)) walk(v);
3003
+ }
3004
+ }
3005
+ walk(obj);
3006
+ return [...refs].sort();
3007
+ }
3008
+ function createTemplateHandler(ctx) {
3009
+ const app = new Hono11();
3010
+ app.get("/templates", async (c) => {
3011
+ const locale = c.req.query("locale");
3012
+ return c.json(await ctx.container.template.list(locale));
3013
+ });
3014
+ app.get("/templates/catalog", async (c) => {
3015
+ const locale = c.req.query("locale");
3016
+ return c.json(await ctx.container.templateI18n.listCatalog(locale));
3017
+ });
3018
+ app.get("/templates/:name/details", async (c) => {
3019
+ const name = c.req.param("name");
3020
+ const locale = c.req.query("locale");
3021
+ const detail = await ctx.container.templateI18n.getTemplateDetail(name, locale);
3022
+ if (!detail) return c.json({ error: `Template not found: ${name}` }, 404);
3023
+ return c.json({ template: detail });
3024
+ });
3025
+ app.get("/templates/:name", async (c) => {
3026
+ const name = c.req.param("name");
3027
+ const content = await ctx.container.template.getTemplate(name);
3028
+ if (!content) return c.json({ error: `Template not found: ${name}` }, 404);
3029
+ return c.json(content);
3030
+ });
3031
+ app.get("/templates/:name/env-refs", async (c) => {
3032
+ const name = c.req.param("name");
3033
+ const content = await ctx.container.template.getTemplate(name);
3034
+ if (!content) return c.json({ error: `Template not found: ${name}` }, 404);
3035
+ const [envRefPolicy, runtimeFields, runtimeEnvVars] = await Promise.all([
3036
+ collectRuntimeEnvRefPolicy(content),
3037
+ collectRuntimeEnvFields(content),
3038
+ collectRuntimeEnvRequirements(content)
3039
+ ]);
3040
+ const refs = extractEnvRefs(content, envRefPolicy);
3041
+ const byKey = new Map(runtimeFields.map((field) => [field.key, field]));
3042
+ for (const key of refs) {
3043
+ if (!byKey.has(key)) {
3044
+ byKey.set(key, {
3045
+ key,
3046
+ label: key,
3047
+ required: true,
3048
+ sensitive: /(TOKEN|SECRET|PASSWORD|PRIVATE|CREDENTIAL|API_KEY|_KEY$|_B64$)/i.test(key),
3049
+ source: "template",
3050
+ sourceId: "template",
3051
+ sourceLabel: "Template"
3052
+ });
3053
+ }
3054
+ }
3055
+ const fields = [...byKey.values()].sort((a, b) => a.key.localeCompare(b.key));
3056
+ const visibleKeys = new Set(fields.map((field) => field.key));
3057
+ const hiddenKeys = new Set(envRefPolicy.hiddenKeys);
3058
+ return c.json({
3059
+ template: name,
3060
+ requiredEnvVars: refs,
3061
+ fields,
3062
+ autoDetectedEnvVars: runtimeEnvVars.filter((key) => !visibleKeys.has(key) && !hiddenKeys.has(key)).sort()
3063
+ });
3064
+ });
3065
+ return app;
3066
+ }
3067
+
3068
+ // src/interfaces/http/middleware/auth.middleware.ts
3069
+ function createAuthMiddleware(authToken) {
3070
+ return async (c, next) => {
3071
+ const header = c.req.header("Authorization");
3072
+ if (!header) return c.json({ error: "Authorization required" }, 401);
3073
+ const token = header.replace(/^Bearer\s+/i, "");
3074
+ if (token !== authToken) return c.json({ error: "Invalid token" }, 403);
3075
+ await next();
3076
+ };
3077
+ }
3078
+
3079
+ // src/interfaces/http/middleware/error.middleware.ts
3080
+ function createErrorHandler(logger) {
3081
+ return (err, c) => {
3082
+ logger.error(`Request error: ${err.message}`);
3083
+ return c.json({ error: err.message }, 500);
3084
+ };
3085
+ }
3086
+
3087
+ // src/interfaces/http/app.ts
3088
+ var MIME_TYPES = {
3089
+ ".html": "text/html",
3090
+ ".js": "application/javascript",
3091
+ ".css": "text/css",
3092
+ ".json": "application/json",
3093
+ ".png": "image/png",
3094
+ ".jpg": "image/jpeg",
3095
+ ".svg": "image/svg+xml",
3096
+ ".ico": "image/x-icon",
3097
+ ".woff": "font/woff",
3098
+ ".woff2": "font/woff2",
3099
+ ".ttf": "font/ttf"
3100
+ };
3101
+ function consoleDir() {
3102
+ return resolve5(fileURLToPath(import.meta.url), "..", "console");
3103
+ }
3104
+ function createCloudApp(ctx, authToken) {
3105
+ const app = new Hono12();
3106
+ app.onError(createErrorHandler(ctx.container.logger));
3107
+ app.use("*", cors());
3108
+ if (authToken) {
3109
+ app.use("/api/*", createAuthMiddleware(authToken));
3110
+ }
3111
+ const healthHandler = createHealthHandler(ctx);
3112
+ app.route("/api", healthHandler);
3113
+ app.get("/health", (c) => c.json({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
3114
+ app.route("/api", createTemplateHandler(ctx));
3115
+ app.route("/api", createDeployHandler(ctx));
3116
+ app.route("/api", createClusterHandler(ctx));
3117
+ app.route("/api", createConfigHandler(ctx));
3118
+ app.route("/api", createSettingsHandler(ctx));
3119
+ app.route("/api", createActivityHandler(ctx));
3120
+ app.route("/api", createSecretHandler(ctx));
3121
+ app.route("/api", createProviderProfileHandler(ctx));
3122
+ app.route("/api", createMyTemplatesHandler(ctx));
3123
+ app.route("/api", createCommunityHandler(ctx));
3124
+ app.get("*", (c) => {
3125
+ const distDir = consoleDir();
3126
+ if (!existsSync6(distDir)) return c.json({ error: "Not found" }, 404);
3127
+ const pathname = new URL(c.req.url).pathname;
3128
+ const filePath = join5(distDir, pathname === "/" ? "index.html" : pathname);
3129
+ if (existsSync6(filePath) && statSync(filePath).isFile()) {
3130
+ const ext = extname(filePath);
3131
+ const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
3132
+ const content = readFileSync5(filePath);
3133
+ return new Response(content, {
3134
+ headers: {
3135
+ "Content-Type": contentType,
3136
+ "Cache-Control": ext === ".html" ? "no-cache" : "public, max-age=31536000, immutable"
3137
+ }
3138
+ });
3139
+ }
3140
+ const indexPath = join5(distDir, "index.html");
3141
+ if (existsSync6(indexPath)) {
3142
+ return c.html(readFileSync5(indexPath, "utf-8"));
3143
+ }
3144
+ return c.json({ error: "Not found" }, 404);
3145
+ });
3146
+ return app;
3147
+ }
3148
+
3149
+ // src/application/cloud-saas-config.ts
3150
+ import { z } from "zod";
3151
+ var CLOUD_SAAS_RUNTIME_KEY = "__shadowobRuntime";
3152
+ var agentSnapshotSchema = z.object({
3153
+ id: z.string().min(1),
3154
+ runtime: z.string().min(1)
3155
+ }).passthrough();
3156
+ var cloudConfigSnapshotSchema = z.object({
3157
+ version: z.string().min(1),
3158
+ deployments: z.object({
3159
+ agents: z.array(agentSnapshotSchema).min(1)
3160
+ }).passthrough()
3161
+ }).passthrough();
3162
+ var runtimeMetadataSchema = z.object({
3163
+ envVars: z.record(z.string()).default({}),
3164
+ context: z.object({
3165
+ locale: z.string().optional(),
3166
+ timezone: z.string().optional()
3167
+ }).optional(),
3168
+ provisionState: z.unknown().optional()
3169
+ }).passthrough();
3170
+ var SENSITIVE_KEY_PATTERN = /(^|[_-])(token|secret|password|passphrase|api[-_]?key|private[-_]?key|credential|authorization|cookie|session|kubeconfig|encrypted)([_-]|$)/i;
3171
+ var SENSITIVE_CONTAINER_KEYS = /* @__PURE__ */ new Set(["secrets", "envVars"]);
3172
+ function formatValidationError(error) {
3173
+ return error.issues.slice(0, 5).map((issue) => {
3174
+ const path = issue.path.length > 0 ? issue.path.join(".") : "root";
3175
+ return `${path}: ${issue.message}`;
3176
+ }).join("; ");
3177
+ }
3178
+ function normalizeRuntimeEnvVars(envVars2) {
3179
+ const normalized = {};
3180
+ if (!envVars2) return normalized;
3181
+ for (const [key, value] of Object.entries(envVars2)) {
3182
+ if (typeof value !== "string") continue;
3183
+ if (value === "__SAVED__" || value.trim() === "") continue;
3184
+ normalized[key] = value;
3185
+ }
3186
+ return normalized;
3187
+ }
3188
+ function normalizeProvisionState(value) {
3189
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
3190
+ const candidate = value;
3191
+ const plugins = candidate.plugins;
3192
+ if (!plugins || typeof plugins !== "object" || Array.isArray(plugins)) return null;
3193
+ return {
3194
+ provisionedAt: typeof candidate.provisionedAt === "string" ? candidate.provisionedAt : (/* @__PURE__ */ new Date()).toISOString(),
3195
+ ...typeof candidate.stackName === "string" ? { stackName: candidate.stackName } : {},
3196
+ ...typeof candidate.namespace === "string" ? { namespace: candidate.namespace } : {},
3197
+ plugins
3198
+ };
3199
+ }
3200
+ function isLoopbackShadowUrl(url) {
3201
+ if (!url) return false;
3202
+ try {
3203
+ const parsed = new URL(url);
3204
+ return parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]";
3205
+ } catch {
3206
+ return false;
3207
+ }
3208
+ }
3209
+ function resolveProvisionShadowUrl(runtimeEnvVars, processEnv, fallbackShadowUrl) {
3210
+ const explicitProvisionUrl = runtimeEnvVars.SHADOW_PROVISION_URL ?? processEnv.SHADOW_PROVISION_URL;
3211
+ if (explicitProvisionUrl) return explicitProvisionUrl;
3212
+ if (isLoopbackShadowUrl(fallbackShadowUrl)) {
3213
+ return processEnv.SHADOW_SERVER_URL ?? fallbackShadowUrl;
3214
+ }
3215
+ return fallbackShadowUrl;
3216
+ }
3217
+ function redactUnknown(value, forceRedact = false) {
3218
+ if (Array.isArray(value)) {
3219
+ return value.map((item) => redactUnknown(item, forceRedact));
3220
+ }
3221
+ if (value && typeof value === "object") {
3222
+ return Object.fromEntries(
3223
+ Object.entries(value).map(([key, childValue]) => {
3224
+ if (key === CLOUD_SAAS_RUNTIME_KEY) {
3225
+ return [key, void 0];
3226
+ }
3227
+ if (forceRedact || SENSITIVE_CONTAINER_KEYS.has(key) || SENSITIVE_KEY_PATTERN.test(key)) {
3228
+ return [key, redactUnknown(childValue, true)];
3229
+ }
3230
+ return [key, redactUnknown(childValue, false)];
3231
+ })
3232
+ );
3233
+ }
3234
+ if (forceRedact && value !== null && value !== void 0) {
3235
+ return "[REDACTED]";
3236
+ }
3237
+ return value;
3238
+ }
3239
+ function validateCloudSaasConfigSnapshot(configSnapshot) {
3240
+ const result = cloudConfigSnapshotSchema.safeParse(configSnapshot);
3241
+ if (!result.success) {
3242
+ throw Object.assign(
3243
+ new Error(`Invalid configSnapshot: ${formatValidationError(result.error)}`),
3244
+ { status: 422 }
3245
+ );
3246
+ }
3247
+ return result.data;
3248
+ }
3249
+ function prepareCloudSaasConfigSnapshot(configSnapshot, envVars2, context) {
3250
+ const validated = validateCloudSaasConfigSnapshot(configSnapshot);
3251
+ const runtimeEnvVars = normalizeRuntimeEnvVars(envVars2);
3252
+ const runtimeContext = normalizeDeploymentRuntimeContext(context);
3253
+ const runtime = runtimeMetadataSchema.safeParse(validated[CLOUD_SAAS_RUNTIME_KEY]);
3254
+ const configWithLocale = runtimeContext.locale ? { ...validated, locale: runtimeContext.locale } : validated;
3255
+ if (Object.keys(runtimeEnvVars).length === 0 && isDeploymentRuntimeContextEmpty(runtimeContext)) {
3256
+ return configWithLocale;
3257
+ }
3258
+ return {
3259
+ ...configWithLocale,
3260
+ [CLOUD_SAAS_RUNTIME_KEY]: {
3261
+ ...runtime.success && runtime.data && typeof runtime.data === "object" ? runtime.data : {},
3262
+ envVars: runtimeEnvVars,
3263
+ ...isDeploymentRuntimeContextEmpty(runtimeContext) ? {} : { context: runtimeContext }
3264
+ }
3265
+ };
3266
+ }
3267
+ function attachCloudSaasProvisionState(configSnapshot, provisionState) {
3268
+ const validated = validateCloudSaasConfigSnapshot(configSnapshot);
3269
+ const runtime = runtimeMetadataSchema.safeParse(validated[CLOUD_SAAS_RUNTIME_KEY]);
3270
+ const envVars2 = runtime.success ? normalizeRuntimeEnvVars(runtime.data.envVars) : {};
3271
+ const context = runtime.success ? normalizeDeploymentRuntimeContext(runtime.data.context) : void 0;
3272
+ return {
3273
+ ...validated,
3274
+ [CLOUD_SAAS_RUNTIME_KEY]: {
3275
+ ...runtime.success && runtime.data && typeof runtime.data === "object" ? runtime.data : {},
3276
+ envVars: envVars2,
3277
+ ...context && !isDeploymentRuntimeContextEmpty(context) ? { context } : {},
3278
+ provisionState
3279
+ }
3280
+ };
3281
+ }
3282
+ function extractCloudSaasRuntime(configSnapshot) {
3283
+ if (!configSnapshot || typeof configSnapshot !== "object" || Array.isArray(configSnapshot)) {
3284
+ return { configSnapshot: null, envVars: {}, context: {}, provisionState: null };
3285
+ }
3286
+ const snapshot = { ...configSnapshot };
3287
+ const runtime = runtimeMetadataSchema.safeParse(snapshot[CLOUD_SAAS_RUNTIME_KEY]);
3288
+ delete snapshot[CLOUD_SAAS_RUNTIME_KEY];
3289
+ return {
3290
+ configSnapshot: snapshot,
3291
+ envVars: runtime.success ? normalizeRuntimeEnvVars(runtime.data.envVars) : {},
3292
+ context: runtime.success ? normalizeDeploymentRuntimeContext(runtime.data.context) : {},
3293
+ provisionState: runtime.success ? normalizeProvisionState(runtime.data.provisionState) : null
3294
+ };
3295
+ }
3296
+ function resolveCloudSaasShadowRuntime(envVars2, processEnv = process.env) {
3297
+ const runtimeEnvVars = normalizeRuntimeEnvVars(envVars2);
3298
+ const runtimeShadowUrl = runtimeEnvVars.SHADOW_SERVER_URL ?? processEnv.SHADOW_SERVER_URL;
3299
+ const shadowUrl = resolveProvisionShadowUrl(runtimeEnvVars, processEnv, runtimeShadowUrl);
3300
+ const podShadowUrl = runtimeEnvVars.SHADOW_AGENT_SERVER_URL ?? processEnv.SHADOW_AGENT_SERVER_URL ?? runtimeShadowUrl ?? shadowUrl;
3301
+ const shadowToken = runtimeEnvVars.SHADOW_USER_TOKEN ?? processEnv.SHADOW_USER_TOKEN;
3302
+ return { shadowUrl, podShadowUrl, shadowToken };
3303
+ }
3304
+ function redactCloudSaasConfigSnapshot(configSnapshot) {
3305
+ const { configSnapshot: snapshot } = extractCloudSaasRuntime(configSnapshot);
3306
+ if (!snapshot) return null;
3307
+ const redacted = redactUnknown(snapshot, false);
3308
+ if (!redacted || typeof redacted !== "object" || Array.isArray(redacted)) {
3309
+ return redacted;
3310
+ }
3311
+ const result = { ...redacted };
3312
+ delete result[CLOUD_SAAS_RUNTIME_KEY];
3313
+ return result;
3314
+ }
3315
+ function sanitizeCloudSaasDeployment(deployment) {
3316
+ return {
3317
+ ...deployment,
3318
+ configSnapshot: redactCloudSaasConfigSnapshot(deployment.configSnapshot)
3319
+ };
3320
+ }
3321
+
3322
+ // src/interfaces/http/deploy-task-manager.ts
3323
+ function shouldSkipProgressLine(line) {
3324
+ const trimmed = line.trim();
3325
+ return trimmed === "." || trimmed === ".." || /^@\s+updating\.+/.test(trimmed) || /^\.\.+$/.test(trimmed);
3326
+ }
3327
+ var DeployTaskManager = class {
3328
+ constructor(container, deploymentDao, deploymentLogDao, envVarDao) {
3329
+ this.container = container;
3330
+ this.deploymentDao = deploymentDao;
3331
+ this.deploymentLogDao = deploymentLogDao;
3332
+ this.envVarDao = envVarDao;
3333
+ }
3334
+ subscribers = /* @__PURE__ */ new Map();
3335
+ activeTaskIds = /* @__PURE__ */ new Set();
3336
+ cancelTokens = /* @__PURE__ */ new Map();
3337
+ async start(config) {
3338
+ const task = this.deploymentDao.create({
3339
+ namespace: config.namespace ?? "shadowob-cloud",
3340
+ templateSlug: config.templateSlug ?? config.name ?? null,
3341
+ status: "pending",
3342
+ config,
3343
+ agentCount: 0
3344
+ });
3345
+ this.activeTaskIds.add(task.id);
3346
+ queueMicrotask(() => {
3347
+ void this.run(task.id, config);
3348
+ });
3349
+ return task;
3350
+ }
3351
+ isActive(taskId) {
3352
+ return this.activeTaskIds.has(taskId);
3353
+ }
3354
+ async cancel(taskId) {
3355
+ const task = this.deploymentDao.findById(taskId);
3356
+ if (!task) return { ok: false, error: "Deployment task not found" };
3357
+ if (!this.activeTaskIds.has(taskId)) {
3358
+ if (task.status === "pending" || task.status === "running" || task.status === "cancelling") {
3359
+ this.deploymentDao.update(taskId, { status: "failed", error: "cancelled by user" });
3360
+ this.appendLog(taskId, "[cancel] Task was not running; marked cancelled");
3361
+ return { ok: true, status: "failed" };
3362
+ }
3363
+ return { ok: false, error: `Cannot cancel deployment in status "${task.status}"` };
3364
+ }
3365
+ const token = this.cancelTokens.get(taskId);
3366
+ if (!token) return { ok: true, status: "cancelling" };
3367
+ token.cancelled = true;
3368
+ this.deploymentDao.update(taskId, { status: "cancelling" });
3369
+ this.appendLog(taskId, "[cancel] User requested cancellation");
3370
+ if (token.stack && !token.cancelSignalled) {
3371
+ token.cancelSignalled = true;
3372
+ await token.stack.cancel().catch((err) => {
3373
+ this.appendLog(taskId, `[cancel] Failed to signal Pulumi cancellation: ${String(err)}`);
3374
+ });
3375
+ }
3376
+ return { ok: true, status: "cancelling" };
3377
+ }
3378
+ subscribe(taskId, listener) {
3379
+ const listeners = this.subscribers.get(taskId) ?? /* @__PURE__ */ new Set();
3380
+ listeners.add(listener);
3381
+ this.subscribers.set(taskId, listeners);
3382
+ return () => {
3383
+ const current = this.subscribers.get(taskId);
3384
+ if (!current) return;
3385
+ current.delete(listener);
3386
+ if (current.size === 0) {
3387
+ this.subscribers.delete(taskId);
3388
+ }
3389
+ };
3390
+ }
3391
+ emit(taskId, event) {
3392
+ const listeners = this.subscribers.get(taskId);
3393
+ if (!listeners?.size) return;
3394
+ for (const listener of listeners) {
3395
+ void listener(event);
3396
+ }
3397
+ }
3398
+ appendLog(taskId, message) {
3399
+ const sanitized = redactSecrets(message);
3400
+ const log = this.deploymentLogDao.create({
3401
+ deploymentId: taskId,
3402
+ event: "log",
3403
+ message: sanitized
3404
+ });
3405
+ this.emit(taskId, {
3406
+ type: "log",
3407
+ data: { id: log.id, message: sanitized, createdAt: log.createdAt }
3408
+ });
3409
+ }
3410
+ appendOutput(taskId, output) {
3411
+ for (const line of output.split("\n").filter(Boolean)) {
3412
+ if (shouldSkipProgressLine(line)) continue;
3413
+ this.appendLog(taskId, line);
3414
+ }
3415
+ }
3416
+ async buildEnvOverrides(config) {
3417
+ const envOverrides = {};
3418
+ const namespace = typeof config.namespace === "string" ? config.namespace : void 0;
3419
+ const scopes = [GLOBAL_ENV_SCOPE];
3420
+ if (namespace) scopes.push(toDeploymentEnvScope(namespace));
3421
+ const savedEnvVars = this.envVarDao.findAllDecryptedByScopes(scopes);
3422
+ Object.assign(envOverrides, savedEnvVars);
3423
+ const wizardEnvVars = config.envVars;
3424
+ if (wizardEnvVars && typeof wizardEnvVars === "object") {
3425
+ for (const [key, value] of Object.entries(wizardEnvVars)) {
3426
+ if (typeof value === "string" && value !== "__SAVED__" && value.trim() !== "") {
3427
+ envOverrides[key] = value;
3428
+ }
3429
+ }
3430
+ }
3431
+ const runtimeContext = normalizeDeploymentRuntimeContext(
3432
+ config.runtimeContext
3433
+ );
3434
+ const { envVars: _envVars, runtimeContext: _runtimeContext, ...templateConfig } = config;
3435
+ if (runtimeContext.locale) {
3436
+ templateConfig.locale = runtimeContext.locale;
3437
+ }
3438
+ const policy = await collectRuntimeEnvRefPolicy(templateConfig);
3439
+ return {
3440
+ envOverrides: applyRuntimeEnvRefPolicy(envOverrides, policy),
3441
+ templateConfig,
3442
+ runtimeContext
3443
+ };
3444
+ }
3445
+ async run(taskId, config) {
3446
+ const { envOverrides, templateConfig, runtimeContext } = await this.buildEnvOverrides(config);
3447
+ const { shadowUrl, podShadowUrl, shadowToken } = resolveCloudSaasShadowRuntime(envOverrides);
3448
+ const cancelToken = { cancelled: false };
3449
+ this.cancelTokens.set(taskId, cancelToken);
3450
+ try {
3451
+ this.deploymentDao.update(taskId, {
3452
+ status: "running",
3453
+ namespace: templateConfig.namespace ?? config.namespace ?? "shadowob-cloud",
3454
+ templateSlug: config.templateSlug ?? config.name ?? null,
3455
+ config
3456
+ });
3457
+ this.appendLog(taskId, "Starting deployment...");
3458
+ const result = await this.container.deploymentRuntime.deployFromSnapshot({
3459
+ configSnapshot: templateConfig,
3460
+ runtimeEnvVars: envOverrides,
3461
+ runtimeContext,
3462
+ namespace: templateConfig.namespace,
3463
+ dryRun: templateConfig.dryRun,
3464
+ shadowUrl,
3465
+ shadowToken,
3466
+ k8sShadowUrl: podShadowUrl,
3467
+ onOutput: (output) => {
3468
+ this.appendOutput(taskId, output);
3469
+ },
3470
+ onStackReady: (stack) => {
3471
+ cancelToken.stack = stack;
3472
+ },
3473
+ isCancelled: () => cancelToken.cancelled
3474
+ });
3475
+ if (cancelToken.cancelled) {
3476
+ throw new Error("Deployment cancelled by user");
3477
+ }
3478
+ this.deploymentDao.update(taskId, {
3479
+ namespace: result.namespace ?? config.namespace ?? "shadowob-cloud",
3480
+ templateSlug: config.templateSlug ?? config.name ?? null,
3481
+ status: "deployed",
3482
+ config,
3483
+ agentCount: result.agentCount ?? 0,
3484
+ error: null
3485
+ });
3486
+ const doneEvent = {
3487
+ exitCode: 0,
3488
+ result: {
3489
+ namespace: result.namespace,
3490
+ agentCount: result.agentCount
3491
+ }
3492
+ };
3493
+ this.emit(taskId, { type: "done", data: doneEvent });
3494
+ } catch (error) {
3495
+ const message = error.message;
3496
+ const cancelled = cancelToken.cancelled || /cancel/i.test(message);
3497
+ this.deploymentDao.update(taskId, {
3498
+ status: "failed",
3499
+ error: cancelled ? "cancelled by user" : message
3500
+ });
3501
+ this.appendLog(taskId, cancelled ? `Cancelled: ${message}` : `Error: ${message}`);
3502
+ this.emit(taskId, { type: "done", data: { exitCode: 1, error: message } });
3503
+ } finally {
3504
+ this.activeTaskIds.delete(taskId);
3505
+ this.cancelTokens.delete(taskId);
3506
+ }
3507
+ }
3508
+ };
3509
+
3510
+ // src/interfaces/http/server.ts
3511
+ function createHandlerContext(container, namespaces) {
3512
+ const db = createDatabase();
3513
+ runMigrations(db);
3514
+ const configDao = new ConfigDao(db);
3515
+ const secretDao = new SecretDao(db);
3516
+ const deploymentDao = new DeploymentDao(db);
3517
+ const deploymentBackupDao = new DeploymentBackupDao(db);
3518
+ const deploymentLogDao = new DeploymentLogDao(db);
3519
+ const activityDao = new ActivityDao(db);
3520
+ const envVarDao = new EnvVarDao(db);
3521
+ const envGroupDao = new EnvGroupDao(db);
3522
+ const legacySecrets = secretDao.findAllDecryptedEntries();
3523
+ for (const entry of legacySecrets) {
3524
+ envGroupDao.ensure(entry.groupName);
3525
+ const envKey = toProviderSecretEnvKey(entry.providerId, entry.key);
3526
+ if (!envVarDao.getValue("global", envKey)) {
3527
+ envVarDao.upsert("global", envKey, entry.value, true, entry.groupName);
3528
+ }
3529
+ }
3530
+ if (legacySecrets.length > 0) {
3531
+ secretDao.deleteAll();
3532
+ }
3533
+ const deployTaskManager = new DeployTaskManager(
3534
+ container,
3535
+ deploymentDao,
3536
+ deploymentLogDao,
3537
+ envVarDao
3538
+ );
3539
+ return {
3540
+ container,
3541
+ configDao,
3542
+ secretDao,
3543
+ deploymentDao,
3544
+ deploymentBackupDao,
3545
+ deploymentLogDao,
3546
+ activityDao,
3547
+ envVarDao,
3548
+ envGroupDao,
3549
+ deployTaskManager,
3550
+ namespaces
3551
+ };
3552
+ }
3553
+ function consoleDir2() {
3554
+ return resolve6(fileURLToPath2(import.meta.url), "..", "console");
3555
+ }
3556
+ function startHttpServer(container, options) {
3557
+ let authToken = options.authToken;
3558
+ const isExternalBind = options.host !== "127.0.0.1" && options.host !== "localhost";
3559
+ if (isExternalBind && !authToken) {
3560
+ authToken = randomBytes3(32).toString("hex");
3561
+ container.logger.warn(
3562
+ `Binding to ${options.host} requires authentication. Auto-generated token:`
3563
+ );
3564
+ container.logger.info(` ${authToken}`);
3565
+ container.logger.dim(" Pass via: --auth-token <token> or Authorization: Bearer <token>");
3566
+ }
3567
+ const ctx = createHandlerContext(container, options.namespaces);
3568
+ const app = createCloudApp(ctx, authToken);
3569
+ const server = serve(
3570
+ {
3571
+ fetch: app.fetch,
3572
+ port: options.port,
3573
+ hostname: options.host
3574
+ },
3575
+ (info) => {
3576
+ container.logger.success(
3577
+ `shadowob-cloud console running at http://${options.host}:${info.port}`
3578
+ );
3579
+ container.logger.dim(`Watching namespaces: ${options.namespaces.join(", ")}`);
3580
+ if (authToken) container.logger.dim("API authentication: enabled");
3581
+ const distDir = consoleDir2();
3582
+ if (existsSync7(distDir)) {
3583
+ container.logger.dim("Console: serving from dist/console/");
3584
+ } else {
3585
+ container.logger.dim("Console: not built (run console:build first)");
3586
+ }
3587
+ }
3588
+ );
3589
+ return server;
3590
+ }
3591
+
3592
+ // src/interfaces/cli/serve.command.ts
3593
+ function createServeCommand(container) {
3594
+ return new Command("serve").description("Start the shadowob-cloud console API server").option("-p, --port <number>", "Port to listen on", "3004").option("-n, --namespace <ns...>", "Kubernetes namespace(s) to watch", ["shadowob-cloud"]).option("--host <host>", "Host to bind to", "127.0.0.1").option("--auth-token <token>", "Bearer token for API authentication").action((options) => {
3595
+ const port = Number.parseInt(options.port, 10);
3596
+ const namespaces = Array.isArray(options.namespace) ? options.namespace : [options.namespace];
3597
+ startHttpServer(container, {
3598
+ port,
3599
+ host: options.host,
3600
+ namespaces,
3601
+ authToken: options.authToken
3602
+ });
3603
+ });
3604
+ }
3605
+
3606
+ export {
3607
+ CLOUD_SAAS_RUNTIME_KEY,
3608
+ validateCloudSaasConfigSnapshot,
3609
+ prepareCloudSaasConfigSnapshot,
3610
+ attachCloudSaasProvisionState,
3611
+ extractCloudSaasRuntime,
3612
+ resolveCloudSaasShadowRuntime,
3613
+ redactCloudSaasConfigSnapshot,
3614
+ sanitizeCloudSaasDeployment,
3615
+ resolveCloudPackageAssetDir,
3616
+ loadCloudConfigSchema,
3617
+ detectInlineKey,
3618
+ listProviderCatalogs,
3619
+ toProviderSecretEnvKey,
3620
+ withLegacyEnvAliases,
3621
+ createServeCommand
3622
+ };