@shadowob/cloud 1.1.6 → 1.1.8
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.
- package/README.md +159 -11
- package/dist/{agent-browser-CERTMCDL.js → agent-browser-EI7FIK3X.js} +3 -3
- package/dist/{agent-browser-CIRZRIY4.js → agent-browser-YXE4ES6Q.js} +3 -3
- package/dist/{agent-pack-LF3O5TR4.js → agent-pack-35TFCZKP.js} +1 -1
- package/dist/{agent-pack-RQT27V7R.js → agent-pack-UOG6ZAUL.js} +1 -1
- package/dist/agentmemory-KP5O7GHB.js +101 -0
- package/dist/agentmemory-UV74POU5.js +100 -0
- package/dist/{airtable-BG2Q75G2.js → airtable-CXXS3YUN.js} +3 -3
- package/dist/{airtable-JCQXFM5D.js → airtable-YZ5JR5JC.js} +3 -3
- package/dist/{alipay-TZQI34RB.js → alipay-WED5P3XC.js} +3 -3
- package/dist/{alipay-MZX2XCDB.js → alipay-WJTVREMG.js} +3 -3
- package/dist/{amap-KPCLZYYL.js → amap-AIN23TQ7.js} +3 -3
- package/dist/{amap-5RQB3VGC.js → amap-F6GF7QKB.js} +3 -3
- package/dist/{atlassian-LGOEWYC7.js → atlassian-G6PM6UVM.js} +3 -3
- package/dist/{atlassian-TVS2A4IU.js → atlassian-IDR2NPJC.js} +3 -3
- package/dist/{baidu-appbuilder-QRRL3ETM.js → baidu-appbuilder-JKQIA5TG.js} +3 -3
- package/dist/{baidu-appbuilder-6UMESXHW.js → baidu-appbuilder-KVMCIFYH.js} +3 -3
- package/dist/{baidu-maps-HEPMVP5D.js → baidu-maps-GWMXV6YT.js} +3 -3
- package/dist/{baidu-maps-HXC4FBVP.js → baidu-maps-ZXGT6QZM.js} +3 -3
- package/dist/{baidu-netdisk-G5Q6B5NH.js → baidu-netdisk-J5SLQVWW.js} +3 -3
- package/dist/{baidu-netdisk-RS2K5W2M.js → baidu-netdisk-PONM3DY6.js} +3 -3
- package/dist/{baidu-smartprogram-JHD3XWF6.js → baidu-smartprogram-WJ5JMO6V.js} +3 -3
- package/dist/{baidu-smartprogram-EWTK5WKK.js → baidu-smartprogram-Y5WBR7RX.js} +3 -3
- package/dist/{browserbase-IUIYVYI7.js → browserbase-CGVQHRC4.js} +3 -3
- package/dist/{browserbase-JFO2PCIA.js → browserbase-PVWYTEZC.js} +3 -3
- package/dist/{canva-3YOFL7JS.js → canva-COCBQAT2.js} +3 -3
- package/dist/{canva-FMYN65SM.js → canva-RSTJSEX5.js} +3 -3
- package/dist/{chunk-ULOJLXQV.js → chunk-3CT6RQNM.js} +766 -482
- package/dist/{chunk-SIDBK5SZ.js → chunk-4YO3NA26.js} +1 -1
- package/dist/{chunk-RECNVWMT.js → chunk-6V7MW4HU.js} +17 -3
- package/dist/{chunk-2FWJSE6Z.js → chunk-EVV774KS.js} +1 -1
- package/dist/{chunk-SVMXSIMG.js → chunk-F6CQ6GAG.js} +2 -1
- package/dist/{chunk-JUPAE5IA.js → chunk-OL5VH6RN.js} +72 -69
- package/dist/{chunk-POSVEKIY.js → chunk-OYY64ZSX.js} +17 -3
- package/dist/{chunk-FSW4TNHN.js → chunk-P5Y6F2NH.js} +766 -482
- package/dist/{chunk-EEFMJYKB.js → chunk-PSK2SYZ3.js} +2 -1
- package/dist/{chunk-HRTBOZ7O.js → chunk-PYJRFKPN.js} +1 -1
- package/dist/{chunk-JY2HTT7Q.js → chunk-RMDY3W4V.js} +6 -0
- package/dist/{chunk-I3NRNCDR.js → chunk-X2SREECR.js} +6 -6
- package/dist/{chunk-XFJX4NWN.js → chunk-X5VOIA72.js} +6 -6
- package/dist/{chunk-CTNUKOQE.js → chunk-Y5BJ3EW2.js} +6 -0
- package/dist/{chunk-IXN3FU5J.js → chunk-Y6BKVDG7.js} +1 -1
- package/dist/{chunk-6P2K6QZR.js → chunk-ZGMWSSCC.js} +72 -69
- package/dist/{claude-plugin-577TAQVS.js → claude-plugin-FPN32WMT.js} +1 -1
- package/dist/{claude-plugin-L3MXJJ6J.js → claude-plugin-IYHOVTVL.js} +1 -1
- package/dist/cli.js +930 -149
- package/dist/{cloudflare-RDFPKMM5.js → cloudflare-U3RHJJKK.js} +3 -3
- package/dist/{cloudflare-HBBABPK6.js → cloudflare-ZHN7UGPX.js} +3 -3
- package/dist/{cnb-FLP3QX46.js → cnb-AMXC5I7D.js} +3 -3
- package/dist/{cnb-YAVVEYFB.js → cnb-P3IZ4JTD.js} +3 -3
- package/dist/console/index.html +1 -1
- package/dist/console/static/css/index.f4563d95.css +1 -0
- package/dist/console/static/js/index.020abc71.js +1 -0
- package/dist/{coze-E6VGRNLV.js → coze-66RYMKVB.js} +3 -3
- package/dist/{coze-C6PMDPBI.js → coze-YE3BINXP.js} +3 -3
- package/dist/{dashboard.command-SCAAQ23X.js → dashboard.command-BRPZCZER.js} +1 -1
- package/dist/{dashboard.command-H5DIGLUR.js → dashboard.command-GUHSJ2CN.js} +1 -1
- package/dist/{dingtalk-JNRNRN7E.js → dingtalk-4RFQG7N2.js} +3 -3
- package/dist/{dingtalk-WZGGIAHJ.js → dingtalk-VNFKXD2P.js} +3 -3
- package/dist/{douyin-miniprogram-AIJPPIZH.js → douyin-miniprogram-UEALAGOS.js} +3 -3
- package/dist/{douyin-miniprogram-HCYZ5NBW.js → douyin-miniprogram-UNB6UO2I.js} +3 -3
- package/dist/{figma-2YYNSCDX.js → figma-A264OWU5.js} +3 -3
- package/dist/{figma-RYOBMENP.js → figma-Y4TGSDZP.js} +3 -3
- package/dist/{firebase-OYSY6HPT.js → firebase-AI3MAGYG.js} +3 -3
- package/dist/{firebase-2IJDDBXX.js → firebase-ZGQARUIH.js} +3 -3
- package/dist/{firecrawl-2T3SBUW7.js → firecrawl-2JW7DMTH.js} +3 -3
- package/dist/{firecrawl-IYYXLAZM.js → firecrawl-UURQ5P5N.js} +3 -3
- package/dist/{flyai-QS5Q6FJR.js → flyai-EJGDMYFA.js} +3 -3
- package/dist/{flyai-7FJ4TRAG.js → flyai-ZFMZBBHJ.js} +3 -3
- package/dist/{gitagent-MWI75OIX.js → gitagent-5SDBYFNA.js} +1 -1
- package/dist/{gitagent-YBMWY7NZ.js → gitagent-ODXPCR4X.js} +1 -1
- package/dist/{gitee-3N7OFOM7.js → gitee-5UMJ4BC7.js} +3 -3
- package/dist/{gitee-KVNK6KLZ.js → gitee-D6NAZTCO.js} +3 -3
- package/dist/{github-LUEC2LID.js → github-JBLDKIA3.js} +3 -3
- package/dist/{github-XRO5Z3GC.js → github-PZQAVEZP.js} +3 -3
- package/dist/{google-ads-VPKWTX67.js → google-ads-BIFQOJ5M.js} +3 -3
- package/dist/{google-ads-A3QAJI4D.js → google-ads-QU3LJE4O.js} +3 -3
- package/dist/{google-analytics-C4UR5ZR2.js → google-analytics-7VZ6YZVA.js} +3 -3
- package/dist/{google-analytics-XDYZA2B7.js → google-analytics-HXMPCL5V.js} +3 -3
- package/dist/{google-workspace-YX35SHHX.js → google-workspace-6SEBJ4VA.js} +2 -2
- package/dist/{google-workspace-LL3EWVHH.js → google-workspace-L3AMJLCF.js} +2 -2
- package/dist/{huawei-xiaoyi-KPWLTSHB.js → huawei-xiaoyi-C6QIJMPM.js} +3 -3
- package/dist/{huawei-xiaoyi-6BSMGJHR.js → huawei-xiaoyi-JGLXWU5P.js} +3 -3
- package/dist/{hubspot-FTIEMNZO.js → hubspot-LACJGE6D.js} +3 -3
- package/dist/{hubspot-DIUHGEDI.js → hubspot-XWPRO4KZ.js} +3 -3
- package/dist/{huggingface-UUXK2RHK.js → huggingface-26QQZK4C.js} +3 -3
- package/dist/{huggingface-MJCOXA7E.js → huggingface-CQICNA2R.js} +3 -3
- package/dist/index.d.ts +1338 -1
- package/dist/index.js +1364 -226
- package/dist/{inference-ai-image-generation-PXV6IG4U.js → inference-ai-image-generation-5KYIUWT6.js} +3 -3
- package/dist/{inference-ai-image-generation-CMI6R5T3.js → inference-ai-image-generation-J2NYDCLZ.js} +3 -3
- package/dist/{inference-sh-7AZOLEFI.js → inference-sh-5SWQTK73.js} +3 -3
- package/dist/{inference-sh-ABQOD3YF.js → inference-sh-PTQF6T3R.js} +3 -3
- package/dist/{init.command-WGKN3GC6.js → init.command-C7UKPK2Y.js} +3 -3
- package/dist/{init.command-775GLXTC.js → init.command-UNL66BMR.js} +3 -3
- package/dist/{klaviyo-LDPBWBSS.js → klaviyo-4UNPMBFT.js} +3 -3
- package/dist/{klaviyo-6K5YEFNH.js → klaviyo-SLYNEULT.js} +3 -3
- package/dist/{kuaidi100-HGFM5VK2.js → kuaidi100-HZKV5AIS.js} +3 -3
- package/dist/{kuaidi100-UHPFCVXP.js → kuaidi100-ZQUW7GHH.js} +3 -3
- package/dist/lark-HQUZNHDI.js +382 -0
- package/dist/lark-PAV7XWJS.js +381 -0
- package/dist/{linear-T4ORUP7N.js → linear-PYGQ5SLK.js} +3 -3
- package/dist/{linear-7QFSFPOD.js → linear-VJLYNTUF.js} +3 -3
- package/dist/{lovart-PDUXRUHJ.js → lovart-KC6SVNAJ.js} +3 -3
- package/dist/{lovart-QO3SK55T.js → lovart-WVKY4RR4.js} +3 -3
- package/dist/{meta-ads-SCNFI45S.js → meta-ads-E6XT33GI.js} +3 -3
- package/dist/{meta-ads-V6XPZWX3.js → meta-ads-RJ6DWRYN.js} +3 -3
- package/dist/{miclaw-TPPPS2WN.js → miclaw-4BA3A2YN.js} +3 -3
- package/dist/{miclaw-5CNTW7VV.js → miclaw-LUV6DCHX.js} +3 -3
- package/dist/{model-provider-KFB76XV5.js → model-provider-SYXJZ3JD.js} +1 -1
- package/dist/{model-provider-AVSFJSZP.js → model-provider-U7NEYA3Y.js} +1 -1
- package/dist/nature-skills-G76ABIWZ.js +143 -0
- package/dist/nature-skills-SQHMFXKT.js +142 -0
- package/dist/{notion-WFA7KGZZ.js → notion-2JZAKOFP.js} +1 -1
- package/dist/{notion-FZK76MN2.js → notion-O3NO5TJH.js} +1 -1
- package/dist/{oceanengine-3JZUS3PP.js → oceanengine-D23UZGVB.js} +3 -3
- package/dist/{oceanengine-5BRIJVJE.js → oceanengine-WDK2OXX5.js} +3 -3
- package/dist/{opencli-PFXHGCS2.js → opencli-36P63YNU.js} +3 -3
- package/dist/{opencli-VIGRJTGH.js → opencli-SUHDFR33.js} +3 -3
- package/dist/{paypal-Z5JYHIWD.js → paypal-K27SUW3B.js} +3 -3
- package/dist/{paypal-33UADIPR.js → paypal-OPZ3KOV5.js} +3 -3
- package/dist/{playwright-SQAQ3DZG.js → playwright-2ULT3NIC.js} +3 -3
- package/dist/{playwright-MG5WHK47.js → playwright-3Q7LBILG.js} +3 -3
- package/dist/{plugins-HZBWK3WQ.js → plugins-2MITZ4ZD.js} +2 -2
- package/dist/{plugins-I4GD5SZX.js → plugins-UK2QWD6G.js} +2 -2
- package/dist/{posthog-MU5MAJOQ.js → posthog-E3EHXLAN.js} +3 -3
- package/dist/{posthog-RJRRKDWB.js → posthog-KPJVLGX6.js} +3 -3
- package/dist/{salesforce-34FVIJTG.js → salesforce-FGPNG7FB.js} +3 -3
- package/dist/{salesforce-3QZ6OFVO.js → salesforce-TVHISKBC.js} +3 -3
- package/dist/{sentry-PIWW46VA.js → sentry-BZ3J3MZM.js} +3 -3
- package/dist/{sentry-MCIRMACU.js → sentry-XC57YRAJ.js} +3 -3
- package/dist/{seo-suite-WJXMA3S4.js → seo-suite-2MDEDLAB.js} +3 -3
- package/dist/{seo-suite-4SQ3I67Q.js → seo-suite-U75O3QP6.js} +3 -3
- package/dist/{serve.command-2AHJI665.js → serve.command-G5RVQFUD.js} +3 -3
- package/dist/{serve.command-Q46LHQG6.js → serve.command-PYGDG7K3.js} +3 -3
- package/dist/{shadowob-PRSMI5MW.js → shadowob-3QZ7DLDW.js} +158 -31
- package/dist/{shadowob-JELOWHWX.js → shadowob-CJLOEKFP.js} +158 -31
- package/dist/{sherlock-2PKY2E2Y.js → sherlock-CQFUHKDH.js} +3 -3
- package/dist/{sherlock-C5ZWPPVT.js → sherlock-DONK2I6E.js} +3 -3
- package/dist/{shopify-GL3NFVGE.js → shopify-NO5GI3WD.js} +3 -3
- package/dist/{shopify-R4G3UXM6.js → shopify-VW2KLKH5.js} +3 -3
- package/dist/{skill-discovery-YPXXV622.js → skill-discovery-6JEPPKKM.js} +3 -3
- package/dist/{skill-discovery-7INAUP4D.js → skill-discovery-PWRAVGIS.js} +3 -3
- package/dist/skills/shadowob-cli/SKILL.md +33 -22
- package/dist/{stripe-LJNPQ3CQ.js → stripe-HCNCKG4C.js} +1 -1
- package/dist/{stripe-C22RR4ZS.js → stripe-IU3KTJ4H.js} +1 -1
- package/dist/{supabase-IRNQ54FJ.js → supabase-KRL7JW2D.js} +3 -3
- package/dist/{supabase-N4ONFJNQ.js → supabase-TYEBTZNO.js} +3 -3
- package/dist/{taobao-aipaas-LRR4GMO3.js → taobao-aipaas-PEUIDOYP.js} +3 -3
- package/dist/{taobao-aipaas-RVKORSF4.js → taobao-aipaas-SA5E4MZA.js} +3 -3
- package/dist/{tapd-TMQRSMFG.js → tapd-6DDIUPVQ.js} +3 -3
- package/dist/{tapd-3JPVJ7XH.js → tapd-OTYLSZGY.js} +3 -3
- package/dist/{tencent-ads-UHC6OPBV.js → tencent-ads-OW2TAMH5.js} +3 -3
- package/dist/{tencent-ads-IGD33LO7.js → tencent-ads-XTQZ27YT.js} +3 -3
- package/dist/{tencent-docs-C3A4J3CJ.js → tencent-docs-6D6A2VCO.js} +3 -3
- package/dist/{tencent-docs-O2SC4FHL.js → tencent-docs-K3TMUIWD.js} +3 -3
- package/dist/{tencent-maps-OQOKHVW2.js → tencent-maps-IZYWITJZ.js} +3 -3
- package/dist/{tencent-maps-HMMWMNF4.js → tencent-maps-SWI7CLQY.js} +3 -3
- package/dist/text-to-cad-B2UP6PKA.js +192 -0
- package/dist/text-to-cad-I4B6VBFV.js +193 -0
- package/dist/{vercel-KOXDDTHX.js → vercel-CCKRC76D.js} +3 -3
- package/dist/{vercel-OLNVDWMG.js → vercel-SBGEMIJJ.js} +3 -3
- package/dist/{webflow-OMJKZM54.js → webflow-C3EHNNSN.js} +3 -3
- package/dist/{webflow-FULU5Q2I.js → webflow-ZFBJH4CR.js} +3 -3
- package/dist/{wechat-miniprogram-skyline-KYCDMQNW.js → wechat-miniprogram-skyline-VNCRERHX.js} +3 -3
- package/dist/{wechat-miniprogram-skyline-VR4FVIQL.js → wechat-miniprogram-skyline-Z5JQUV5Q.js} +3 -3
- package/dist/{wechat-pay-BCMAJ6UW.js → wechat-pay-RPDKPUEB.js} +3 -3
- package/dist/{wechat-pay-YQQKXVUI.js → wechat-pay-XVDGJRF2.js} +3 -3
- package/dist/{wonda-NGWIORYN.js → wonda-EL2P44S7.js} +3 -3
- package/dist/{wonda-RBABXFNM.js → wonda-XK5JK4X3.js} +3 -3
- package/dist/{wordpress-woocommerce-RNA5HB3N.js → wordpress-woocommerce-4MEE5A2M.js} +3 -3
- package/dist/{wordpress-woocommerce-RDIUTHYT.js → wordpress-woocommerce-6ECNM2QU.js} +3 -3
- package/dist/{wps-LUWHMZQQ.js → wps-E4OZEMOF.js} +3 -3
- package/dist/{wps-DAEFQHDE.js → wps-JNQUC4JS.js} +3 -3
- package/dist/{yuque-HCHTJWNI.js → yuque-GLAAOS7X.js} +3 -3
- package/dist/{yuque-KRH5O74J.js → yuque-MEF6VFLJ.js} +3 -3
- package/images/RUNNERS.md +15 -0
- package/images/cc-connect-runner/entrypoint.mjs +228 -0
- package/images/claude-runner/RUNNER.md +5 -3
- package/images/codex-runner/RUNNER.md +5 -3
- package/images/gemini-runner/RUNNER.md +5 -2
- package/images/hermes-runner/RUNNER.md +4 -2
- package/images/hermes-runner/entrypoint.mjs +269 -1
- package/images/openclaw-runner/Dockerfile +1 -0
- package/images/openclaw-runner/RUNNER.md +3 -0
- package/images/openclaw-runner/entrypoint.mjs +249 -1
- package/images/openclaw-runner/warm-runtime-deps.mjs +1 -3
- package/images/opencode-runner/RUNNER.md +5 -3
- package/package.json +3 -3
- package/templates/agent-marketplace-buddy.template.json +4 -1
- package/templates/bmad-method-buddy.template.json +4 -1
- package/templates/code-trainer.template.json +331 -0
- package/templates/gstack-buddy.template.json +4 -1
- package/templates/little-match-girl.template.json +10 -3
- package/dist/console/static/css/index.7f91f806.css +0 -1
- package/dist/console/static/js/index.4487e1ff.js +0 -1
- package/dist/lark-6LNA3LUQ.js +0 -103
- package/dist/lark-URVBZNS4.js +0 -102
package/dist/index.js
CHANGED
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createConsoleCommand
|
|
3
|
-
} from "./chunk-HRTBOZ7O.js";
|
|
4
|
-
import {
|
|
5
|
-
createInitCommand
|
|
6
|
-
} from "./chunk-2FWJSE6Z.js";
|
|
7
|
-
import {
|
|
8
|
-
formatProvisionState,
|
|
9
|
-
loadProvisionState,
|
|
10
|
-
mergeProvisionState,
|
|
11
|
-
saveProvisionState
|
|
12
|
-
} from "./chunk-7VMRQ7MG.js";
|
|
13
1
|
import {
|
|
14
2
|
CLOUD_SAAS_RUNTIME_KEY,
|
|
15
3
|
attachCloudSaasProvisionState,
|
|
@@ -26,7 +14,13 @@ import {
|
|
|
26
14
|
toProviderSecretEnvKey,
|
|
27
15
|
validateCloudSaasConfigSnapshot,
|
|
28
16
|
withLegacyEnvAliases
|
|
29
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-X2SREECR.js";
|
|
18
|
+
import {
|
|
19
|
+
createConsoleCommand
|
|
20
|
+
} from "./chunk-PYJRFKPN.js";
|
|
21
|
+
import {
|
|
22
|
+
createInitCommand
|
|
23
|
+
} from "./chunk-EVV774KS.js";
|
|
30
24
|
import {
|
|
31
25
|
RUNNER_AGENTS_VOLUME_NAME,
|
|
32
26
|
RUNNER_CONFIG_MOUNT_PATH,
|
|
@@ -39,6 +33,7 @@ import {
|
|
|
39
33
|
applyRuntimeEnvRefPolicy,
|
|
40
34
|
buildOpenClawConfig,
|
|
41
35
|
collectPluginBuildEnvVars,
|
|
36
|
+
collectPluginRuntimeEnvOmitKeys,
|
|
42
37
|
collectPluginRuntimeExtensions,
|
|
43
38
|
collectRuntimeEnvFields,
|
|
44
39
|
collectRuntimeEnvRefPolicy,
|
|
@@ -56,11 +51,17 @@ import {
|
|
|
56
51
|
runtimeContextEnv,
|
|
57
52
|
runtimeStatePvcName,
|
|
58
53
|
validateCloudConfig
|
|
59
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-P5Y6F2NH.js";
|
|
60
55
|
import {
|
|
61
56
|
deepMerge,
|
|
62
57
|
getPluginRegistry
|
|
63
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-OL5VH6RN.js";
|
|
59
|
+
import {
|
|
60
|
+
formatProvisionState,
|
|
61
|
+
loadProvisionState,
|
|
62
|
+
mergeProvisionState,
|
|
63
|
+
saveProvisionState
|
|
64
|
+
} from "./chunk-7VMRQ7MG.js";
|
|
64
65
|
import {
|
|
65
66
|
log
|
|
66
67
|
} from "./chunk-HUICDC56.js";
|
|
@@ -206,9 +207,9 @@ var RAW_PLUGIN_LIBRARY = String.raw`[
|
|
|
206
207
|
"type": "array",
|
|
207
208
|
"description": "Binding rules connecting buddies to agents and channels"
|
|
208
209
|
},
|
|
209
|
-
"
|
|
210
|
+
"greeting": {
|
|
210
211
|
"type": "object",
|
|
211
|
-
"description": "
|
|
212
|
+
"description": "Greeting and entry-channel behavior for provisioned Shadow spaces"
|
|
212
213
|
}
|
|
213
214
|
}
|
|
214
215
|
},
|
|
@@ -247,7 +248,7 @@ var RAW_PLUGIN_LIBRARY = String.raw`[
|
|
|
247
248
|
{
|
|
248
249
|
"id": "lark",
|
|
249
250
|
"name": "Lark / Feishu",
|
|
250
|
-
"description": "Lark and
|
|
251
|
+
"description": "Lark, Feishu, and Meegle workspace operations for messages, docs, Base, sheets, calendar, mail, tasks, meetings, approvals, projects, and weekly execution workflows.",
|
|
251
252
|
"version": "1.0.0",
|
|
252
253
|
"category": "communication",
|
|
253
254
|
"capabilities": [
|
|
@@ -255,18 +256,19 @@ var RAW_PLUGIN_LIBRARY = String.raw`[
|
|
|
255
256
|
"data-source",
|
|
256
257
|
"action",
|
|
257
258
|
"cli",
|
|
258
|
-
"mcp",
|
|
259
259
|
"skill"
|
|
260
260
|
],
|
|
261
261
|
"tags": [
|
|
262
262
|
"lark",
|
|
263
263
|
"feishu",
|
|
264
|
+
"meegle",
|
|
264
265
|
"docs",
|
|
265
266
|
"base",
|
|
266
267
|
"calendar",
|
|
267
268
|
"messenger",
|
|
269
|
+
"projects",
|
|
268
270
|
"skills",
|
|
269
|
-
"
|
|
271
|
+
"cli"
|
|
270
272
|
],
|
|
271
273
|
"authType": "api-key",
|
|
272
274
|
"website": "https://open.feishu.cn",
|
|
@@ -275,7 +277,7 @@ var RAW_PLUGIN_LIBRARY = String.raw`[
|
|
|
275
277
|
"manifest": {
|
|
276
278
|
"id": "lark",
|
|
277
279
|
"name": "Lark / Feishu",
|
|
278
|
-
"description": "Lark and
|
|
280
|
+
"description": "Lark, Feishu, and Meegle workspace operations for messages, docs, Base, sheets, calendar, mail, tasks, meetings, approvals, projects, and weekly execution workflows.",
|
|
279
281
|
"version": "1.0.0",
|
|
280
282
|
"category": "communication",
|
|
281
283
|
"icon": "messages-square",
|
|
@@ -287,20 +289,20 @@ var RAW_PLUGIN_LIBRARY = String.raw`[
|
|
|
287
289
|
{
|
|
288
290
|
"key": "LARKSUITE_CLI_APP_ID",
|
|
289
291
|
"label": "App ID",
|
|
290
|
-
"description": "Feishu or Lark app ID used by lark-cli
|
|
292
|
+
"description": "Feishu or Lark app ID used by lark-cli. Get it from the app console.",
|
|
291
293
|
"required": true,
|
|
292
294
|
"sensitive": false,
|
|
293
295
|
"placeholder": "cli_xxx",
|
|
294
|
-
"helpUrl": "https://open.feishu.cn/
|
|
296
|
+
"helpUrl": "https://open.feishu.cn/app"
|
|
295
297
|
},
|
|
296
298
|
{
|
|
297
299
|
"key": "LARKSUITE_CLI_APP_SECRET",
|
|
298
300
|
"label": "App secret",
|
|
299
|
-
"description": "App secret from the Feishu or Lark
|
|
301
|
+
"description": "App secret from the Feishu or Lark app console. Cloud writes this into lark-cli config.json instead of raw app env vars so bot CLI calls can mint tenant tokens correctly.",
|
|
300
302
|
"required": true,
|
|
301
303
|
"sensitive": true,
|
|
302
304
|
"placeholder": "App secret",
|
|
303
|
-
"helpUrl": "https://open.feishu.cn/
|
|
305
|
+
"helpUrl": "https://open.feishu.cn/app"
|
|
304
306
|
},
|
|
305
307
|
{
|
|
306
308
|
"key": "LARKSUITE_CLI_BRAND",
|
|
@@ -308,7 +310,60 @@ var RAW_PLUGIN_LIBRARY = String.raw`[
|
|
|
308
310
|
"description": "Use feishu for China tenants or lark for global tenants.",
|
|
309
311
|
"required": false,
|
|
310
312
|
"sensitive": false,
|
|
311
|
-
"placeholder": "feishu"
|
|
313
|
+
"placeholder": "feishu",
|
|
314
|
+
"helpUrl": "https://open.feishu.cn/app"
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
"key": "LARKSUITE_CLI_DEFAULT_AS",
|
|
318
|
+
"label": "Default identity",
|
|
319
|
+
"description": "Default lark-cli identity: bot, user, or auto. Defaults to bot.",
|
|
320
|
+
"required": false,
|
|
321
|
+
"sensitive": false,
|
|
322
|
+
"placeholder": "bot"
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
"key": "LARKSUITE_CLI_STRICT_MODE",
|
|
326
|
+
"label": "Strict mode",
|
|
327
|
+
"description": "Restrict lark-cli to bot, user, or off. Defaults to bot.",
|
|
328
|
+
"required": false,
|
|
329
|
+
"sensitive": false,
|
|
330
|
+
"placeholder": "bot"
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
"key": "MEEGLE_HOST",
|
|
334
|
+
"label": "Meegle host",
|
|
335
|
+
"description": "Meegle site domain, such as project.feishu.cn, meegle.com, or a tenant host.",
|
|
336
|
+
"required": false,
|
|
337
|
+
"sensitive": false,
|
|
338
|
+
"placeholder": "project.feishu.cn",
|
|
339
|
+
"helpUrl": "https://github.com/larksuite/meegle-cli#configuration"
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
"key": "MEEGLE_USER_ACCESS_TOKEN",
|
|
343
|
+
"label": "Meegle user access token",
|
|
344
|
+
"description": "Optional Meegle user access token for direct CLI auth. Rotate this token when Meegle returns 401.",
|
|
345
|
+
"required": false,
|
|
346
|
+
"sensitive": true,
|
|
347
|
+
"placeholder": "u-...",
|
|
348
|
+
"helpUrl": "https://github.com/larksuite/meegle-cli#configuration"
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
"key": "MEEGLE_ACCESS_TOKEN_HEADER",
|
|
352
|
+
"label": "Meegle token header",
|
|
353
|
+
"description": "Optional custom Meegle token header. Empty uses Authorization: Bearer <token>.",
|
|
354
|
+
"required": false,
|
|
355
|
+
"sensitive": false,
|
|
356
|
+
"placeholder": "x-meegle-auth",
|
|
357
|
+
"helpUrl": "https://github.com/larksuite/meegle-cli#configuration"
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
"key": "MEEGLE_USER_AGENT",
|
|
361
|
+
"label": "Meegle user agent suffix",
|
|
362
|
+
"description": "Optional caller suffix appended to the Meegle CLI User-Agent.",
|
|
363
|
+
"required": false,
|
|
364
|
+
"sensitive": false,
|
|
365
|
+
"placeholder": "shadow-cloud",
|
|
366
|
+
"helpUrl": "https://github.com/larksuite/meegle-cli#configuration"
|
|
312
367
|
}
|
|
313
368
|
]
|
|
314
369
|
},
|
|
@@ -317,18 +372,19 @@ var RAW_PLUGIN_LIBRARY = String.raw`[
|
|
|
317
372
|
"data-source",
|
|
318
373
|
"action",
|
|
319
374
|
"cli",
|
|
320
|
-
"mcp",
|
|
321
375
|
"skill"
|
|
322
376
|
],
|
|
323
377
|
"tags": [
|
|
324
378
|
"lark",
|
|
325
379
|
"feishu",
|
|
380
|
+
"meegle",
|
|
326
381
|
"docs",
|
|
327
382
|
"base",
|
|
328
383
|
"calendar",
|
|
329
384
|
"messenger",
|
|
385
|
+
"projects",
|
|
330
386
|
"skills",
|
|
331
|
-
"
|
|
387
|
+
"cli"
|
|
332
388
|
],
|
|
333
389
|
"popularity": 99
|
|
334
390
|
},
|
|
@@ -336,28 +392,32 @@ var RAW_PLUGIN_LIBRARY = String.raw`[
|
|
|
336
392
|
{
|
|
337
393
|
"key": "LARKSUITE_CLI_APP_ID",
|
|
338
394
|
"label": "App ID",
|
|
339
|
-
"description": "Feishu or Lark app ID used by lark-cli
|
|
395
|
+
"description": "Feishu or Lark app ID used by lark-cli. Get it from the app console.",
|
|
340
396
|
"sensitive": false
|
|
341
397
|
},
|
|
342
398
|
{
|
|
343
399
|
"key": "LARKSUITE_CLI_APP_SECRET",
|
|
344
400
|
"label": "App secret",
|
|
345
|
-
"description": "App secret from the Feishu or Lark
|
|
401
|
+
"description": "App secret from the Feishu or Lark app console. Cloud writes this into lark-cli config.json instead of raw app env vars so bot CLI calls can mint tenant tokens correctly.",
|
|
346
402
|
"sensitive": true
|
|
347
403
|
}
|
|
348
404
|
],
|
|
349
405
|
"readme": {
|
|
350
406
|
"title": "Lark / Feishu Plugin",
|
|
351
|
-
"excerpt": "Lark / Feishu Plugin\n\nLark / Feishu connects a Buddy to workspace operations across messages, Docs, Base, Sheets, Calendar, Mail, Tasks, Meetings, approvals, and weekly execution workflows.\n\nConfiguration Keys\n\n Key Required Sensitive Description \n \n LARKSUITE CLI APP ID Yes No Feishu or Lark app ID used by lark cli
|
|
407
|
+
"excerpt": "Lark / Feishu Plugin\n\nLark / Feishu connects a Buddy to workspace operations across messages, Docs, Base, Sheets, Calendar, Mail, Tasks, Meetings, approvals, Meegle work items, and weekly execution workflows.\n\nConfiguration Keys\n\n Key Required Sensitive Description \n \n LARKSUITE CLI APP ID Yes No Feishu or Lark app ID used by lark cli. \n LARKSUITE CLI APP SECRET Yes Yes App secret from the Feishu or Lark developer console. \n LARKSUITE CLI BRAND No No Use feishu for China tenants or lark for global tenants. Defaults to feishu. \n LARKSUITE CLI DEFAULT AS No No Default lark cli identity: bot, user, or auto. Defaults to bot. \n LARKSUITE CLI STRICT MODE No No Restrict lark cli to bot, user, or off. Defaults to bot. \n MEEGLE HOST No No Meegle site domain, such as project.feishu.cn, meegle.com, or a tenant host. \n MEEGLE USER ACCESS TOKEN No Yes Optional Meegle user access token for direct CLI auth. \n MEEGLE ACCESS TOKEN HEADER No No Optional custom Meegle token header. Empty uses Authorization: Bearer <token . \n MEEGLE USER AGENT No No Optional caller suffix appended to the Meegle CLI User Agent. \n\nCredential Source URLs\n\n Environment variable Where to get it \n \n LARKSUITE CLI APP ID Feishu China app console: <https://open.feishu.cn/app \n LARKSUITE CLI APP SECRET Feishu China app console: <https://open.feishu.cn/app \n LARKSUITE CLI BRAND Use feishu for apps from <https://open.feishu.cn/app ; use lark for global Lark tenants. \n MEEGLE HOST The Meegle/Lark Project tenant host, for example <https://project.feishu.cn . \n MEEGLE USER ACCESS TOKEN Meegle CLI direct env token configurat",
|
|
352
408
|
"headings": [
|
|
353
409
|
"Lark / Feishu Plugin",
|
|
354
410
|
"Configuration Keys",
|
|
411
|
+
"Credential Source URLs",
|
|
355
412
|
"Setup",
|
|
413
|
+
"Lark CLI Credential Model",
|
|
414
|
+
"Knowledge Base Access",
|
|
415
|
+
"Local Plugin Test",
|
|
356
416
|
"Runtime Assets",
|
|
357
417
|
"References"
|
|
358
418
|
]
|
|
359
419
|
},
|
|
360
|
-
"searchText": "lark\nlark / feishu\nlark and
|
|
420
|
+
"searchText": "lark\nlark / feishu\nlark, feishu, and meegle workspace operations for messages, docs, base, sheets, calendar, mail, tasks, meetings, approvals, projects, and weekly execution workflows.\ncommunication\ntool data-source action cli skill\nlark feishu meegle docs base calendar messenger projects skills cli\nlarksuite_cli_app_id app id feishu or lark app id used by lark-cli. get it from the app console. larksuite_cli_app_secret app secret app secret from the feishu or lark app console. cloud writes this into lark-cli config.json instead of raw app env vars so bot cli calls can mint tenant tokens correctly. larksuite_cli_brand workspace brand use feishu for china tenants or lark for global tenants. larksuite_cli_default_as default identity default lark-cli identity: bot, user, or auto. defaults to bot. larksuite_cli_strict_mode strict mode restrict lark-cli to bot, user, or off. defaults to bot. meegle_host meegle host meegle site domain, such as project.feishu.cn, meegle.com, or a tenant host. meegle_user_access_token meegle user access token optional meegle user access token for direct cli auth. rotate this token when meegle returns 401. meegle_access_token_header meegle token header optional custom meegle token header. empty uses authorization: bearer <token>. meegle_user_agent meegle user agent suffix optional caller suffix appended to the meegle cli user-agent.\nlark / feishu plugin\nlark / feishu plugin configuration keys credential source urls setup lark cli credential model knowledge base access local plugin test runtime assets references\nlark / feishu plugin\n\nlark / feishu connects a buddy to workspace operations across messages, docs, base, sheets, calendar, mail, tasks, meetings, approvals, meegle work items, and weekly execution workflows.\n\nconfiguration keys\n\n key required sensitive description \n \n larksuite cli app id yes no feishu or lark app id used by lark cli. \n larksuite cli app secret yes yes app secret from the feishu or lark developer console. \n larksuite cli brand no no use feishu for china tenants or lark for global tenants. defaults to feishu. \n larksuite cli default as no no default lark cli identity: bot, user, or auto. defaults to bot. \n larksuite cli strict mode no no restrict lark cli to bot, user, or off. defaults to bot. \n meegle host no no meegle site domain, such as project.feishu.cn, meegle.com, or a tenant host. \n meegle user access token no yes optional meegle user access token for direct cli auth. \n meegle access token header no no optional custom meegle token header. empty uses authorization: bearer <token . \n meegle user agent no no optional caller suffix appended to the meegle cli user agent. \n\ncredential source urls\n\n environment variable where to get it \n \n larksuite cli app id feishu china app console: <https://open.feishu.cn/app \n larksuite cli app secret feishu china app console: <https://open.feishu.cn/app \n larksuite cli brand use feishu for apps from <https://open.feishu.cn/app ; use lark for global lark tenants. \n meegle host the meegle/lark project tenant host, for example <https://project.feishu.cn . \n meegle user access token meegle cli direct env token configurat"
|
|
361
421
|
},
|
|
362
422
|
{
|
|
363
423
|
"id": "figma",
|
|
@@ -4206,6 +4266,79 @@ var RAW_PLUGIN_LIBRARY = String.raw`[
|
|
|
4206
4266
|
},
|
|
4207
4267
|
"searchText": "inference-sh\ninference.sh\ninference.sh lets buddies run cloud ai apps for images, videos, llms, search, audio, 3d, and automation through the belt cli.\nautomation\ntool data-source action cli skill\ninference-sh belt ai-apps image video llm search skills\ninfsh_api_key inference.sh api key api key used by the belt cli.\ninference.sh plugin\ninference.sh plugin configuration keys setup runtime assets references\ninference.sh plugin\n\ninference.sh lets a buddy run cloud ai apps for images, videos, llms, search, audio, 3d, and automation through the belt cli.\n\nconfiguration keys\n\n key required sensitive description \n \n infsh api key yes yes api key used by the belt cli. \n\nsetup\n\n1. sign in to inference.sh.\n2. create or copy an api key from your inference.sh account settings.\n3. add the key as infsh api key in the deployment form or secret group.\n4. deploy the buddy.\n5. run the verification check to confirm belt help works inside the runtime.\n\nruntime assets\n\n installs system prerequisites for the official installer.\n installs the belt cli with https://cli.inference.sh.\n mounts the infsh cli skill from infsh skills/skills.\n\nreferences\n\n inference.sh skills\n inference.sh cli setup\n inference.sh authentication\n infsh skills repository"
|
|
4208
4268
|
},
|
|
4269
|
+
{
|
|
4270
|
+
"id": "text-to-cad",
|
|
4271
|
+
"name": "CAD Skills",
|
|
4272
|
+
"description": "CAD Skills mounts text-to-cad workflows for STEP-first CAD generation, inspection, rendering, robot descriptions, standard parts, and fabrication preflight.",
|
|
4273
|
+
"version": "1.0.0",
|
|
4274
|
+
"category": "automation",
|
|
4275
|
+
"capabilities": [
|
|
4276
|
+
"tool",
|
|
4277
|
+
"data-source",
|
|
4278
|
+
"action",
|
|
4279
|
+
"cli",
|
|
4280
|
+
"skill"
|
|
4281
|
+
],
|
|
4282
|
+
"tags": [
|
|
4283
|
+
"cad",
|
|
4284
|
+
"step",
|
|
4285
|
+
"stl",
|
|
4286
|
+
"dxf",
|
|
4287
|
+
"robotics",
|
|
4288
|
+
"urdf",
|
|
4289
|
+
"sdf",
|
|
4290
|
+
"srdf",
|
|
4291
|
+
"hardware"
|
|
4292
|
+
],
|
|
4293
|
+
"authType": "none",
|
|
4294
|
+
"website": "https://www.cadskills.xyz",
|
|
4295
|
+
"docs": "https://github.com/earthtojake/text-to-cad",
|
|
4296
|
+
"popularity": 88,
|
|
4297
|
+
"manifest": {
|
|
4298
|
+
"id": "text-to-cad",
|
|
4299
|
+
"name": "CAD Skills",
|
|
4300
|
+
"description": "CAD Skills mounts text-to-cad workflows for STEP-first CAD generation, inspection, rendering, robot descriptions, standard parts, and fabrication preflight.",
|
|
4301
|
+
"version": "1.0.0",
|
|
4302
|
+
"category": "automation",
|
|
4303
|
+
"icon": "boxes",
|
|
4304
|
+
"website": "https://www.cadskills.xyz",
|
|
4305
|
+
"docs": "https://github.com/earthtojake/text-to-cad",
|
|
4306
|
+
"auth": {
|
|
4307
|
+
"type": "none",
|
|
4308
|
+
"fields": []
|
|
4309
|
+
},
|
|
4310
|
+
"capabilities": [
|
|
4311
|
+
"tool",
|
|
4312
|
+
"data-source",
|
|
4313
|
+
"action",
|
|
4314
|
+
"cli",
|
|
4315
|
+
"skill"
|
|
4316
|
+
],
|
|
4317
|
+
"tags": [
|
|
4318
|
+
"cad",
|
|
4319
|
+
"step",
|
|
4320
|
+
"stl",
|
|
4321
|
+
"dxf",
|
|
4322
|
+
"robotics",
|
|
4323
|
+
"urdf",
|
|
4324
|
+
"sdf",
|
|
4325
|
+
"srdf",
|
|
4326
|
+
"hardware"
|
|
4327
|
+
],
|
|
4328
|
+
"popularity": 88
|
|
4329
|
+
},
|
|
4330
|
+
"requiredFields": [],
|
|
4331
|
+
"readme": {
|
|
4332
|
+
"title": "CAD Skills",
|
|
4333
|
+
"excerpt": "CAD Skills\n\nMounts the earthtojake/text to cad agent skills for CAD, robotics, and\nhardware design workflows.\n\nRuntime\n\n Pulls the repository skills/ tree into\n /workspace/.agents/plugin skills/text to cad.\n Installs Python CAD dependencies into the plugin runtime dependency volume\n from a Debian based Node init image so binary Python wheels are available.\n Installs the CAD Explorer viewer dependencies after the skills have been\n copied into the init container staging volume.\n Patches the bundled step.parts downloader to use the currently live\n https://www.step.parts/v1 API origin.\n Exposes cad step, cad inspect, and cad dxf wrappers on PATH.\n\nNotes\n\nThe upstream repository excludes LFS heavy benchmark assets from normal use.\nThis plugin mounts only the skill tree and keeps CAD outputs in the active\nworkspace as ordinary derived artifacts.",
|
|
4334
|
+
"headings": [
|
|
4335
|
+
"CAD Skills",
|
|
4336
|
+
"Runtime",
|
|
4337
|
+
"Notes"
|
|
4338
|
+
]
|
|
4339
|
+
},
|
|
4340
|
+
"searchText": "text-to-cad\ncad skills\ncad skills mounts text-to-cad workflows for step-first cad generation, inspection, rendering, robot descriptions, standard parts, and fabrication preflight.\nautomation\ntool data-source action cli skill\ncad step stl dxf robotics urdf sdf srdf hardware\n\ncad skills\ncad skills runtime notes\ncad skills\n\nmounts the earthtojake/text to cad agent skills for cad, robotics, and\nhardware design workflows.\n\nruntime\n\n pulls the repository skills/ tree into\n /workspace/.agents/plugin skills/text to cad.\n installs python cad dependencies into the plugin runtime dependency volume\n from a debian based node init image so binary python wheels are available.\n installs the cad explorer viewer dependencies after the skills have been\n copied into the init container staging volume.\n patches the bundled step.parts downloader to use the currently live\n https://www.step.parts/v1 api origin.\n exposes cad step, cad inspect, and cad dxf wrappers on path.\n\nnotes\n\nthe upstream repository excludes lfs heavy benchmark assets from normal use.\nthis plugin mounts only the skill tree and keeps cad outputs in the active\nworkspace as ordinary derived artifacts."
|
|
4341
|
+
},
|
|
4209
4342
|
{
|
|
4210
4343
|
"id": "claude-plugin",
|
|
4211
4344
|
"name": "Claude Plugin Importer",
|
|
@@ -6387,6 +6520,107 @@ var RAW_PLUGIN_LIBRARY = String.raw`[
|
|
|
6387
6520
|
},
|
|
6388
6521
|
"searchText": "vercel\nvercel\nvercel operations for deployments, domains, logs, project settings, environment variables, and next.js production diagnostics.\ndevops\ntool data-source action cli mcp skill\nvercel deployments nextjs logs domains mcp\nvercel_token vercel token vercel access token. vercel_team_id team id default vercel team id. vercel_project_id project id default vercel project id.\nvercel plugin\nvercel plugin configuration keys setup runtime assets references\nvercel plugin\n\nvercel launchops supports preview deploys, deployment logs, domain configuration, project settings, environment variables, and next.js production diagnostics.\n\nconfiguration keys\n\n key required sensitive description \n \n vercel token yes yes vercel access token for project and deployment operations. \n vercel team id no no optional default vercel team id. \n vercel project id no no optional default vercel project id. \n\nsetup\n\n1. open vercel account settings.\n2. create an access token.\n3. add the token as vercel token.\n4. copy vercel team id from team settings if the buddy should operate under a team.\n5. copy vercel project id from project settings if the buddy should default to one project.\n6. deploy the buddy and verify vercel version.\n\nruntime assets\n\n installs the vercel cli.\n registers hosted vercel mcp metadata.\n\nreferences\n\n vercel agent resources\n vercel mcp\n vercel cli\n creating a vercel access token"
|
|
6389
6522
|
},
|
|
6523
|
+
{
|
|
6524
|
+
"id": "nature-skills",
|
|
6525
|
+
"name": "Nature Skills",
|
|
6526
|
+
"description": "Nature Skills mounts academic writing, polishing, citation, paper reading, figure, reviewer response, paper-to-PPT, data availability, and literature search workflows.",
|
|
6527
|
+
"version": "1.0.0",
|
|
6528
|
+
"category": "productivity",
|
|
6529
|
+
"capabilities": [
|
|
6530
|
+
"tool",
|
|
6531
|
+
"data-source",
|
|
6532
|
+
"action",
|
|
6533
|
+
"cli",
|
|
6534
|
+
"mcp",
|
|
6535
|
+
"skill"
|
|
6536
|
+
],
|
|
6537
|
+
"tags": [
|
|
6538
|
+
"nature",
|
|
6539
|
+
"academic-writing",
|
|
6540
|
+
"paper-reading",
|
|
6541
|
+
"citation",
|
|
6542
|
+
"figures",
|
|
6543
|
+
"literature-search",
|
|
6544
|
+
"review-response",
|
|
6545
|
+
"ppt"
|
|
6546
|
+
],
|
|
6547
|
+
"authType": "none",
|
|
6548
|
+
"website": "https://github.com/Yuan1z0825/nature-skills",
|
|
6549
|
+
"docs": "https://github.com/Yuan1z0825/nature-skills",
|
|
6550
|
+
"popularity": 84,
|
|
6551
|
+
"manifest": {
|
|
6552
|
+
"id": "nature-skills",
|
|
6553
|
+
"name": "Nature Skills",
|
|
6554
|
+
"description": "Nature Skills mounts academic writing, polishing, citation, paper reading, figure, reviewer response, paper-to-PPT, data availability, and literature search workflows.",
|
|
6555
|
+
"version": "1.0.0",
|
|
6556
|
+
"category": "productivity",
|
|
6557
|
+
"icon": "microscope",
|
|
6558
|
+
"website": "https://github.com/Yuan1z0825/nature-skills",
|
|
6559
|
+
"docs": "https://github.com/Yuan1z0825/nature-skills",
|
|
6560
|
+
"auth": {
|
|
6561
|
+
"type": "none",
|
|
6562
|
+
"fields": [
|
|
6563
|
+
{
|
|
6564
|
+
"key": "PUBMED_EMAIL",
|
|
6565
|
+
"label": "PubMed email",
|
|
6566
|
+
"description": "Optional email used by NCBI E-utilities for academic search workflows.",
|
|
6567
|
+
"required": false,
|
|
6568
|
+
"sensitive": false,
|
|
6569
|
+
"placeholder": "researcher@example.com",
|
|
6570
|
+
"helpUrl": "https://www.ncbi.nlm.nih.gov/books/NBK25497/"
|
|
6571
|
+
},
|
|
6572
|
+
{
|
|
6573
|
+
"key": "NCBI_API_KEY",
|
|
6574
|
+
"label": "NCBI API key",
|
|
6575
|
+
"description": "Optional NCBI API key for higher PubMed rate limits.",
|
|
6576
|
+
"required": false,
|
|
6577
|
+
"sensitive": true,
|
|
6578
|
+
"placeholder": "ncbi_...",
|
|
6579
|
+
"helpUrl": "https://www.ncbi.nlm.nih.gov/account/settings/"
|
|
6580
|
+
},
|
|
6581
|
+
{
|
|
6582
|
+
"key": "SEMANTIC_SCHOLAR_API_KEY",
|
|
6583
|
+
"label": "Semantic Scholar API key",
|
|
6584
|
+
"description": "Optional Semantic Scholar API key for literature search workflows.",
|
|
6585
|
+
"required": false,
|
|
6586
|
+
"sensitive": true,
|
|
6587
|
+
"placeholder": "sk-...",
|
|
6588
|
+
"helpUrl": "https://api.semanticscholar.org/"
|
|
6589
|
+
}
|
|
6590
|
+
]
|
|
6591
|
+
},
|
|
6592
|
+
"capabilities": [
|
|
6593
|
+
"tool",
|
|
6594
|
+
"data-source",
|
|
6595
|
+
"action",
|
|
6596
|
+
"cli",
|
|
6597
|
+
"mcp",
|
|
6598
|
+
"skill"
|
|
6599
|
+
],
|
|
6600
|
+
"tags": [
|
|
6601
|
+
"nature",
|
|
6602
|
+
"academic-writing",
|
|
6603
|
+
"paper-reading",
|
|
6604
|
+
"citation",
|
|
6605
|
+
"figures",
|
|
6606
|
+
"literature-search",
|
|
6607
|
+
"review-response",
|
|
6608
|
+
"ppt"
|
|
6609
|
+
],
|
|
6610
|
+
"popularity": 84
|
|
6611
|
+
},
|
|
6612
|
+
"requiredFields": [],
|
|
6613
|
+
"readme": {
|
|
6614
|
+
"title": "Nature Skills",
|
|
6615
|
+
"excerpt": "Nature Skills\n\nMounts Yuan1z0825/nature skills into Cloud agents as a bundled academic\nresearch and publication workflow plugin.\n\nRuntime\n\n Pulls all skills/nature folders into\n /workspace/.agents/plugin skills/nature skills.\n Installs the Python dependencies needed by the bundled academic search MCP\n server into the plugin runtime dependency volume from a Debian based Node init\n image.\n Registers the nature academic search stdio MCP server.\n\nOptional Credentials\n\n PUBMED EMAIL improves compliance with NCBI E utilities guidance.\n NCBI API KEY raises PubMed rate limits.\n SEMANTIC SCHOLAR API KEY can improve Semantic Scholar search throughput.\n\nThe non search skills can be used without credentials.",
|
|
6616
|
+
"headings": [
|
|
6617
|
+
"Nature Skills",
|
|
6618
|
+
"Runtime",
|
|
6619
|
+
"Optional Credentials"
|
|
6620
|
+
]
|
|
6621
|
+
},
|
|
6622
|
+
"searchText": "nature-skills\nnature skills\nnature skills mounts academic writing, polishing, citation, paper reading, figure, reviewer response, paper-to-ppt, data availability, and literature search workflows.\nproductivity\ntool data-source action cli mcp skill\nnature academic-writing paper-reading citation figures literature-search review-response ppt\npubmed_email pubmed email optional email used by ncbi e-utilities for academic search workflows. ncbi_api_key ncbi api key optional ncbi api key for higher pubmed rate limits. semantic_scholar_api_key semantic scholar api key optional semantic scholar api key for literature search workflows.\nnature skills\nnature skills runtime optional credentials\nnature skills\n\nmounts yuan1z0825/nature skills into cloud agents as a bundled academic\nresearch and publication workflow plugin.\n\nruntime\n\n pulls all skills/nature folders into\n /workspace/.agents/plugin skills/nature skills.\n installs the python dependencies needed by the bundled academic search mcp\n server into the plugin runtime dependency volume from a debian based node init\n image.\n registers the nature academic search stdio mcp server.\n\noptional credentials\n\n pubmed email improves compliance with ncbi e utilities guidance.\n ncbi api key raises pubmed rate limits.\n semantic scholar api key can improve semantic scholar search throughput.\n\nthe non search skills can be used without credentials."
|
|
6623
|
+
},
|
|
6390
6624
|
{
|
|
6391
6625
|
"id": "atlassian",
|
|
6392
6626
|
"name": "Atlassian",
|
|
@@ -6698,6 +6932,103 @@ var RAW_PLUGIN_LIBRARY = String.raw`[
|
|
|
6698
6932
|
},
|
|
6699
6933
|
"searchText": "posthog\nposthog\nposthog productops covers funnels, retention, feature flags, experiments, session replay, hogql, logs, and product-growth diagnostics.\nanalytics\ntool data-source action cli mcp skill\nposthog analytics feature-flags funnels hogql experiments\nposthog_api_key posthog api key personal or project api key for posthog. posthog_project_id project id optional default project. posthog_host posthog host optional posthog host.\nposthog plugin\nposthog plugin configuration keys setup runtime assets references\nposthog plugin\n\nposthog productops covers funnels, retention, feature flags, experiments, session replay, hogql, logs, and product growth diagnostics.\n\nconfiguration keys\n\n key required sensitive description \n \n posthog api key yes yes posthog personal or project api key. \n posthog project id no no optional default posthog project id. \n posthog host no no optional posthog host, for example https://app.posthog.com or a self hosted url. \n\nsetup\n\n1. open posthog project or personal api settings.\n2. create an api key with the minimum scopes needed by the buddy.\n3. add the key as posthog api key.\n4. add posthog project id if the buddy should default to one project.\n5. add posthog host for eu cloud or self hosted installations.\n6. deploy the buddy and run the cli verification check.\n\nruntime assets\n\n installs the experimental posthog cli package.\n registers hosted posthog mcp metadata.\n\nreferences\n\n posthog mcp\n posthog api overview\n posthog endpoints cli"
|
|
6700
6934
|
},
|
|
6935
|
+
{
|
|
6936
|
+
"id": "agentmemory",
|
|
6937
|
+
"name": "AgentMemory",
|
|
6938
|
+
"description": "AgentMemory adds persistent, searchable memory for coding agents through its MCP server, CLI, and optional local/remote memory service.",
|
|
6939
|
+
"version": "1.0.0",
|
|
6940
|
+
"category": "automation",
|
|
6941
|
+
"capabilities": [
|
|
6942
|
+
"tool",
|
|
6943
|
+
"data-source",
|
|
6944
|
+
"action",
|
|
6945
|
+
"cli",
|
|
6946
|
+
"mcp",
|
|
6947
|
+
"skill"
|
|
6948
|
+
],
|
|
6949
|
+
"tags": [
|
|
6950
|
+
"memory",
|
|
6951
|
+
"mcp",
|
|
6952
|
+
"coding-agent",
|
|
6953
|
+
"context",
|
|
6954
|
+
"search",
|
|
6955
|
+
"persistence"
|
|
6956
|
+
],
|
|
6957
|
+
"authType": "none",
|
|
6958
|
+
"website": "https://agent-memory.dev",
|
|
6959
|
+
"docs": "https://github.com/rohitg00/agentmemory",
|
|
6960
|
+
"popularity": 82,
|
|
6961
|
+
"manifest": {
|
|
6962
|
+
"id": "agentmemory",
|
|
6963
|
+
"name": "AgentMemory",
|
|
6964
|
+
"description": "AgentMemory adds persistent, searchable memory for coding agents through its MCP server, CLI, and optional local/remote memory service.",
|
|
6965
|
+
"version": "1.0.0",
|
|
6966
|
+
"category": "automation",
|
|
6967
|
+
"icon": "brain",
|
|
6968
|
+
"website": "https://agent-memory.dev",
|
|
6969
|
+
"docs": "https://github.com/rohitg00/agentmemory",
|
|
6970
|
+
"auth": {
|
|
6971
|
+
"type": "none",
|
|
6972
|
+
"fields": [
|
|
6973
|
+
{
|
|
6974
|
+
"key": "AGENTMEMORY_URL",
|
|
6975
|
+
"label": "AgentMemory service URL",
|
|
6976
|
+
"description": "Optional remote AgentMemory service URL. Omit to use the local runtime store.",
|
|
6977
|
+
"required": false,
|
|
6978
|
+
"sensitive": false,
|
|
6979
|
+
"placeholder": "http://127.0.0.1:7331",
|
|
6980
|
+
"helpUrl": "https://github.com/rohitg00/agentmemory"
|
|
6981
|
+
},
|
|
6982
|
+
{
|
|
6983
|
+
"key": "AGENTMEMORY_API_KEY",
|
|
6984
|
+
"label": "AgentMemory API key",
|
|
6985
|
+
"description": "Optional API key for a protected AgentMemory service.",
|
|
6986
|
+
"required": false,
|
|
6987
|
+
"sensitive": true,
|
|
6988
|
+
"placeholder": "am_...",
|
|
6989
|
+
"helpUrl": "https://github.com/rohitg00/agentmemory"
|
|
6990
|
+
},
|
|
6991
|
+
{
|
|
6992
|
+
"key": "AGENTMEMORY_PROJECT_ID",
|
|
6993
|
+
"label": "AgentMemory project id",
|
|
6994
|
+
"description": "Optional project/workspace id used to partition memories.",
|
|
6995
|
+
"required": false,
|
|
6996
|
+
"sensitive": false,
|
|
6997
|
+
"placeholder": "shadow-cloud"
|
|
6998
|
+
}
|
|
6999
|
+
]
|
|
7000
|
+
},
|
|
7001
|
+
"capabilities": [
|
|
7002
|
+
"tool",
|
|
7003
|
+
"data-source",
|
|
7004
|
+
"action",
|
|
7005
|
+
"cli",
|
|
7006
|
+
"mcp",
|
|
7007
|
+
"skill"
|
|
7008
|
+
],
|
|
7009
|
+
"tags": [
|
|
7010
|
+
"memory",
|
|
7011
|
+
"mcp",
|
|
7012
|
+
"coding-agent",
|
|
7013
|
+
"context",
|
|
7014
|
+
"search",
|
|
7015
|
+
"persistence"
|
|
7016
|
+
],
|
|
7017
|
+
"popularity": 82
|
|
7018
|
+
},
|
|
7019
|
+
"requiredFields": [],
|
|
7020
|
+
"readme": {
|
|
7021
|
+
"title": "AgentMemory",
|
|
7022
|
+
"excerpt": "AgentMemory\n\nAgentMemory adds persistent, searchable memory to Shadow Cloud agents through\nthe @agentmemory/mcp MCP server and the agentmemory CLI.\n\nCapabilities\n\n Registers the AgentMemory MCP server for every supported runtime.\n Installs the AgentMemory CLI and MCP package into the runtime dependency\n volume.\n Supports an optional remote AgentMemory service via AGENTMEMORY URL and\n AGENTMEMORY API KEY.\n\nConfiguration\n\n \n\nOptional environment fields:\n\n AGENTMEMORY URL: remote service URL. Omit to use the local runtime store.\n AGENTMEMORY API KEY: key for a protected remote service.\n AGENTMEMORY PROJECT ID: project/workspace partition id.\n\nSafety\n\nAgents should only store durable project context, decisions, and user approved\nfacts. Do not store secrets, tokens, credentials, or private personal data.",
|
|
7023
|
+
"headings": [
|
|
7024
|
+
"AgentMemory",
|
|
7025
|
+
"Capabilities",
|
|
7026
|
+
"Configuration",
|
|
7027
|
+
"Safety"
|
|
7028
|
+
]
|
|
7029
|
+
},
|
|
7030
|
+
"searchText": "agentmemory\nagentmemory\nagentmemory adds persistent, searchable memory for coding agents through its mcp server, cli, and optional local/remote memory service.\nautomation\ntool data-source action cli mcp skill\nmemory mcp coding-agent context search persistence\nagentmemory_url agentmemory service url optional remote agentmemory service url. omit to use the local runtime store. agentmemory_api_key agentmemory api key optional api key for a protected agentmemory service. agentmemory_project_id agentmemory project id optional project/workspace id used to partition memories.\nagentmemory\nagentmemory capabilities configuration safety\nagentmemory\n\nagentmemory adds persistent, searchable memory to shadow cloud agents through\nthe @agentmemory/mcp mcp server and the agentmemory cli.\n\ncapabilities\n\n registers the agentmemory mcp server for every supported runtime.\n installs the agentmemory cli and mcp package into the runtime dependency\n volume.\n supports an optional remote agentmemory service via agentmemory url and\n agentmemory api key.\n\nconfiguration\n\n \n\noptional environment fields:\n\n agentmemory url: remote service url. omit to use the local runtime store.\n agentmemory api key: key for a protected remote service.\n agentmemory project id: project/workspace partition id.\n\nsafety\n\nagents should only store durable project context, decisions, and user approved\nfacts. do not store secrets, tokens, credentials, or private personal data."
|
|
7031
|
+
},
|
|
6701
7032
|
{
|
|
6702
7033
|
"id": "baidu-smartprogram",
|
|
6703
7034
|
"name": "Baidu Smart Program",
|
|
@@ -7583,6 +7914,43 @@ var RAW_TEMPLATE_LIBRARY = String.raw`[
|
|
|
7583
7914
|
},
|
|
7584
7915
|
"searchText": "code-arena\n代码擂台\n用于出题、限时挑战、提示和代码复盘的编程对战 buddy 模板。\ngeneral\nmodel-provider shadowob\nproblems arena review\narena buddy\ngenerate coding challenges, set constraints, track attempts, give hints, and review solutions. prefer concise test cases and actionable feedback."
|
|
7585
7916
|
},
|
|
7917
|
+
{
|
|
7918
|
+
"slug": "code-trainer",
|
|
7919
|
+
"title": "Code Trainer 算法训练",
|
|
7920
|
+
"description": "基于 Code Trainer Server App 授权的单 Buddy 算法训练模板,用多个频道承载自动复盘、题目推荐、学习计划、技巧推送、错题回炉和进度报告。",
|
|
7921
|
+
"category": "general",
|
|
7922
|
+
"plugins": [
|
|
7923
|
+
"model-provider",
|
|
7924
|
+
"shadowob"
|
|
7925
|
+
],
|
|
7926
|
+
"channels": [
|
|
7927
|
+
"助教资讯",
|
|
7928
|
+
"题目推荐",
|
|
7929
|
+
"学习规划",
|
|
7930
|
+
"代码复盘",
|
|
7931
|
+
"错题回炉",
|
|
7932
|
+
"算法小技巧"
|
|
7933
|
+
],
|
|
7934
|
+
"buddyNames": [
|
|
7935
|
+
"算法教练"
|
|
7936
|
+
],
|
|
7937
|
+
"agentCount": 1,
|
|
7938
|
+
"systemPromptExcerpt": "You are the single Code Trainer Buddy. Use the mounted shadowob CLI skill as the source of truth: run \u0060shadowob app discover --server \"$SHADOW_SERVER_APP_SERVER_CODE_TRAINER_APP\" --json\u0060, then call \u0060shadowob app call \"$SHADOW_SERVER_APP_KEY_CODE_TRAINER_APP\" <command> --server \"$SHADOW_SERVER_APP_SERVER_CODE_TRAINER_APP\" --json-input '<raw-command-input-json>' --json\u0060.\n\nUse channels as modes, not separate identities:\n- #代码复盘: claim Inbox tasks immediately, acknowledge within 60 seconds, run visible examples plus hidden/edge cases, then write submissions.analyze with verdict, score, complexity, diagnosis, and next steps. If review takes more than 3 minutes, post one brief progress note.\n- #题目推荐: recommend the next problem from learning.overview using reinforcement, diversity, review, and popular coverage. Include predicted ACK rate when available and offer a lighter fallback.\n- #学习规划: maintain daily/weekly lists, deadline target, and first concrete action.\n- #算法小技巧: post compact templates, pitfalls, complexity reminders, and short checks tied to current weak or active tags.\n- #错题回炉: schedule spaced review for wrong or nearly forgotten problems and suggest one similar variation.\n- #助",
|
|
7939
|
+
"valid": true,
|
|
7940
|
+
"validation": {
|
|
7941
|
+
"valid": true,
|
|
7942
|
+
"agents": 1,
|
|
7943
|
+
"configurations": 0,
|
|
7944
|
+
"violations": [],
|
|
7945
|
+
"extendsErrors": [],
|
|
7946
|
+
"templateRefs": {
|
|
7947
|
+
"env": 1,
|
|
7948
|
+
"secret": 0,
|
|
7949
|
+
"file": 0
|
|
7950
|
+
}
|
|
7951
|
+
},
|
|
7952
|
+
"searchText": "code-trainer\ncode trainer 算法训练\n基于 code trainer server app 授权的单 buddy 算法训练模板,用多个频道承载自动复盘、题目推荐、学习计划、技巧推送、错题回炉和进度报告。\ngeneral\nmodel-provider shadowob\n助教资讯 题目推荐 学习规划 代码复盘 错题回炉 算法小技巧\n算法教练\nyou are the single code trainer buddy. use the mounted shadowob cli skill as the source of truth: run \u0060shadowob app discover --server \"$shadow_server_app_server_code_trainer_app\" --json\u0060, then call \u0060shadowob app call \"$shadow_server_app_key_code_trainer_app\" <command> --server \"$shadow_server_app_server_code_trainer_app\" --json-input '<raw-command-input-json>' --json\u0060.\n\nuse channels as modes, not separate identities:\n- #代码复盘: claim inbox tasks immediately, acknowledge within 60 seconds, run visible examples plus hidden/edge cases, then write submissions.analyze with verdict, score, complexity, diagnosis, and next steps. if review takes more than 3 minutes, post one brief progress note.\n- #题目推荐: recommend the next problem from learning.overview using reinforcement, diversity, review, and popular coverage. include predicted ack rate when available and offer a lighter fallback.\n- #学习规划: maintain daily/weekly lists, deadline target, and first concrete action.\n- #算法小技巧: post compact templates, pitfalls, complexity reminders, and short checks tied to current weak or active tags.\n- #错题回炉: schedule spaced review for wrong or nearly forgotten problems and suggest one similar variation.\n- #助"
|
|
7953
|
+
},
|
|
7586
7954
|
{
|
|
7587
7955
|
"slug": "daily-brief",
|
|
7588
7956
|
"title": "晨间简报",
|
|
@@ -8682,15 +9050,75 @@ function summarizeCostOverview(summaries, billingUnit) {
|
|
|
8682
9050
|
|
|
8683
9051
|
// src/clients/kubectl-runtime.ts
|
|
8684
9052
|
import { execFileSync, spawn, spawnSync } from "child_process";
|
|
8685
|
-
import { existsSync as
|
|
8686
|
-
import {
|
|
8687
|
-
import { delimiter as delimiter2, join as
|
|
9053
|
+
import { existsSync as existsSync3, mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
9054
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
9055
|
+
import { delimiter as delimiter2, join as join3 } from "path";
|
|
8688
9056
|
|
|
8689
9057
|
// src/services/deployment-runtime.service.ts
|
|
8690
9058
|
import { createHash } from "crypto";
|
|
8691
|
-
import { existsSync, mkdirSync, mkdtempSync,
|
|
8692
|
-
import { homedir, tmpdir } from "os";
|
|
8693
|
-
import { delimiter, join } from "path";
|
|
9059
|
+
import { existsSync as existsSync2, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "fs";
|
|
9060
|
+
import { homedir as homedir2, tmpdir } from "os";
|
|
9061
|
+
import { delimiter, join as join2 } from "path";
|
|
9062
|
+
|
|
9063
|
+
// src/utils/kubeconfig-file.ts
|
|
9064
|
+
import { existsSync, readFileSync, statSync } from "fs";
|
|
9065
|
+
import { homedir } from "os";
|
|
9066
|
+
import { join } from "path";
|
|
9067
|
+
function defaultKubeconfigPath() {
|
|
9068
|
+
return join(homedir(), ".kube", "config");
|
|
9069
|
+
}
|
|
9070
|
+
function kubeconfigSetupHint() {
|
|
9071
|
+
return "Configure KUBECONFIG_HOST_PATH or CLOUD_SAAS_CLUSTER_KUBECONFIG_HOST_PATH to an existing host kubeconfig file, or initialize/import a cluster before deploying.";
|
|
9072
|
+
}
|
|
9073
|
+
function assertReadableKubeconfigFile(kubeconfigPath, label = "Kubernetes kubeconfig") {
|
|
9074
|
+
if (!existsSync(kubeconfigPath)) {
|
|
9075
|
+
throw new Error(`${label} not found at ${kubeconfigPath}. ${kubeconfigSetupHint()}`);
|
|
9076
|
+
}
|
|
9077
|
+
let stat3;
|
|
9078
|
+
try {
|
|
9079
|
+
stat3 = statSync(kubeconfigPath);
|
|
9080
|
+
} catch (err) {
|
|
9081
|
+
throw new Error(
|
|
9082
|
+
`Failed to inspect ${label} at ${kubeconfigPath}: ${err.message}. ` + kubeconfigSetupHint()
|
|
9083
|
+
);
|
|
9084
|
+
}
|
|
9085
|
+
if (stat3.isDirectory()) {
|
|
9086
|
+
throw new Error(
|
|
9087
|
+
`${label} path ${kubeconfigPath} is a directory, not a file. This usually means Docker created the bind-mount target because the host kubeconfig file is missing. ` + kubeconfigSetupHint()
|
|
9088
|
+
);
|
|
9089
|
+
}
|
|
9090
|
+
if (!stat3.isFile()) {
|
|
9091
|
+
throw new Error(
|
|
9092
|
+
`${label} path ${kubeconfigPath} is not a regular file. ${kubeconfigSetupHint()}`
|
|
9093
|
+
);
|
|
9094
|
+
}
|
|
9095
|
+
if (stat3.size === 0) {
|
|
9096
|
+
throw new Error(`${label} at ${kubeconfigPath} is empty. ${kubeconfigSetupHint()}`);
|
|
9097
|
+
}
|
|
9098
|
+
}
|
|
9099
|
+
function readKubeconfigFile(kubeconfigPath, label = "Kubernetes kubeconfig") {
|
|
9100
|
+
assertReadableKubeconfigFile(kubeconfigPath, label);
|
|
9101
|
+
try {
|
|
9102
|
+
return readFileSync(kubeconfigPath, "utf8");
|
|
9103
|
+
} catch (err) {
|
|
9104
|
+
throw new Error(
|
|
9105
|
+
`Failed to read ${label} at ${kubeconfigPath}: ${err.message}. ` + kubeconfigSetupHint()
|
|
9106
|
+
);
|
|
9107
|
+
}
|
|
9108
|
+
}
|
|
9109
|
+
function findReadableKubeconfigPath(candidates, label = "Kubernetes kubeconfig") {
|
|
9110
|
+
const uniqueCandidates = [
|
|
9111
|
+
...new Set(candidates.filter((candidate) => Boolean(candidate)))
|
|
9112
|
+
];
|
|
9113
|
+
for (const candidate of uniqueCandidates) {
|
|
9114
|
+
if (!existsSync(candidate)) continue;
|
|
9115
|
+
assertReadableKubeconfigFile(candidate, label);
|
|
9116
|
+
return candidate;
|
|
9117
|
+
}
|
|
9118
|
+
return void 0;
|
|
9119
|
+
}
|
|
9120
|
+
|
|
9121
|
+
// src/services/deployment-runtime.service.ts
|
|
8694
9122
|
function extractKubeContext(kubeconfigYaml) {
|
|
8695
9123
|
const match = kubeconfigYaml.match(/current-context:\s*(\S+)/);
|
|
8696
9124
|
return match?.[1];
|
|
@@ -8727,24 +9155,24 @@ function normalizeRuntimeEnvVars(envVars) {
|
|
|
8727
9155
|
return normalized;
|
|
8728
9156
|
}
|
|
8729
9157
|
function getStableRuntimeKubeconfigPath(kubeconfigYaml) {
|
|
8730
|
-
const runtimeDir =
|
|
9158
|
+
const runtimeDir = join2(homedir2(), ".shadowob", "kubeconfigs");
|
|
8731
9159
|
mkdirSync(runtimeDir, { recursive: true });
|
|
8732
9160
|
const hash = createHash("sha256").update(kubeconfigYaml).digest("hex");
|
|
8733
|
-
const kubeconfigPath =
|
|
8734
|
-
if (!
|
|
9161
|
+
const kubeconfigPath = join2(runtimeDir, `${hash}.yaml`);
|
|
9162
|
+
if (!existsSync2(kubeconfigPath)) {
|
|
8735
9163
|
writeFileSync(kubeconfigPath, kubeconfigYaml, { mode: 384 });
|
|
8736
9164
|
}
|
|
8737
9165
|
return kubeconfigPath;
|
|
8738
9166
|
}
|
|
8739
9167
|
function isContainerizedRuntime() {
|
|
8740
|
-
return process.env.SHADOW_CONTAINERIZED === "1" ||
|
|
9168
|
+
return process.env.SHADOW_CONTAINERIZED === "1" || existsSync2("/.dockerenv");
|
|
8741
9169
|
}
|
|
8742
9170
|
function getHostLocalRuntimeKubeconfigPaths() {
|
|
8743
9171
|
const candidates = [process.env.KUBECONFIG_HOST_PATH?.trim()];
|
|
8744
9172
|
if (!isContainerizedRuntime()) {
|
|
8745
9173
|
candidates.push(
|
|
8746
9174
|
...process.env.KUBECONFIG?.split(delimiter).map((candidate) => candidate.trim()).filter((candidate) => candidate.length > 0) ?? [],
|
|
8747
|
-
|
|
9175
|
+
defaultKubeconfigPath()
|
|
8748
9176
|
);
|
|
8749
9177
|
}
|
|
8750
9178
|
return [...new Set(candidates.filter((candidate) => Boolean(candidate)))];
|
|
@@ -8757,17 +9185,17 @@ function resolveAmbientRuntimeKubeconfigPath() {
|
|
|
8757
9185
|
const candidates = [
|
|
8758
9186
|
...process.env.KUBECONFIG?.split(delimiter).map((candidate) => candidate.trim()).filter((candidate) => candidate.length > 0) ?? [],
|
|
8759
9187
|
process.env.KUBECONFIG_HOST_PATH?.trim(),
|
|
8760
|
-
|
|
9188
|
+
defaultKubeconfigPath()
|
|
8761
9189
|
].filter((candidate) => Boolean(candidate));
|
|
8762
|
-
return candidates
|
|
9190
|
+
return findReadableKubeconfigPath(candidates, "Cloud SaaS Kubernetes kubeconfig");
|
|
8763
9191
|
}
|
|
8764
9192
|
var DeploymentRuntimeService = class {
|
|
8765
9193
|
constructor(deployService) {
|
|
8766
9194
|
this.deployService = deployService;
|
|
8767
9195
|
}
|
|
8768
9196
|
async deployFromSnapshot(options) {
|
|
8769
|
-
const configDir = mkdtempSync(
|
|
8770
|
-
const configPath =
|
|
9197
|
+
const configDir = mkdtempSync(join2(tmpdir(), "sc-cfg-"));
|
|
9198
|
+
const configPath = join2(configDir, "shadowob-cloud.json");
|
|
8771
9199
|
writeFileSync(configPath, JSON.stringify(options.configSnapshot, null, 2), "utf-8");
|
|
8772
9200
|
const {
|
|
8773
9201
|
configSnapshot: _configSnapshot,
|
|
@@ -8820,7 +9248,7 @@ var DeploymentRuntimeService = class {
|
|
|
8820
9248
|
let k8sContext;
|
|
8821
9249
|
let kubeConfigPath;
|
|
8822
9250
|
const activeKubeconfigPath = resolveAmbientRuntimeKubeconfigPath();
|
|
8823
|
-
const activeKubeconfig = cluster?.kubeconfig ? cluster.kubeconfig : activeKubeconfigPath ?
|
|
9251
|
+
const activeKubeconfig = cluster?.kubeconfig ? cluster.kubeconfig : activeKubeconfigPath ? readKubeconfigFile(activeKubeconfigPath, "Cloud SaaS Kubernetes kubeconfig") : void 0;
|
|
8824
9252
|
if (activeKubeconfig) {
|
|
8825
9253
|
const shouldRewriteLoopback = Boolean(
|
|
8826
9254
|
cluster?.kubeconfig || activeKubeconfigPath && !isHostLocalRuntimeKubeconfigPath(activeKubeconfigPath)
|
|
@@ -8845,14 +9273,14 @@ function volumeSnapshotApiAvailableFromOutput(output2) {
|
|
|
8845
9273
|
);
|
|
8846
9274
|
}
|
|
8847
9275
|
function isContainerizedRuntime2() {
|
|
8848
|
-
return process.env.SHADOW_CONTAINERIZED === "1" ||
|
|
9276
|
+
return process.env.SHADOW_CONTAINERIZED === "1" || existsSync3("/.dockerenv");
|
|
8849
9277
|
}
|
|
8850
9278
|
function getHostLocalKubeconfigPaths() {
|
|
8851
9279
|
const candidates = [process.env.KUBECONFIG_HOST_PATH?.trim()];
|
|
8852
9280
|
if (!isContainerizedRuntime2()) {
|
|
8853
9281
|
candidates.push(
|
|
8854
9282
|
...process.env.KUBECONFIG?.split(delimiter2).map((candidate) => candidate.trim()).filter((candidate) => candidate.length > 0) ?? [],
|
|
8855
|
-
|
|
9283
|
+
defaultKubeconfigPath()
|
|
8856
9284
|
);
|
|
8857
9285
|
}
|
|
8858
9286
|
return [...new Set(candidates.filter((candidate) => Boolean(candidate)))];
|
|
@@ -8866,22 +9294,23 @@ function extractCurrentContext(kubeconfigYaml) {
|
|
|
8866
9294
|
}
|
|
8867
9295
|
function resolveAmbientKubeconfig() {
|
|
8868
9296
|
const envCandidates = process.env.KUBECONFIG?.split(delimiter2).map((candidate) => candidate.trim()).filter((candidate) => candidate.length > 0) ?? [];
|
|
8869
|
-
const
|
|
9297
|
+
const candidates = [
|
|
8870
9298
|
...envCandidates,
|
|
8871
9299
|
process.env.KUBECONFIG_HOST_PATH?.trim(),
|
|
8872
|
-
|
|
8873
|
-
].filter((candidate) => Boolean(candidate))
|
|
9300
|
+
defaultKubeconfigPath()
|
|
9301
|
+
].filter((candidate) => Boolean(candidate));
|
|
9302
|
+
const kubeconfigPath = findReadableKubeconfigPath(candidates, "Kubernetes kubectl kubeconfig");
|
|
8874
9303
|
if (!kubeconfigPath) {
|
|
8875
9304
|
return void 0;
|
|
8876
9305
|
}
|
|
8877
9306
|
return {
|
|
8878
|
-
kubeconfig:
|
|
9307
|
+
kubeconfig: readKubeconfigFile(kubeconfigPath, "Kubernetes kubectl kubeconfig"),
|
|
8879
9308
|
shouldRewriteLoopback: !isHostLocalKubeconfigPath(kubeconfigPath)
|
|
8880
9309
|
};
|
|
8881
9310
|
}
|
|
8882
9311
|
function createTempKubeconfig(kubeconfig, includeAmbientContext = false, rewriteLoopback = true) {
|
|
8883
|
-
const dir = mkdtempSync2(
|
|
8884
|
-
const path =
|
|
9312
|
+
const dir = mkdtempSync2(join3(tmpdir2(), "sc-saas-kube-"));
|
|
9313
|
+
const path = join3(dir, "kubeconfig");
|
|
8885
9314
|
const rewritten = rewriteLoopback ? rewriteLoopbackKubeconfig(kubeconfig, process.env.KUBECONFIG_LOOPBACK_HOST) : kubeconfig;
|
|
8886
9315
|
writeFileSync2(path, rewritten, { mode: 384 });
|
|
8887
9316
|
const args = ["--kubeconfig", path];
|
|
@@ -8944,6 +9373,83 @@ function execKubectl(args, kubeconfig, timeout = 3e3) {
|
|
|
8944
9373
|
})
|
|
8945
9374
|
);
|
|
8946
9375
|
}
|
|
9376
|
+
function tryExecKubectl(args, kubeconfig, timeout = 5e3) {
|
|
9377
|
+
try {
|
|
9378
|
+
return execKubectl(args, kubeconfig, timeout);
|
|
9379
|
+
} catch {
|
|
9380
|
+
return null;
|
|
9381
|
+
}
|
|
9382
|
+
}
|
|
9383
|
+
function resourceOutputHas(output2, resourceName) {
|
|
9384
|
+
return Boolean(
|
|
9385
|
+
output2?.split(/\s+/).map((item) => item.trim()).some((item) => item === resourceName)
|
|
9386
|
+
);
|
|
9387
|
+
}
|
|
9388
|
+
function checkAgentSandboxPreflight(options) {
|
|
9389
|
+
const missing = [];
|
|
9390
|
+
const warnings = [];
|
|
9391
|
+
const kubeconfig = options?.kubeconfig;
|
|
9392
|
+
const extensionResources = tryExecKubectl(
|
|
9393
|
+
["api-resources", "--api-group", "extensions.agents.x-k8s.io", "-o", "name"],
|
|
9394
|
+
kubeconfig
|
|
9395
|
+
);
|
|
9396
|
+
if (!resourceOutputHas(extensionResources, "sandboxtemplates")) {
|
|
9397
|
+
missing.push("CRD sandboxtemplates.extensions.agents.x-k8s.io");
|
|
9398
|
+
}
|
|
9399
|
+
if (!resourceOutputHas(extensionResources, "sandboxclaims")) {
|
|
9400
|
+
missing.push("CRD sandboxclaims.extensions.agents.x-k8s.io");
|
|
9401
|
+
}
|
|
9402
|
+
const coreResources = tryExecKubectl(
|
|
9403
|
+
["api-resources", "--api-group", "agents.x-k8s.io", "-o", "name"],
|
|
9404
|
+
kubeconfig
|
|
9405
|
+
);
|
|
9406
|
+
if (!resourceOutputHas(coreResources, "sandboxes")) {
|
|
9407
|
+
missing.push("CRD sandboxes.agents.x-k8s.io");
|
|
9408
|
+
}
|
|
9409
|
+
const controllerOutput = tryExecKubectl(
|
|
9410
|
+
["-n", "agent-sandbox-system", "get", "deployment", "agent-sandbox-controller", "-o", "json"],
|
|
9411
|
+
kubeconfig,
|
|
9412
|
+
1e4
|
|
9413
|
+
);
|
|
9414
|
+
if (!controllerOutput) {
|
|
9415
|
+
missing.push("deployment/agent-sandbox-controller in namespace agent-sandbox-system");
|
|
9416
|
+
} else {
|
|
9417
|
+
try {
|
|
9418
|
+
const controller = JSON.parse(controllerOutput);
|
|
9419
|
+
const status = controller.status ?? {};
|
|
9420
|
+
if ((status.availableReplicas ?? 0) < 1) {
|
|
9421
|
+
missing.push("Ready agent-sandbox controller");
|
|
9422
|
+
}
|
|
9423
|
+
} catch {
|
|
9424
|
+
missing.push("Readable agent-sandbox controller status");
|
|
9425
|
+
}
|
|
9426
|
+
}
|
|
9427
|
+
const runtimeClassNames = [
|
|
9428
|
+
...new Set(
|
|
9429
|
+
[options?.runtimeClassName, ...options?.runtimeClassNames ?? []].map((name) => name?.trim()).filter((name) => Boolean(name))
|
|
9430
|
+
)
|
|
9431
|
+
];
|
|
9432
|
+
for (const runtimeClassName of runtimeClassNames) {
|
|
9433
|
+
const runtimeClass = tryExecKubectl(["get", "runtimeclass", runtimeClassName], kubeconfig);
|
|
9434
|
+
if (!runtimeClass) {
|
|
9435
|
+
missing.push(`RuntimeClass ${runtimeClassName}`);
|
|
9436
|
+
}
|
|
9437
|
+
}
|
|
9438
|
+
const sandboxNodes = tryExecKubectl(
|
|
9439
|
+
["get", "nodes", "-l", "shadowob.com/sandbox-ready=true", "-o", "name"],
|
|
9440
|
+
kubeconfig
|
|
9441
|
+
);
|
|
9442
|
+
if (!sandboxNodes?.trim()) {
|
|
9443
|
+
warnings.push("No nodes are labeled shadowob.com/sandbox-ready=true");
|
|
9444
|
+
}
|
|
9445
|
+
return {
|
|
9446
|
+
ok: missing.length === 0,
|
|
9447
|
+
missing,
|
|
9448
|
+
warnings,
|
|
9449
|
+
runtimeClassName: runtimeClassNames[0],
|
|
9450
|
+
runtimeClassNames
|
|
9451
|
+
};
|
|
9452
|
+
}
|
|
8947
9453
|
function execKubectlAsync(args, kubeconfig, timeout = 3e3) {
|
|
8948
9454
|
return withKubeconfigAsync(
|
|
8949
9455
|
kubeconfig,
|
|
@@ -9594,25 +10100,25 @@ function deleteNamespace(namespace, kubeconfig) {
|
|
|
9594
10100
|
|
|
9595
10101
|
// src/cluster/kubeconfig.ts
|
|
9596
10102
|
import {
|
|
9597
|
-
existsSync as
|
|
10103
|
+
existsSync as existsSync4,
|
|
9598
10104
|
mkdirSync as mkdirSync2,
|
|
9599
10105
|
readdirSync,
|
|
9600
|
-
readFileSync as
|
|
10106
|
+
readFileSync as readFileSync2,
|
|
9601
10107
|
unlinkSync,
|
|
9602
10108
|
writeFileSync as writeFileSync3
|
|
9603
10109
|
} from "fs";
|
|
9604
10110
|
import { homedir as homedir3 } from "os";
|
|
9605
|
-
import { join as
|
|
10111
|
+
import { join as join4, resolve } from "path";
|
|
9606
10112
|
function getClustersDir() {
|
|
9607
10113
|
return resolve(homedir3(), ".shadow-cloud", "clusters");
|
|
9608
10114
|
}
|
|
9609
10115
|
function getKubeconfigPath(clusterName) {
|
|
9610
|
-
return
|
|
10116
|
+
return join4(getClustersDir(), `${clusterName}.yaml`);
|
|
9611
10117
|
}
|
|
9612
10118
|
function getMetaPath(clusterName) {
|
|
9613
|
-
return
|
|
10119
|
+
return join4(getClustersDir(), `${clusterName}.json`);
|
|
9614
10120
|
}
|
|
9615
|
-
function storeKubeconfig(clusterName, rawKubeconfig, masterPublicIp, nodeCount) {
|
|
10121
|
+
function storeKubeconfig(clusterName, rawKubeconfig, masterPublicIp, nodeCount, options) {
|
|
9616
10122
|
const dir = getClustersDir();
|
|
9617
10123
|
mkdirSync2(dir, { recursive: true });
|
|
9618
10124
|
const kubeconfig = rawKubeconfig.replace(/https?:\/\/127\.0\.0\.1/g, `https://${masterPublicIp}`);
|
|
@@ -9623,14 +10129,16 @@ function storeKubeconfig(clusterName, rawKubeconfig, masterPublicIp, nodeCount)
|
|
|
9623
10129
|
masterHost: masterPublicIp,
|
|
9624
10130
|
nodeCount,
|
|
9625
10131
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9626
|
-
kubeconfigPath
|
|
10132
|
+
kubeconfigPath,
|
|
10133
|
+
...options?.configHash ? { configHash: options.configHash } : {},
|
|
10134
|
+
...options?.features ? { features: options.features } : {}
|
|
9627
10135
|
};
|
|
9628
10136
|
writeFileSync3(getMetaPath(clusterName), JSON.stringify(meta, null, 2), { mode: 384 });
|
|
9629
10137
|
return meta;
|
|
9630
10138
|
}
|
|
9631
10139
|
function loadKubeconfigPath(clusterName) {
|
|
9632
10140
|
const path = getKubeconfigPath(clusterName);
|
|
9633
|
-
if (!
|
|
10141
|
+
if (!existsSync4(path)) {
|
|
9634
10142
|
throw new Error(
|
|
9635
10143
|
`Cluster "${clusterName}" not found.
|
|
9636
10144
|
Run: shadowob-cloud cluster init --config cluster.json`
|
|
@@ -9638,12 +10146,21 @@ Run: shadowob-cloud cluster init --config cluster.json`
|
|
|
9638
10146
|
}
|
|
9639
10147
|
return path;
|
|
9640
10148
|
}
|
|
10149
|
+
function loadClusterMeta(clusterName) {
|
|
10150
|
+
const path = getMetaPath(clusterName);
|
|
10151
|
+
if (!existsSync4(path)) return null;
|
|
10152
|
+
try {
|
|
10153
|
+
return JSON.parse(readFileSync2(path, "utf8"));
|
|
10154
|
+
} catch {
|
|
10155
|
+
return null;
|
|
10156
|
+
}
|
|
10157
|
+
}
|
|
9641
10158
|
function listRegisteredClusters() {
|
|
9642
10159
|
const dir = getClustersDir();
|
|
9643
|
-
if (!
|
|
10160
|
+
if (!existsSync4(dir)) return [];
|
|
9644
10161
|
return readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
9645
10162
|
try {
|
|
9646
|
-
return JSON.parse(
|
|
10163
|
+
return JSON.parse(readFileSync2(join4(dir, f), "utf8"));
|
|
9647
10164
|
} catch {
|
|
9648
10165
|
return null;
|
|
9649
10166
|
}
|
|
@@ -9652,7 +10169,7 @@ function listRegisteredClusters() {
|
|
|
9652
10169
|
function importKubeconfig(clusterName, kubeconfigFilePath) {
|
|
9653
10170
|
const dir = getClustersDir();
|
|
9654
10171
|
mkdirSync2(dir, { recursive: true });
|
|
9655
|
-
const raw =
|
|
10172
|
+
const raw = readFileSync2(kubeconfigFilePath, "utf8");
|
|
9656
10173
|
const serverMatch = raw.match(/server:\s*https?:\/\/([^/\s]+)/);
|
|
9657
10174
|
const rawHost = serverMatch?.[1] ?? "unknown";
|
|
9658
10175
|
const masterHost = rawHost.replace(/:\d+$/, "");
|
|
@@ -9672,33 +10189,52 @@ function importKubeconfig(clusterName, kubeconfigFilePath) {
|
|
|
9672
10189
|
function removeClusterFiles(clusterName) {
|
|
9673
10190
|
const kubeconfig = getKubeconfigPath(clusterName);
|
|
9674
10191
|
const meta = getMetaPath(clusterName);
|
|
9675
|
-
if (
|
|
9676
|
-
if (
|
|
10192
|
+
if (existsSync4(kubeconfig)) unlinkSync(kubeconfig);
|
|
10193
|
+
if (existsSync4(meta)) unlinkSync(meta);
|
|
9677
10194
|
}
|
|
9678
10195
|
|
|
9679
10196
|
// src/cluster/parser.ts
|
|
9680
|
-
import { readFileSync as
|
|
10197
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
9681
10198
|
import { homedir as homedir4 } from "os";
|
|
9682
10199
|
import { resolve as resolve2 } from "path";
|
|
9683
10200
|
|
|
9684
10201
|
// src/cluster/schema.ts
|
|
9685
10202
|
import { z } from "zod";
|
|
9686
|
-
var
|
|
9687
|
-
var
|
|
9688
|
-
|
|
9689
|
-
|
|
9690
|
-
|
|
9691
|
-
|
|
9692
|
-
|
|
9693
|
-
|
|
9694
|
-
/**
|
|
9695
|
-
|
|
9696
|
-
|
|
9697
|
-
|
|
9698
|
-
|
|
9699
|
-
|
|
9700
|
-
|
|
9701
|
-
|
|
10203
|
+
var K8S_LABEL_KEY_RE = /^([A-Za-z0-9][-A-Za-z0-9_.]*[A-Za-z0-9]\/)?[A-Za-z0-9]([-A-Za-z0-9_.]*[A-Za-z0-9])?$/;
|
|
10204
|
+
var K8S_LABEL_VALUE_RE = /^(([A-Za-z0-9][-A-Za-z0-9_.]*[A-Za-z0-9])?)$/;
|
|
10205
|
+
var KubernetesLabelMapSchema = z.record(
|
|
10206
|
+
z.string().min(1).regex(K8S_LABEL_KEY_RE, "Invalid Kubernetes label key"),
|
|
10207
|
+
z.string().max(63).regex(K8S_LABEL_VALUE_RE, "Invalid Kubernetes label value")
|
|
10208
|
+
);
|
|
10209
|
+
var ImageReferenceSchema = z.string().min(1).regex(/^\S+$/, "image must not contain whitespace");
|
|
10210
|
+
var ClusterContainerdRegistriesSchema = z.object({
|
|
10211
|
+
/**
|
|
10212
|
+
* k3s containerd mirrors. Written to /etc/rancher/k3s/registries.yaml as JSON/YAML.
|
|
10213
|
+
* Example: { "docker.io": { "endpoint": ["https://registry-1.docker.io"] } }
|
|
10214
|
+
*/
|
|
10215
|
+
mirrors: z.record(
|
|
10216
|
+
z.string().min(1),
|
|
10217
|
+
z.object({
|
|
10218
|
+
endpoint: z.array(z.string().url()).min(1)
|
|
10219
|
+
})
|
|
10220
|
+
).optional(),
|
|
10221
|
+
configs: z.record(
|
|
10222
|
+
z.string().min(1),
|
|
10223
|
+
z.object({
|
|
10224
|
+
auth: z.object({
|
|
10225
|
+
username: z.string().optional(),
|
|
10226
|
+
password: z.string().optional(),
|
|
10227
|
+
auth: z.string().optional(),
|
|
10228
|
+
identityToken: z.string().optional()
|
|
10229
|
+
}).optional(),
|
|
10230
|
+
tls: z.object({
|
|
10231
|
+
caFile: z.string().optional(),
|
|
10232
|
+
certFile: z.string().optional(),
|
|
10233
|
+
keyFile: z.string().optional(),
|
|
10234
|
+
insecureSkipVerify: z.boolean().optional()
|
|
10235
|
+
}).optional()
|
|
10236
|
+
})
|
|
10237
|
+
).optional()
|
|
9702
10238
|
});
|
|
9703
10239
|
var ClusterInstallConfigSchema = z.object({
|
|
9704
10240
|
/** k3s release version. Example: v1.35.4+k3s1 */
|
|
@@ -9714,7 +10250,86 @@ var ClusterInstallConfigSchema = z.object({
|
|
|
9714
10250
|
/** Registry prefix used by k3s for bundled system images. */
|
|
9715
10251
|
systemDefaultRegistry: z.string().min(1).regex(/^\S+$/, "systemDefaultRegistry must not contain whitespace").optional(),
|
|
9716
10252
|
/** Sandbox pause image used by k3s/containerd. Useful when Docker Hub is unreachable. */
|
|
9717
|
-
pauseImage: z.string().min(1).regex(/^\S+$/, "pauseImage must not contain whitespace").optional()
|
|
10253
|
+
pauseImage: z.string().min(1).regex(/^\S+$/, "pauseImage must not contain whitespace").optional(),
|
|
10254
|
+
/** Optional k3s containerd registry mirrors/auth config for workload images. */
|
|
10255
|
+
registries: ClusterContainerdRegistriesSchema.optional()
|
|
10256
|
+
});
|
|
10257
|
+
var AGENT_SANDBOX_DEFAULT_VERSION = "v0.4.5";
|
|
10258
|
+
var AGENT_SANDBOX_DEFAULT_RUNTIME_CLASS = "shadow-runc";
|
|
10259
|
+
var AGENT_SANDBOX_DEFAULT_RUNTIME_HANDLER = "runc";
|
|
10260
|
+
var RuntimeClassNameSchema = z.string().min(1).regex(/^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/, "runtimeClassName must be a Kubernetes DNS label");
|
|
10261
|
+
var ClusterSandboxFeatureConfigSchema = z.object({
|
|
10262
|
+
/** Enable agent-sandbox as a cluster capability. */
|
|
10263
|
+
enabled: z.boolean().default(true),
|
|
10264
|
+
/** Install/upgrade the upstream CRDs and controller during cluster init/apply. */
|
|
10265
|
+
install: z.boolean().default(true),
|
|
10266
|
+
/** Pinned upstream agent-sandbox release used to build default manifest URLs. */
|
|
10267
|
+
version: z.string().min(1).regex(/^v\d+\.\d+\.\d+(?:[-+][A-Za-z0-9._-]+)?$/, "version must look like v0.4.5").default(AGENT_SANDBOX_DEFAULT_VERSION),
|
|
10268
|
+
/**
|
|
10269
|
+
* Optional manifest URLs. Defaults to the upstream release manifest.yaml and extensions.yaml.
|
|
10270
|
+
* Use mirrored URLs for restricted networks.
|
|
10271
|
+
*/
|
|
10272
|
+
manifestUrls: z.array(z.string().url()).min(1).optional(),
|
|
10273
|
+
/** Optional controller image override, useful for domestic/private registries. */
|
|
10274
|
+
controllerImage: ImageReferenceSchema.optional(),
|
|
10275
|
+
/** RuntimeClass injected into generated Cloud SaaS sandbox configs. */
|
|
10276
|
+
runtimeClassName: RuntimeClassNameSchema.default(AGENT_SANDBOX_DEFAULT_RUNTIME_CLASS),
|
|
10277
|
+
/** Create RuntimeClass automatically. Disable when the cluster already provides gvisor/runsc. */
|
|
10278
|
+
createRuntimeClass: z.boolean().default(true),
|
|
10279
|
+
/** RuntimeClass handler used when createRuntimeClass is true. */
|
|
10280
|
+
runtimeClassHandler: z.string().min(1).regex(/^\S+$/, "runtimeClassHandler must not contain whitespace").default(AGENT_SANDBOX_DEFAULT_RUNTIME_HANDLER),
|
|
10281
|
+
/** Wait timeout for CRDs/controller readiness. */
|
|
10282
|
+
waitTimeoutSeconds: z.number().int().min(30).max(1200).default(300),
|
|
10283
|
+
/** Fail cluster init/apply when sandbox cannot be verified. */
|
|
10284
|
+
required: z.boolean().default(true),
|
|
10285
|
+
/** Label selector injected into sandbox workloads unless templates override it. */
|
|
10286
|
+
nodeSelector: KubernetesLabelMapSchema.default({ "shadowob.com/sandbox-ready": "true" }),
|
|
10287
|
+
/** Run a real SandboxTemplate/SandboxClaim smoke after install/verify. */
|
|
10288
|
+
smokeTest: z.boolean().default(false),
|
|
10289
|
+
/** Image used by the optional smoke test. Mirror this for restricted networks. */
|
|
10290
|
+
smokeImage: ImageReferenceSchema.default("busybox:1.36")
|
|
10291
|
+
}).refine((sandbox) => !sandbox.createRuntimeClass || Boolean(sandbox.runtimeClassHandler), {
|
|
10292
|
+
path: ["runtimeClassHandler"],
|
|
10293
|
+
message: "runtimeClassHandler is required when createRuntimeClass is true"
|
|
10294
|
+
});
|
|
10295
|
+
var ClusterSandboxFeatureSchema = z.union([z.boolean(), ClusterSandboxFeatureConfigSchema]);
|
|
10296
|
+
var ClusterFeaturesSchema = z.object({
|
|
10297
|
+
/** agent-sandbox CRDs/controller/runtime-class management. */
|
|
10298
|
+
sandbox: ClusterSandboxFeatureSchema.optional()
|
|
10299
|
+
});
|
|
10300
|
+
var NodeRoleSchema = z.enum(["master", "worker"]);
|
|
10301
|
+
var NodeConfigSchema = z.object({
|
|
10302
|
+
/** Node role in the cluster */
|
|
10303
|
+
role: NodeRoleSchema,
|
|
10304
|
+
/** Public IP or hostname */
|
|
10305
|
+
host: z.string().min(1),
|
|
10306
|
+
/** SSH port (default: 22) */
|
|
10307
|
+
port: z.number().int().min(1).max(65535).default(22),
|
|
10308
|
+
/** SSH username */
|
|
10309
|
+
user: z.string().min(1),
|
|
10310
|
+
/** Path to SSH private key (supports ~) — mutually inclusive with or exclusive of password */
|
|
10311
|
+
sshKeyPath: z.string().optional(),
|
|
10312
|
+
/** SSH private key passphrase — use ${env:VAR} to avoid storing plaintext */
|
|
10313
|
+
sshKeyPassphrase: z.string().optional(),
|
|
10314
|
+
/**
|
|
10315
|
+
* SSH agent socket. Use true for SSH_AUTH_SOCK, or a socket path/template.
|
|
10316
|
+
* Useful for encrypted private keys already loaded into an agent.
|
|
10317
|
+
*/
|
|
10318
|
+
sshAgent: z.union([z.boolean(), z.string().min(1)]).optional(),
|
|
10319
|
+
/** SSH password — use ${env:VAR} to avoid storing plaintext */
|
|
10320
|
+
password: z.string().optional(),
|
|
10321
|
+
/** Optional per-node k3s installer overrides for mixed-region clusters. */
|
|
10322
|
+
install: ClusterInstallConfigSchema.optional(),
|
|
10323
|
+
/** Region label applied during cluster init/apply, e.g. cn or us. */
|
|
10324
|
+
region: z.string().min(1).max(63).regex(K8S_LABEL_VALUE_RE).optional(),
|
|
10325
|
+
/** Extra Kubernetes node labels applied during cluster init/apply. */
|
|
10326
|
+
labels: KubernetesLabelMapSchema.optional(),
|
|
10327
|
+
/** Per-node feature flags used for mixed-capability clusters. */
|
|
10328
|
+
features: z.object({
|
|
10329
|
+
sandbox: z.boolean().optional()
|
|
10330
|
+
}).optional()
|
|
10331
|
+
}).refine((n) => n.sshKeyPath !== void 0 || n.password !== void 0 || n.sshAgent, {
|
|
10332
|
+
message: "Each node must have either sshKeyPath, password, or sshAgent"
|
|
9718
10333
|
});
|
|
9719
10334
|
var ClusterProviderSchema = z.enum(["ssh"]);
|
|
9720
10335
|
var ClusterConfigSchema = z.object({
|
|
@@ -9726,7 +10341,9 @@ var ClusterConfigSchema = z.object({
|
|
|
9726
10341
|
/** List of nodes */
|
|
9727
10342
|
nodes: z.array(NodeConfigSchema).min(1),
|
|
9728
10343
|
/** Optional k3s installer settings for restricted networks or pinned versions. */
|
|
9729
|
-
install: ClusterInstallConfigSchema.optional()
|
|
10344
|
+
install: ClusterInstallConfigSchema.optional(),
|
|
10345
|
+
/** Optional cluster capabilities managed by cluster init/apply. */
|
|
10346
|
+
features: ClusterFeaturesSchema.optional()
|
|
9730
10347
|
}).refine((c) => c.nodes.filter((n) => n.role === "master").length === 1, {
|
|
9731
10348
|
message: "Cluster must have exactly one master node"
|
|
9732
10349
|
});
|
|
@@ -9749,7 +10366,7 @@ function readClusterConfig(filePath) {
|
|
|
9749
10366
|
const abs = resolve2(filePath);
|
|
9750
10367
|
let raw;
|
|
9751
10368
|
try {
|
|
9752
|
-
raw = JSON.parse(
|
|
10369
|
+
raw = JSON.parse(readFileSync3(abs, "utf8"));
|
|
9753
10370
|
} catch (err) {
|
|
9754
10371
|
throw new Error(`Failed to read cluster config at ${abs}: ${err.message}`);
|
|
9755
10372
|
}
|
|
@@ -9762,14 +10379,57 @@ ${issues.join("\n")}`);
|
|
|
9762
10379
|
return result.data;
|
|
9763
10380
|
}
|
|
9764
10381
|
function resolveNodeCredentials(node) {
|
|
10382
|
+
let sshAgent;
|
|
10383
|
+
if (node.sshAgent === true) {
|
|
10384
|
+
sshAgent = process.env.SSH_AUTH_SOCK;
|
|
10385
|
+
if (!sshAgent) {
|
|
10386
|
+
throw new Error("SSH_AUTH_SOCK is not set (required by cluster.json sshAgent=true)");
|
|
10387
|
+
}
|
|
10388
|
+
} else if (typeof node.sshAgent === "string") {
|
|
10389
|
+
sshAgent = resolveEnvTemplate(node.sshAgent);
|
|
10390
|
+
}
|
|
9765
10391
|
return {
|
|
9766
10392
|
host: node.host,
|
|
9767
10393
|
port: node.port,
|
|
9768
10394
|
user: node.user,
|
|
9769
10395
|
sshKeyPath: node.sshKeyPath ? expandHome(node.sshKeyPath) : void 0,
|
|
10396
|
+
sshKeyPassphrase: node.sshKeyPassphrase ? resolveEnvTemplate(node.sshKeyPassphrase) : void 0,
|
|
10397
|
+
sshAgent,
|
|
9770
10398
|
password: node.password ? resolveEnvTemplate(node.password) : void 0
|
|
9771
10399
|
};
|
|
9772
10400
|
}
|
|
10401
|
+
function resolveNodeInstallConfig(clusterInstall, node) {
|
|
10402
|
+
const merged = { ...clusterInstall, ...node.install };
|
|
10403
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
10404
|
+
}
|
|
10405
|
+
function defaultAgentSandboxManifestUrls(version = AGENT_SANDBOX_DEFAULT_VERSION) {
|
|
10406
|
+
return [
|
|
10407
|
+
`https://github.com/kubernetes-sigs/agent-sandbox/releases/download/${version}/manifest.yaml`,
|
|
10408
|
+
`https://github.com/kubernetes-sigs/agent-sandbox/releases/download/${version}/extensions.yaml`
|
|
10409
|
+
];
|
|
10410
|
+
}
|
|
10411
|
+
function resolveClusterSandboxConfig(config) {
|
|
10412
|
+
const sandbox = config.features?.sandbox;
|
|
10413
|
+
if (sandbox === void 0 || sandbox === false) return null;
|
|
10414
|
+
const normalized = sandbox === true ? {
|
|
10415
|
+
enabled: true,
|
|
10416
|
+
install: true,
|
|
10417
|
+
version: AGENT_SANDBOX_DEFAULT_VERSION,
|
|
10418
|
+
runtimeClassName: AGENT_SANDBOX_DEFAULT_RUNTIME_CLASS,
|
|
10419
|
+
createRuntimeClass: true,
|
|
10420
|
+
runtimeClassHandler: AGENT_SANDBOX_DEFAULT_RUNTIME_HANDLER,
|
|
10421
|
+
waitTimeoutSeconds: 300,
|
|
10422
|
+
required: true,
|
|
10423
|
+
nodeSelector: { "shadowob.com/sandbox-ready": "true" },
|
|
10424
|
+
smokeTest: false,
|
|
10425
|
+
smokeImage: "busybox:1.36"
|
|
10426
|
+
} : sandbox;
|
|
10427
|
+
if (!normalized.enabled) return null;
|
|
10428
|
+
return {
|
|
10429
|
+
...normalized,
|
|
10430
|
+
manifestUrls: normalized.manifestUrls && normalized.manifestUrls.length > 0 ? normalized.manifestUrls : defaultAgentSandboxManifestUrls(normalized.version)
|
|
10431
|
+
};
|
|
10432
|
+
}
|
|
9773
10433
|
function getMasterNode(config) {
|
|
9774
10434
|
const master = config.nodes.find((n) => n.role === "master");
|
|
9775
10435
|
if (!master) throw new Error("No master node found in cluster config");
|
|
@@ -9831,14 +10491,14 @@ import chalk2 from "chalk";
|
|
|
9831
10491
|
import { Command as Command17 } from "commander";
|
|
9832
10492
|
|
|
9833
10493
|
// src/utils/env.ts
|
|
9834
|
-
import { existsSync as
|
|
10494
|
+
import { existsSync as existsSync5 } from "fs";
|
|
9835
10495
|
import { resolve as resolve3 } from "path";
|
|
9836
10496
|
function loadEnvFiles(paths) {
|
|
9837
10497
|
const loaded = [];
|
|
9838
10498
|
if (paths && paths.length > 0) {
|
|
9839
10499
|
for (const p of paths) {
|
|
9840
10500
|
const abs = resolve3(p);
|
|
9841
|
-
if (!
|
|
10501
|
+
if (!existsSync5(abs)) {
|
|
9842
10502
|
throw new Error(`Env file not found: ${abs}`);
|
|
9843
10503
|
}
|
|
9844
10504
|
process.loadEnvFile(abs);
|
|
@@ -9846,7 +10506,7 @@ function loadEnvFiles(paths) {
|
|
|
9846
10506
|
}
|
|
9847
10507
|
} else {
|
|
9848
10508
|
const defaultPath = resolve3(".env");
|
|
9849
|
-
if (
|
|
10509
|
+
if (existsSync5(defaultPath)) {
|
|
9850
10510
|
process.loadEnvFile(defaultPath);
|
|
9851
10511
|
loaded.push(defaultPath);
|
|
9852
10512
|
}
|
|
@@ -9856,12 +10516,51 @@ function loadEnvFiles(paths) {
|
|
|
9856
10516
|
|
|
9857
10517
|
// src/interfaces/cli/build.command.ts
|
|
9858
10518
|
import { spawn as spawn2 } from "child_process";
|
|
9859
|
-
import { existsSync as
|
|
10519
|
+
import { existsSync as existsSync6, mkdtempSync as mkdtempSync3, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
9860
10520
|
import { tmpdir as tmpdir3 } from "os";
|
|
9861
|
-
import { join as
|
|
10521
|
+
import { join as join5, resolve as resolve4 } from "path";
|
|
9862
10522
|
import { Command } from "commander";
|
|
9863
10523
|
|
|
9864
10524
|
// src/infra/plugin-k8s.ts
|
|
10525
|
+
var COLON_SEPARATED_ENV_KEYS = /* @__PURE__ */ new Set(["PATH", "PYTHONPATH", "NODE_PATH"]);
|
|
10526
|
+
var DEFAULT_CONTAINER_PATH_PARTS = [
|
|
10527
|
+
"/usr/local/sbin",
|
|
10528
|
+
"/usr/local/bin",
|
|
10529
|
+
"/usr/sbin",
|
|
10530
|
+
"/usr/bin",
|
|
10531
|
+
"/sbin",
|
|
10532
|
+
"/bin"
|
|
10533
|
+
];
|
|
10534
|
+
function uniquePathParts(parts) {
|
|
10535
|
+
const out = [];
|
|
10536
|
+
for (const part of parts) {
|
|
10537
|
+
if (!part || out.includes(part)) continue;
|
|
10538
|
+
out.push(part);
|
|
10539
|
+
}
|
|
10540
|
+
return out;
|
|
10541
|
+
}
|
|
10542
|
+
function mergeColonSeparatedEnvValue(key, current, next) {
|
|
10543
|
+
const parts = [...current.split(":"), ...next.split(":")];
|
|
10544
|
+
if (key !== "PATH") return uniquePathParts(parts).join(":");
|
|
10545
|
+
const defaults = new Set(DEFAULT_CONTAINER_PATH_PARTS);
|
|
10546
|
+
const pluginParts = parts.filter((part) => part && !defaults.has(part));
|
|
10547
|
+
const defaultParts = parts.filter((part) => defaults.has(part));
|
|
10548
|
+
return uniquePathParts([...pluginParts, ...defaultParts]).join(":");
|
|
10549
|
+
}
|
|
10550
|
+
function mergePluginEnvVars(target, incoming) {
|
|
10551
|
+
for (const envVar of incoming) {
|
|
10552
|
+
if (typeof envVar.name === "string" && typeof envVar.value === "string" && COLON_SEPARATED_ENV_KEYS.has(envVar.name)) {
|
|
10553
|
+
const existing = target.find(
|
|
10554
|
+
(candidate) => candidate.name === envVar.name && typeof candidate.value === "string"
|
|
10555
|
+
);
|
|
10556
|
+
if (existing && typeof existing.value === "string") {
|
|
10557
|
+
existing.value = mergeColonSeparatedEnvValue(envVar.name, existing.value, envVar.value);
|
|
10558
|
+
continue;
|
|
10559
|
+
}
|
|
10560
|
+
}
|
|
10561
|
+
target.push(envVar);
|
|
10562
|
+
}
|
|
10563
|
+
}
|
|
9865
10564
|
function collectPluginK8sArtifacts(agent, config, namespace) {
|
|
9866
10565
|
const result = {
|
|
9867
10566
|
initContainers: [],
|
|
@@ -9899,9 +10598,7 @@ function collectPluginK8sArtifacts(agent, config, namespace) {
|
|
|
9899
10598
|
if (artifacts.volumeMounts?.length) {
|
|
9900
10599
|
result.volumeMounts.push(...artifacts.volumeMounts);
|
|
9901
10600
|
}
|
|
9902
|
-
if (artifacts.envVars?.length)
|
|
9903
|
-
result.envVars.push(...artifacts.envVars);
|
|
9904
|
-
}
|
|
10601
|
+
if (artifacts.envVars?.length) mergePluginEnvVars(result.envVars, artifacts.envVars);
|
|
9905
10602
|
if (artifacts.labels) {
|
|
9906
10603
|
Object.assign(result.labels, artifacts.labels);
|
|
9907
10604
|
}
|
|
@@ -9932,7 +10629,7 @@ function createBuildCommand(container) {
|
|
|
9932
10629
|
return new Command("build").description("Build Docker images for agents using build-image git source strategy").option("-f, --file <path>", "Config file path", "shadowob-cloud.json").option("-a, --agent <id>", "Build a specific agent (default: all build-image agents)").option("--push", "Push image(s) after building").option("--no-cache", "Pass --no-cache to docker build").option("--platform <platform>", "Target platform(s) e.g. linux/amd64,linux/arm64").option("--tag <tag>", "Override image tag (only valid with --agent)").option("--output-dockerfile", "Print generated Dockerfile(s) to stdout, skip building").action(
|
|
9933
10630
|
async (options) => {
|
|
9934
10631
|
const filePath = resolve4(options.file);
|
|
9935
|
-
if (!
|
|
10632
|
+
if (!existsSync6(filePath)) {
|
|
9936
10633
|
container.logger.error(`Config file not found: ${filePath}`);
|
|
9937
10634
|
process.exit(1);
|
|
9938
10635
|
}
|
|
@@ -9972,8 +10669,8 @@ function createBuildCommand(container) {
|
|
|
9972
10669
|
continue;
|
|
9973
10670
|
}
|
|
9974
10671
|
container.logger.step(`Building image for ${agent.id}: ${imageTag2}`);
|
|
9975
|
-
const tmpDir = mkdtempSync3(
|
|
9976
|
-
const dockerfilePath =
|
|
10672
|
+
const tmpDir = mkdtempSync3(join5(tmpdir3(), `shadowob-cloud-build-${agent.id}-`));
|
|
10673
|
+
const dockerfilePath = join5(tmpDir, "Dockerfile");
|
|
9977
10674
|
writeFileSync4(dockerfilePath, dockerfile, "utf-8");
|
|
9978
10675
|
try {
|
|
9979
10676
|
const buildArgs = ["build", "-t", imageTag2, "-f", dockerfilePath, tmpDir];
|
|
@@ -10013,21 +10710,35 @@ import chalk from "chalk";
|
|
|
10013
10710
|
import { Command as Command2 } from "commander";
|
|
10014
10711
|
function createClusterCommand(container) {
|
|
10015
10712
|
const cluster = new Command2("cluster").description(
|
|
10016
|
-
"Manage bare-server k3s clusters (init, status, list, destroy)"
|
|
10713
|
+
"Manage bare-server k3s clusters (init, apply, status, list, destroy)"
|
|
10017
10714
|
);
|
|
10018
|
-
|
|
10715
|
+
async function applyClusterConfig(options) {
|
|
10716
|
+
const config = readClusterConfig(options.config);
|
|
10717
|
+
container.logger.info(`Applying cluster "${config.name}" from ${options.config}...`);
|
|
10718
|
+
const meta = await container.cluster.init(
|
|
10719
|
+
config,
|
|
10720
|
+
(msg) => {
|
|
10721
|
+
container.logger.dim(msg);
|
|
10722
|
+
},
|
|
10723
|
+
options.force
|
|
10724
|
+
);
|
|
10725
|
+
container.logger.success(`Cluster "${meta.name}" ready! Kubeconfig: ${meta.kubeconfigPath}`);
|
|
10726
|
+
container.logger.info(
|
|
10727
|
+
`Edit ${options.config} and run this command again to add newly listed nodes.`
|
|
10728
|
+
);
|
|
10729
|
+
container.logger.info(`Deploy agents: shadowob-cloud up --cluster ${meta.name}`);
|
|
10730
|
+
}
|
|
10731
|
+
cluster.command("init").description("Bootstrap or update a k3s cluster on bare servers").option("-c, --config <path>", "Path to cluster.json", "cluster.json").option("--force", "Reinstall k3s even if already installed on nodes").action(async (options) => {
|
|
10019
10732
|
try {
|
|
10020
|
-
|
|
10021
|
-
|
|
10022
|
-
|
|
10023
|
-
|
|
10024
|
-
|
|
10025
|
-
|
|
10026
|
-
|
|
10027
|
-
|
|
10028
|
-
);
|
|
10029
|
-
container.logger.success(`Cluster "${meta.name}" ready! Kubeconfig: ${meta.kubeconfigPath}`);
|
|
10030
|
-
container.logger.info(`Deploy agents: shadowob-cloud up --cluster ${meta.name}`);
|
|
10733
|
+
await applyClusterConfig(options);
|
|
10734
|
+
} catch (err) {
|
|
10735
|
+
container.logger.error(err.message);
|
|
10736
|
+
process.exit(1);
|
|
10737
|
+
}
|
|
10738
|
+
});
|
|
10739
|
+
cluster.command("apply").description("Apply cluster.json idempotently and add newly listed nodes").option("-c, --config <path>", "Path to cluster.json", "cluster.json").option("--force", "Reinstall k3s even if already installed on nodes").action(async (options) => {
|
|
10740
|
+
try {
|
|
10741
|
+
await applyClusterConfig(options);
|
|
10031
10742
|
} catch (err) {
|
|
10032
10743
|
container.logger.error(err.message);
|
|
10033
10744
|
process.exit(1);
|
|
@@ -10144,13 +10855,13 @@ function createClusterCommand(container) {
|
|
|
10144
10855
|
}
|
|
10145
10856
|
|
|
10146
10857
|
// src/interfaces/cli/costs.command.ts
|
|
10147
|
-
import { existsSync as
|
|
10858
|
+
import { existsSync as existsSync7 } from "fs";
|
|
10148
10859
|
import { resolve as resolve5 } from "path";
|
|
10149
10860
|
import { Command as Command3 } from "commander";
|
|
10150
10861
|
async function resolveNamespace(container, file, namespace) {
|
|
10151
10862
|
if (namespace) return namespace;
|
|
10152
10863
|
const filePath = resolve5(file);
|
|
10153
|
-
if (!
|
|
10864
|
+
if (!existsSync7(filePath)) return "shadowob-cloud";
|
|
10154
10865
|
try {
|
|
10155
10866
|
const config = await container.config.parseFile(filePath);
|
|
10156
10867
|
return config.deployments?.namespace ?? "shadowob-cloud";
|
|
@@ -10220,7 +10931,7 @@ function createCostsCommand(container) {
|
|
|
10220
10931
|
|
|
10221
10932
|
// src/interfaces/cli/doctor.command.ts
|
|
10222
10933
|
import { execSync } from "child_process";
|
|
10223
|
-
import { existsSync as
|
|
10934
|
+
import { existsSync as existsSync8, readFileSync as readFileSync4 } from "fs";
|
|
10224
10935
|
import { platform } from "os";
|
|
10225
10936
|
import { Command as Command4 } from "commander";
|
|
10226
10937
|
function getVersion(cmd, versionFlag = "--version") {
|
|
@@ -10374,10 +11085,10 @@ function createDoctorCommand(container) {
|
|
|
10374
11085
|
});
|
|
10375
11086
|
}
|
|
10376
11087
|
}
|
|
10377
|
-
if (
|
|
11088
|
+
if (existsSync8(".env")) {
|
|
10378
11089
|
let inGitignore = false;
|
|
10379
|
-
if (
|
|
10380
|
-
const gitignore =
|
|
11090
|
+
if (existsSync8(".gitignore")) {
|
|
11091
|
+
const gitignore = readFileSync4(".gitignore", "utf-8");
|
|
10381
11092
|
inGitignore = gitignore.split("\n").some((line) => line.trim() === ".env");
|
|
10382
11093
|
}
|
|
10383
11094
|
secResults.push({
|
|
@@ -10432,7 +11143,7 @@ function createDoctorCommand(container) {
|
|
|
10432
11143
|
}
|
|
10433
11144
|
|
|
10434
11145
|
// src/interfaces/cli/down.command.ts
|
|
10435
|
-
import { existsSync as
|
|
11146
|
+
import { existsSync as existsSync9 } from "fs";
|
|
10436
11147
|
import { resolve as resolve6 } from "path";
|
|
10437
11148
|
import { Command as Command5 } from "commander";
|
|
10438
11149
|
function createDownCommand(container) {
|
|
@@ -10441,7 +11152,7 @@ function createDownCommand(container) {
|
|
|
10441
11152
|
const filePath = resolve6(options.file);
|
|
10442
11153
|
let namespace = options.namespace;
|
|
10443
11154
|
let config;
|
|
10444
|
-
if (
|
|
11155
|
+
if (existsSync9(filePath)) {
|
|
10445
11156
|
try {
|
|
10446
11157
|
config = await container.config.parseFile(filePath);
|
|
10447
11158
|
namespace = namespace ?? config.deployments?.namespace;
|
|
@@ -10510,7 +11221,7 @@ function createDownCommand(container) {
|
|
|
10510
11221
|
}
|
|
10511
11222
|
|
|
10512
11223
|
// src/interfaces/cli/generate.command.ts
|
|
10513
|
-
import { existsSync as
|
|
11224
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
10514
11225
|
import { dirname, resolve as resolve7 } from "path";
|
|
10515
11226
|
import { fileURLToPath } from "url";
|
|
10516
11227
|
import { Command as Command6 } from "commander";
|
|
@@ -10520,7 +11231,7 @@ function createGenerateCommand(container) {
|
|
|
10520
11231
|
new Command6("manifests").description("Generate K8s YAML/JSON manifests").option("-f, --file <path>", "Config file path", "shadowob-cloud.json").option("-o, --output <dir>", "Output directory", ".shadowob-manifests").option("-n, --namespace <ns>", "Kubernetes namespace").option("--provision-url <url>", "Shadow server URL (for NetworkPolicy egress)").action(
|
|
10521
11232
|
async (options) => {
|
|
10522
11233
|
const filePath = resolve7(options.file);
|
|
10523
|
-
if (!
|
|
11234
|
+
if (!existsSync10(filePath)) {
|
|
10524
11235
|
container.logger.error(`Config file not found: ${filePath}`);
|
|
10525
11236
|
process.exit(1);
|
|
10526
11237
|
}
|
|
@@ -10553,11 +11264,11 @@ function createGenerateCommand(container) {
|
|
|
10553
11264
|
cmd.addCommand(
|
|
10554
11265
|
new Command6("openclaw-config").description("Generate OpenClaw config.json for a specific agent").argument("<agent>", "Agent ID").option("-f, --file <path>", "Config file path", "shadowob-cloud.json").option("-o, --output <path>", "Output file path").action(async (agent, options) => {
|
|
10555
11266
|
const filePath = resolve7(options.file);
|
|
10556
|
-
if (!
|
|
11267
|
+
if (!existsSync10(filePath)) {
|
|
10557
11268
|
container.logger.error(`Config file not found: ${filePath}`);
|
|
10558
11269
|
process.exit(1);
|
|
10559
11270
|
}
|
|
10560
|
-
const { loadAllPlugins, getPluginRegistry: getPluginRegistry2 } = await import("./plugins-
|
|
11271
|
+
const { loadAllPlugins, getPluginRegistry: getPluginRegistry2 } = await import("./plugins-2MITZ4ZD.js");
|
|
10561
11272
|
await loadAllPlugins(getPluginRegistry2());
|
|
10562
11273
|
const outputPath = options.output ? resolve7(options.output) : void 0;
|
|
10563
11274
|
const configCwd = dirname(filePath);
|
|
@@ -10589,13 +11300,13 @@ function createGenerateCommand(container) {
|
|
|
10589
11300
|
"schemas",
|
|
10590
11301
|
"config.schema.json"
|
|
10591
11302
|
);
|
|
10592
|
-
if (!
|
|
11303
|
+
if (!existsSync10(schemaPath)) {
|
|
10593
11304
|
container.logger.error(
|
|
10594
11305
|
"Schema file not found. Run `pnpm generate:schema` in the cloud package first."
|
|
10595
11306
|
);
|
|
10596
11307
|
process.exit(1);
|
|
10597
11308
|
}
|
|
10598
|
-
const schema =
|
|
11309
|
+
const schema = readFileSync5(schemaPath, "utf-8");
|
|
10599
11310
|
const outPath = resolve7(options.output);
|
|
10600
11311
|
mkdirSync3(dirname(outPath), { recursive: true });
|
|
10601
11312
|
writeFileSync5(outPath, schema, "utf-8");
|
|
@@ -10613,7 +11324,7 @@ import { Command as Command7 } from "commander";
|
|
|
10613
11324
|
|
|
10614
11325
|
// src/services/image.service.ts
|
|
10615
11326
|
import { execFileSync as execFileSync2, spawn as spawn3 } from "child_process";
|
|
10616
|
-
import { existsSync as
|
|
11327
|
+
import { existsSync as existsSync11 } from "fs";
|
|
10617
11328
|
import { resolve as resolve8 } from "path";
|
|
10618
11329
|
var IMAGES = [
|
|
10619
11330
|
"openclaw-runner",
|
|
@@ -10650,7 +11361,7 @@ var ImageService = class {
|
|
|
10650
11361
|
const { name, tag = "latest", noCache, intoK8s, push, platform: platform3 } = options;
|
|
10651
11362
|
const dockerfilePath = resolve8(this.imagesDir, name, "Dockerfile");
|
|
10652
11363
|
const buildContext = this.getBuildContext(name);
|
|
10653
|
-
if (!
|
|
11364
|
+
if (!existsSync11(dockerfilePath)) {
|
|
10654
11365
|
throw new Error(`Dockerfile not found: ${dockerfilePath}`);
|
|
10655
11366
|
}
|
|
10656
11367
|
if (!IMAGES.includes(name)) {
|
|
@@ -10691,14 +11402,14 @@ var ImageService = class {
|
|
|
10691
11402
|
list() {
|
|
10692
11403
|
return IMAGES.map((name) => ({
|
|
10693
11404
|
name,
|
|
10694
|
-
hasDockerfile:
|
|
11405
|
+
hasDockerfile: existsSync11(resolve8(this.imagesDir, name, "Dockerfile"))
|
|
10695
11406
|
}));
|
|
10696
11407
|
}
|
|
10697
11408
|
getBuildContext(name) {
|
|
10698
11409
|
if (name !== "openclaw-runner") return resolve8(this.imagesDir, name);
|
|
10699
11410
|
const repoRoot = resolve8(this.imagesDir, "../../..");
|
|
10700
11411
|
const localShadowobPlugin = resolve8(repoRoot, "packages", "openclaw-shadowob", "package.json");
|
|
10701
|
-
return
|
|
11412
|
+
return existsSync11(localShadowobPlugin) ? repoRoot : resolve8(this.imagesDir, name);
|
|
10702
11413
|
}
|
|
10703
11414
|
dockerBuild(args) {
|
|
10704
11415
|
return this.spawnProcess("docker", ["build", ...args]);
|
|
@@ -10780,7 +11491,7 @@ function createImagesCommand(container) {
|
|
|
10780
11491
|
}
|
|
10781
11492
|
|
|
10782
11493
|
// src/interfaces/cli/logs.command.ts
|
|
10783
|
-
import { existsSync as
|
|
11494
|
+
import { existsSync as existsSync12 } from "fs";
|
|
10784
11495
|
import { resolve as resolve9 } from "path";
|
|
10785
11496
|
import { Command as Command8 } from "commander";
|
|
10786
11497
|
function createLogsCommand(container) {
|
|
@@ -10789,7 +11500,7 @@ function createLogsCommand(container) {
|
|
|
10789
11500
|
let namespace = options.namespace;
|
|
10790
11501
|
if (!namespace) {
|
|
10791
11502
|
const filePath = resolve9(options.file);
|
|
10792
|
-
if (
|
|
11503
|
+
if (existsSync12(filePath)) {
|
|
10793
11504
|
try {
|
|
10794
11505
|
const config = await container.config.parseFile(filePath);
|
|
10795
11506
|
namespace = config.deployments?.namespace;
|
|
@@ -10835,7 +11546,7 @@ function createLogsCommand(container) {
|
|
|
10835
11546
|
|
|
10836
11547
|
// src/interfaces/cli/onboard.command.ts
|
|
10837
11548
|
import { execSync as execSync2 } from "child_process";
|
|
10838
|
-
import { existsSync as
|
|
11549
|
+
import { existsSync as existsSync13 } from "fs";
|
|
10839
11550
|
import { platform as platform2 } from "os";
|
|
10840
11551
|
import { createInterface } from "readline";
|
|
10841
11552
|
import { Command as Command9 } from "commander";
|
|
@@ -10960,12 +11671,12 @@ function createOnboardCommand(container) {
|
|
|
10960
11671
|
container.logger.step("Step 3/4 \u2014 Configuration...");
|
|
10961
11672
|
console.log();
|
|
10962
11673
|
const configPath = "shadowob-cloud.json";
|
|
10963
|
-
if (
|
|
11674
|
+
if (existsSync13(configPath)) {
|
|
10964
11675
|
container.logger.success(`Config file found: ${configPath}`);
|
|
10965
11676
|
} else {
|
|
10966
11677
|
const answer = await ask(` No ${configPath} found. Create one from template? [Y/n] `);
|
|
10967
11678
|
if (!answer || answer.toLowerCase() !== "n") {
|
|
10968
|
-
const { createInitCommand: createInitCommand2 } = await import("./init.command-
|
|
11679
|
+
const { createInitCommand: createInitCommand2 } = await import("./init.command-UNL66BMR.js");
|
|
10969
11680
|
const init = createInitCommand2(container);
|
|
10970
11681
|
await init.parseAsync(["--quick"], { from: "user" });
|
|
10971
11682
|
} else {
|
|
@@ -10982,14 +11693,14 @@ function createOnboardCommand(container) {
|
|
|
10982
11693
|
}
|
|
10983
11694
|
container.logger.success("Onboarding complete! Starting console...");
|
|
10984
11695
|
console.log();
|
|
10985
|
-
const { createConsoleCommand: createConsoleCommand2 } = await import("./dashboard.command-
|
|
11696
|
+
const { createConsoleCommand: createConsoleCommand2 } = await import("./dashboard.command-GUHSJ2CN.js");
|
|
10986
11697
|
const console_ = createConsoleCommand2(container);
|
|
10987
11698
|
await console_.parseAsync([], { from: "user" });
|
|
10988
11699
|
});
|
|
10989
11700
|
}
|
|
10990
11701
|
|
|
10991
11702
|
// src/interfaces/cli/provision.command.ts
|
|
10992
|
-
import { existsSync as
|
|
11703
|
+
import { existsSync as existsSync14 } from "fs";
|
|
10993
11704
|
import { resolve as resolve10 } from "path";
|
|
10994
11705
|
import { Command as Command10 } from "commander";
|
|
10995
11706
|
function shadowBuddyTokenEnvKey(id) {
|
|
@@ -10999,7 +11710,7 @@ function createProvisionCommand(container) {
|
|
|
10999
11710
|
return new Command10("provision").description("Provision Shadow resources (servers, channels, buddies) without deploying").option("-f, --file <path>", "Config file path", "shadowob-cloud.json").option("--provision-url <url>", "Shadow server URL").option("--provision-token <token>", "Shadow user token").option("--dry-run", "Preview what would be provisioned").option("--output <path>", "Write provisioned tokens to file").option("--force", "Force re-provisioning even if state shows resources exist").option("--state-dir <dir>", "Subdirectory for provision state (default: .shadowob)").action(
|
|
11000
11711
|
async (options) => {
|
|
11001
11712
|
const filePath = resolve10(options.file);
|
|
11002
|
-
if (!
|
|
11713
|
+
if (!existsSync14(filePath)) {
|
|
11003
11714
|
container.logger.error(`Config file not found: ${filePath}`);
|
|
11004
11715
|
process.exit(1);
|
|
11005
11716
|
}
|
|
@@ -11020,14 +11731,14 @@ function createProvisionCommand(container) {
|
|
|
11020
11731
|
process.exit(1);
|
|
11021
11732
|
}
|
|
11022
11733
|
try {
|
|
11023
|
-
const { executePluginProvisions, loadAllPlugins, getPluginRegistry: getPluginRegistry2 } = await import("./plugins-
|
|
11734
|
+
const { executePluginProvisions, loadAllPlugins, getPluginRegistry: getPluginRegistry2 } = await import("./plugins-2MITZ4ZD.js");
|
|
11024
11735
|
try {
|
|
11025
11736
|
await loadAllPlugins(getPluginRegistry2());
|
|
11026
11737
|
} catch {
|
|
11027
11738
|
}
|
|
11028
11739
|
const agents = resolved.deployments?.agents ?? [];
|
|
11029
11740
|
const namespace = resolved.deployments?.namespace ?? "shadowob-cloud";
|
|
11030
|
-
const existing = loadProvisionState(filePath, options.stateDir);
|
|
11741
|
+
const existing = options.force ? null : loadProvisionState(filePath, options.stateDir);
|
|
11031
11742
|
const extraSecrets = {
|
|
11032
11743
|
SHADOW_SERVER_URL: shadowUrl,
|
|
11033
11744
|
SHADOW_USER_TOKEN: shadowToken
|
|
@@ -11121,13 +11832,13 @@ function createProvisionCommand(container) {
|
|
|
11121
11832
|
}
|
|
11122
11833
|
|
|
11123
11834
|
// src/interfaces/cli/sandbox.command.ts
|
|
11124
|
-
import { existsSync as
|
|
11835
|
+
import { existsSync as existsSync15 } from "fs";
|
|
11125
11836
|
import { resolve as resolve11 } from "path";
|
|
11126
11837
|
import { Command as Command11 } from "commander";
|
|
11127
11838
|
async function resolveNamespace2(container, options) {
|
|
11128
11839
|
if (options.namespace) return options.namespace;
|
|
11129
11840
|
const filePath = resolve11(options.file ?? "shadowob-cloud.json");
|
|
11130
|
-
if (
|
|
11841
|
+
if (existsSync15(filePath)) {
|
|
11131
11842
|
try {
|
|
11132
11843
|
const config = await container.config.parseFile(filePath);
|
|
11133
11844
|
return config.deployments?.namespace ?? "shadowob-cloud";
|
|
@@ -11228,7 +11939,7 @@ function createSandboxCommand(container) {
|
|
|
11228
11939
|
}
|
|
11229
11940
|
|
|
11230
11941
|
// src/interfaces/cli/scale.command.ts
|
|
11231
|
-
import { existsSync as
|
|
11942
|
+
import { existsSync as existsSync16 } from "fs";
|
|
11232
11943
|
import { resolve as resolve12 } from "path";
|
|
11233
11944
|
import { Command as Command12 } from "commander";
|
|
11234
11945
|
function createScaleCommand(container) {
|
|
@@ -11237,7 +11948,7 @@ function createScaleCommand(container) {
|
|
|
11237
11948
|
let namespace = options.namespace;
|
|
11238
11949
|
if (!namespace) {
|
|
11239
11950
|
const filePath = resolve12(options.file);
|
|
11240
|
-
if (
|
|
11951
|
+
if (existsSync16(filePath)) {
|
|
11241
11952
|
try {
|
|
11242
11953
|
const config = await container.config.parseFile(filePath);
|
|
11243
11954
|
namespace = config.deployments?.namespace;
|
|
@@ -11264,7 +11975,7 @@ function createScaleCommand(container) {
|
|
|
11264
11975
|
}
|
|
11265
11976
|
|
|
11266
11977
|
// src/interfaces/cli/status.command.ts
|
|
11267
|
-
import { existsSync as
|
|
11978
|
+
import { existsSync as existsSync17 } from "fs";
|
|
11268
11979
|
import { resolve as resolve13 } from "path";
|
|
11269
11980
|
import { Command as Command13 } from "commander";
|
|
11270
11981
|
function createStatusCommand(container) {
|
|
@@ -11273,7 +11984,7 @@ function createStatusCommand(container) {
|
|
|
11273
11984
|
let namespace = options.namespace;
|
|
11274
11985
|
let config;
|
|
11275
11986
|
const filePath = resolve13(options.file);
|
|
11276
|
-
if (
|
|
11987
|
+
if (existsSync17(filePath)) {
|
|
11277
11988
|
try {
|
|
11278
11989
|
config = await container.config.parseFile(filePath);
|
|
11279
11990
|
namespace = namespace ?? config.deployments?.namespace;
|
|
@@ -11486,13 +12197,13 @@ function createUpCommand(container) {
|
|
|
11486
12197
|
}
|
|
11487
12198
|
|
|
11488
12199
|
// src/interfaces/cli/validate.command.ts
|
|
11489
|
-
import { existsSync as
|
|
12200
|
+
import { existsSync as existsSync18 } from "fs";
|
|
11490
12201
|
import { resolve as resolve15 } from "path";
|
|
11491
12202
|
import { Command as Command16 } from "commander";
|
|
11492
12203
|
function createValidateCommand(container) {
|
|
11493
12204
|
return new Command16("validate").description("Validate an shadowob-cloud.json config file").option("-f, --file <path>", "Config file path", "shadowob-cloud.json").option("--strict", "Fail on unresolvable env vars").option("--dry-run", "Validate structure without requiring env vars to be set").action(async (options) => {
|
|
11494
12205
|
const filePath = resolve15(options.file);
|
|
11495
|
-
if (!
|
|
12206
|
+
if (!existsSync18(filePath)) {
|
|
11496
12207
|
container.logger.error(`Config file not found: ${filePath}`);
|
|
11497
12208
|
process.exit(1);
|
|
11498
12209
|
}
|
|
@@ -11606,7 +12317,7 @@ function createCLI(container) {
|
|
|
11606
12317
|
|
|
11607
12318
|
// src/dao/template.dao.ts
|
|
11608
12319
|
import { cp, mkdir as mkdir2, readdir, readFile, stat } from "fs/promises";
|
|
11609
|
-
import { join as
|
|
12320
|
+
import { join as join6 } from "path";
|
|
11610
12321
|
var TemplateDao = class {
|
|
11611
12322
|
constructor(templatesDir) {
|
|
11612
12323
|
this.templatesDir = templatesDir;
|
|
@@ -11622,7 +12333,7 @@ var TemplateDao = class {
|
|
|
11622
12333
|
const seenSlugs = /* @__PURE__ */ new Set();
|
|
11623
12334
|
const records = [];
|
|
11624
12335
|
for (const name of dirEntries) {
|
|
11625
|
-
const fullPath =
|
|
12336
|
+
const fullPath = join6(this.templatesDir, name);
|
|
11626
12337
|
let isDir = false;
|
|
11627
12338
|
try {
|
|
11628
12339
|
isDir = (await stat(fullPath)).isDirectory();
|
|
@@ -11631,24 +12342,24 @@ var TemplateDao = class {
|
|
|
11631
12342
|
}
|
|
11632
12343
|
if (!isDir) continue;
|
|
11633
12344
|
const slug = name;
|
|
11634
|
-
const configPath =
|
|
12345
|
+
const configPath = join6(this.templatesDir, slug, "shadowob-cloud.json");
|
|
11635
12346
|
if (!await this.exists(configPath)) continue;
|
|
11636
12347
|
seenSlugs.add(slug);
|
|
11637
|
-
const indexPath =
|
|
12348
|
+
const indexPath = join6(this.templatesDir, slug, "index.json");
|
|
11638
12349
|
const hasIndex = await this.exists(indexPath);
|
|
11639
12350
|
records.push({
|
|
11640
12351
|
slug,
|
|
11641
12352
|
isFolder: true,
|
|
11642
12353
|
configPath,
|
|
11643
12354
|
indexPath: hasIndex ? indexPath : null,
|
|
11644
|
-
dir:
|
|
12355
|
+
dir: join6(this.templatesDir, slug)
|
|
11645
12356
|
});
|
|
11646
12357
|
}
|
|
11647
12358
|
for (const name of dirEntries) {
|
|
11648
12359
|
if (!name.endsWith(".template.json")) continue;
|
|
11649
12360
|
const slug = name.replace(/\.template\.json$/, "");
|
|
11650
12361
|
if (seenSlugs.has(slug)) continue;
|
|
11651
|
-
const configPath =
|
|
12362
|
+
const configPath = join6(this.templatesDir, name);
|
|
11652
12363
|
records.push({
|
|
11653
12364
|
slug,
|
|
11654
12365
|
isFolder: false,
|
|
@@ -11661,19 +12372,19 @@ var TemplateDao = class {
|
|
|
11661
12372
|
}
|
|
11662
12373
|
/** Find a single template by slug. Returns null if not found. */
|
|
11663
12374
|
async findBySlug(slug) {
|
|
11664
|
-
const folderConfig =
|
|
12375
|
+
const folderConfig = join6(this.templatesDir, slug, "shadowob-cloud.json");
|
|
11665
12376
|
if (await this.exists(folderConfig)) {
|
|
11666
|
-
const indexPath =
|
|
12377
|
+
const indexPath = join6(this.templatesDir, slug, "index.json");
|
|
11667
12378
|
const hasIndex = await this.exists(indexPath);
|
|
11668
12379
|
return {
|
|
11669
12380
|
slug,
|
|
11670
12381
|
isFolder: true,
|
|
11671
12382
|
configPath: folderConfig,
|
|
11672
12383
|
indexPath: hasIndex ? indexPath : null,
|
|
11673
|
-
dir:
|
|
12384
|
+
dir: join6(this.templatesDir, slug)
|
|
11674
12385
|
};
|
|
11675
12386
|
}
|
|
11676
|
-
const flatConfig =
|
|
12387
|
+
const flatConfig = join6(this.templatesDir, `${slug}.template.json`);
|
|
11677
12388
|
if (await this.exists(flatConfig)) {
|
|
11678
12389
|
return {
|
|
11679
12390
|
slug,
|
|
@@ -11710,7 +12421,7 @@ var TemplateDao = class {
|
|
|
11710
12421
|
if (record.isFolder) {
|
|
11711
12422
|
await cp(record.dir, destDir, { recursive: true });
|
|
11712
12423
|
} else {
|
|
11713
|
-
await cp(record.configPath,
|
|
12424
|
+
await cp(record.configPath, join6(destDir, "shadowob-cloud.json"));
|
|
11714
12425
|
}
|
|
11715
12426
|
}
|
|
11716
12427
|
async exists(path) {
|
|
@@ -11724,7 +12435,7 @@ var TemplateDao = class {
|
|
|
11724
12435
|
};
|
|
11725
12436
|
|
|
11726
12437
|
// src/cluster/ssh.ts
|
|
11727
|
-
import { readFileSync as
|
|
12438
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
11728
12439
|
import { NodeSSH } from "node-ssh";
|
|
11729
12440
|
var SSHClient = class {
|
|
11730
12441
|
ssh = new NodeSSH();
|
|
@@ -11734,7 +12445,7 @@ var SSHClient = class {
|
|
|
11734
12445
|
host: opts.host,
|
|
11735
12446
|
port: opts.port,
|
|
11736
12447
|
username: opts.user,
|
|
11737
|
-
...opts.sshKeyPath ? { privateKey:
|
|
12448
|
+
...opts.sshAgent ? { agent: opts.sshAgent } : opts.sshKeyPath ? { privateKey: readFileSync6(opts.sshKeyPath, "utf8"), passphrase: opts.sshKeyPassphrase } : { password: opts.password },
|
|
11738
12449
|
// Reasonable timeout for WAN SSH
|
|
11739
12450
|
readyTimeout: 3e4
|
|
11740
12451
|
});
|
|
@@ -11820,9 +12531,14 @@ async function destroyCluster(options) {
|
|
|
11820
12531
|
}
|
|
11821
12532
|
|
|
11822
12533
|
// src/cluster/init.ts
|
|
11823
|
-
|
|
11824
|
-
|
|
11825
|
-
|
|
12534
|
+
import { createHash as createHash2 } from "crypto";
|
|
12535
|
+
|
|
12536
|
+
// src/cluster/sandbox.ts
|
|
12537
|
+
var REQUIRED_AGENT_SANDBOX_CRDS = [
|
|
12538
|
+
"sandboxes.agents.x-k8s.io",
|
|
12539
|
+
"sandboxtemplates.extensions.agents.x-k8s.io",
|
|
12540
|
+
"sandboxclaims.extensions.agents.x-k8s.io"
|
|
12541
|
+
];
|
|
11826
12542
|
function log2(onLog, msg) {
|
|
11827
12543
|
onLog?.(msg);
|
|
11828
12544
|
}
|
|
@@ -11833,6 +12549,291 @@ function asRoot2(shellCommand) {
|
|
|
11833
12549
|
const quoted = shellQuote2(shellCommand);
|
|
11834
12550
|
return `if [ "$(id -u)" -eq 0 ]; then sh -c ${quoted}; else sudo -n sh -c ${quoted}; fi`;
|
|
11835
12551
|
}
|
|
12552
|
+
async function hasAgentSandboxCrds(client) {
|
|
12553
|
+
const result = await client.exec(
|
|
12554
|
+
asRoot2(
|
|
12555
|
+
`k3s kubectl get crd ${REQUIRED_AGENT_SANDBOX_CRDS.map(shellQuote2).join(" ")} >/dev/null`
|
|
12556
|
+
)
|
|
12557
|
+
);
|
|
12558
|
+
return result.code === 0;
|
|
12559
|
+
}
|
|
12560
|
+
async function applyManifestUrl(client, url, onLog) {
|
|
12561
|
+
log2(onLog, `[sandbox] Applying ${url}`);
|
|
12562
|
+
await client.execOrThrow(asRoot2(`k3s kubectl apply -f ${shellQuote2(url)}`), {
|
|
12563
|
+
onStdout: (chunk) => log2(onLog, `[sandbox] ${chunk.trimEnd()}`),
|
|
12564
|
+
onStderr: (chunk) => log2(onLog, `[sandbox] ${chunk.trimEnd()}`),
|
|
12565
|
+
errorMessage: `Failed to apply agent-sandbox manifest ${url}. If this node cannot reach GitHub, set features.sandbox.manifestUrls to mirrored URLs.`
|
|
12566
|
+
});
|
|
12567
|
+
}
|
|
12568
|
+
async function createOrVerifyRuntimeClass(client, options) {
|
|
12569
|
+
if (options.create) {
|
|
12570
|
+
const yaml = [
|
|
12571
|
+
"apiVersion: node.k8s.io/v1",
|
|
12572
|
+
"kind: RuntimeClass",
|
|
12573
|
+
"metadata:",
|
|
12574
|
+
` name: ${options.name}`,
|
|
12575
|
+
`handler: ${options.handler}`,
|
|
12576
|
+
""
|
|
12577
|
+
].join("\n");
|
|
12578
|
+
log2(
|
|
12579
|
+
options.onLog,
|
|
12580
|
+
`[sandbox] Ensuring RuntimeClass ${options.name} uses handler ${options.handler}`
|
|
12581
|
+
);
|
|
12582
|
+
await client.execOrThrow(asRoot2(`cat <<'EOF' | k3s kubectl apply -f -
|
|
12583
|
+
${yaml}EOF`), {
|
|
12584
|
+
errorMessage: `Failed to create RuntimeClass ${options.name}`
|
|
12585
|
+
});
|
|
12586
|
+
return;
|
|
12587
|
+
}
|
|
12588
|
+
log2(options.onLog, `[sandbox] Verifying RuntimeClass ${options.name}`);
|
|
12589
|
+
await client.execOrThrow(asRoot2(`k3s kubectl get runtimeclass ${shellQuote2(options.name)}`), {
|
|
12590
|
+
errorMessage: `RuntimeClass ${options.name} was not found. Install the runtime handler first, or set features.sandbox.createRuntimeClass=true with an explicit runtimeClassHandler.`
|
|
12591
|
+
});
|
|
12592
|
+
}
|
|
12593
|
+
async function waitForAgentSandboxReady2(client, timeoutSeconds, onLog) {
|
|
12594
|
+
const crdNames = REQUIRED_AGENT_SANDBOX_CRDS.map((name) => `crd/${name}`).join(" ");
|
|
12595
|
+
log2(onLog, "[sandbox] Waiting for agent-sandbox CRDs to become Established");
|
|
12596
|
+
await client.execOrThrow(
|
|
12597
|
+
asRoot2(`k3s kubectl wait --for=condition=Established ${crdNames} --timeout=${timeoutSeconds}s`),
|
|
12598
|
+
{ errorMessage: "agent-sandbox CRDs did not become Established" }
|
|
12599
|
+
);
|
|
12600
|
+
log2(onLog, "[sandbox] Waiting for agent-sandbox controller rollout");
|
|
12601
|
+
await client.execOrThrow(
|
|
12602
|
+
asRoot2(
|
|
12603
|
+
`k3s kubectl -n agent-sandbox-system rollout status deployment/agent-sandbox-controller --timeout=${timeoutSeconds}s`
|
|
12604
|
+
),
|
|
12605
|
+
{ errorMessage: "agent-sandbox controller did not become Ready" }
|
|
12606
|
+
);
|
|
12607
|
+
}
|
|
12608
|
+
function nodeLabelArgs(labels) {
|
|
12609
|
+
return Object.entries(labels).map(([key, value]) => `${key}=${value}`).map(shellQuote2).join(" ");
|
|
12610
|
+
}
|
|
12611
|
+
function kubeNodeMatchesConfigNode(kubeNode, node) {
|
|
12612
|
+
const metadata = kubeNode.metadata ?? {};
|
|
12613
|
+
const status = kubeNode.status ?? {};
|
|
12614
|
+
const addresses = Array.isArray(status.addresses) ? status.addresses : [];
|
|
12615
|
+
const candidates = /* @__PURE__ */ new Set([
|
|
12616
|
+
typeof metadata.name === "string" ? metadata.name : "",
|
|
12617
|
+
...addresses.map((address) => address.address).filter((address) => typeof address === "string")
|
|
12618
|
+
]);
|
|
12619
|
+
return candidates.has(node.host);
|
|
12620
|
+
}
|
|
12621
|
+
async function labelConfiguredNodes(client, config, sandboxReady, onLog) {
|
|
12622
|
+
const output2 = await client.execOrThrow(asRoot2("k3s kubectl get nodes -o json"), {
|
|
12623
|
+
errorMessage: "Failed to list Kubernetes nodes for labeling"
|
|
12624
|
+
});
|
|
12625
|
+
const kubeNodes = JSON.parse(output2.stdout).items ?? [];
|
|
12626
|
+
for (const node of config.nodes) {
|
|
12627
|
+
const kubeNode = kubeNodes.find((candidate) => kubeNodeMatchesConfigNode(candidate, node));
|
|
12628
|
+
if (!kubeNode) {
|
|
12629
|
+
log2(onLog, `[sandbox] Could not match cluster.json node ${node.host} to a Kubernetes node`);
|
|
12630
|
+
continue;
|
|
12631
|
+
}
|
|
12632
|
+
const metadata = kubeNode.metadata;
|
|
12633
|
+
const nodeName = metadata.name;
|
|
12634
|
+
if (typeof nodeName !== "string" || !nodeName) continue;
|
|
12635
|
+
const nodeSandboxReady = sandboxReady && (node.features?.sandbox ?? true);
|
|
12636
|
+
const labels = {
|
|
12637
|
+
"shadowob.com/sandbox-ready": nodeSandboxReady ? "true" : "false",
|
|
12638
|
+
...node.region ? { "shadowob.com/region": node.region } : {},
|
|
12639
|
+
...node.labels ?? {}
|
|
12640
|
+
};
|
|
12641
|
+
log2(onLog, `[sandbox] Labeling node ${nodeName}`);
|
|
12642
|
+
await client.execOrThrow(
|
|
12643
|
+
asRoot2(`k3s kubectl label node ${shellQuote2(nodeName)} ${nodeLabelArgs(labels)} --overwrite`),
|
|
12644
|
+
{ errorMessage: `Failed to label Kubernetes node ${nodeName}` }
|
|
12645
|
+
);
|
|
12646
|
+
}
|
|
12647
|
+
}
|
|
12648
|
+
async function runAgentSandboxSmoke(client, options) {
|
|
12649
|
+
const namespace = `shadow-sandbox-smoke-${Date.now().toString(36)}`;
|
|
12650
|
+
const manifest = [
|
|
12651
|
+
{
|
|
12652
|
+
apiVersion: "v1",
|
|
12653
|
+
kind: "Namespace",
|
|
12654
|
+
metadata: { name: namespace }
|
|
12655
|
+
},
|
|
12656
|
+
{
|
|
12657
|
+
apiVersion: "extensions.agents.x-k8s.io/v1alpha1",
|
|
12658
|
+
kind: "SandboxTemplate",
|
|
12659
|
+
metadata: { name: "smoke-template", namespace },
|
|
12660
|
+
spec: {
|
|
12661
|
+
networkPolicyManagement: "Unmanaged",
|
|
12662
|
+
envVarsInjectionPolicy: "Disallowed",
|
|
12663
|
+
podTemplate: {
|
|
12664
|
+
metadata: { labels: { app: "shadow-sandbox-smoke" } },
|
|
12665
|
+
spec: {
|
|
12666
|
+
runtimeClassName: options.runtimeClassName,
|
|
12667
|
+
containers: [
|
|
12668
|
+
{
|
|
12669
|
+
name: "smoke",
|
|
12670
|
+
image: options.image,
|
|
12671
|
+
command: ["sh", "-c", "echo shadow-sandbox-smoke && sleep 30"]
|
|
12672
|
+
}
|
|
12673
|
+
],
|
|
12674
|
+
restartPolicy: "Always"
|
|
12675
|
+
}
|
|
12676
|
+
},
|
|
12677
|
+
volumeClaimTemplates: []
|
|
12678
|
+
}
|
|
12679
|
+
},
|
|
12680
|
+
{
|
|
12681
|
+
apiVersion: "extensions.agents.x-k8s.io/v1alpha1",
|
|
12682
|
+
kind: "SandboxClaim",
|
|
12683
|
+
metadata: { name: "smoke", namespace },
|
|
12684
|
+
spec: {
|
|
12685
|
+
sandboxTemplateRef: { name: "smoke-template" },
|
|
12686
|
+
warmpool: "none",
|
|
12687
|
+
lifecycle: { shutdownPolicy: "Delete" }
|
|
12688
|
+
}
|
|
12689
|
+
}
|
|
12690
|
+
];
|
|
12691
|
+
const yaml = JSON.stringify({ apiVersion: "v1", kind: "List", items: manifest }, null, 2);
|
|
12692
|
+
log2(options.onLog, `[sandbox] Running smoke test in namespace ${namespace}`);
|
|
12693
|
+
try {
|
|
12694
|
+
await client.execOrThrow(asRoot2(`cat <<'EOF' | k3s kubectl apply -f -
|
|
12695
|
+
${yaml}
|
|
12696
|
+
EOF`), {
|
|
12697
|
+
errorMessage: "Failed to create agent-sandbox smoke resources"
|
|
12698
|
+
});
|
|
12699
|
+
await client.execOrThrow(
|
|
12700
|
+
asRoot2(
|
|
12701
|
+
`timeout ${options.timeoutSeconds} sh -c ` + shellQuote2(
|
|
12702
|
+
'until [ "$(k3s kubectl -n ' + namespace + ' get sandboxclaim smoke -o jsonpath="{.status.conditions[?(@.type==\\"Ready\\")].status}" 2>/dev/null)" = "True" ]; do sleep 3; done'
|
|
12703
|
+
)
|
|
12704
|
+
),
|
|
12705
|
+
{ errorMessage: "agent-sandbox smoke SandboxClaim did not become Ready" }
|
|
12706
|
+
);
|
|
12707
|
+
log2(options.onLog, "[sandbox] Smoke test passed");
|
|
12708
|
+
} finally {
|
|
12709
|
+
await client.exec(
|
|
12710
|
+
asRoot2(`k3s kubectl delete namespace ${shellQuote2(namespace)} --ignore-not-found=true`)
|
|
12711
|
+
);
|
|
12712
|
+
}
|
|
12713
|
+
}
|
|
12714
|
+
async function installClusterSandbox(options) {
|
|
12715
|
+
const sandbox = resolveClusterSandboxConfig(options.config);
|
|
12716
|
+
if (!sandbox) return false;
|
|
12717
|
+
const master = getMasterNode(options.config);
|
|
12718
|
+
const creds = resolveNodeCredentials(master);
|
|
12719
|
+
const client = new SSHClient();
|
|
12720
|
+
log2(options.onLog, `[sandbox ${creds.host}] Connecting via SSH...`);
|
|
12721
|
+
await client.connect(creds);
|
|
12722
|
+
try {
|
|
12723
|
+
const alreadyInstalled = await hasAgentSandboxCrds(client);
|
|
12724
|
+
if (!sandbox.install && !alreadyInstalled) {
|
|
12725
|
+
const message = "features.sandbox.enabled=true but the required agent-sandbox CRDs are not installed. Set features.sandbox.install=true or apply the CRDs/controller before using sandbox.";
|
|
12726
|
+
if (sandbox.required) throw new Error(message);
|
|
12727
|
+
log2(options.onLog, `[sandbox] ${message}`);
|
|
12728
|
+
return false;
|
|
12729
|
+
}
|
|
12730
|
+
if (sandbox.install) {
|
|
12731
|
+
log2(options.onLog, `[sandbox] Installing agent-sandbox ${sandbox.version}`);
|
|
12732
|
+
for (const url of sandbox.manifestUrls) {
|
|
12733
|
+
await applyManifestUrl(client, url, options.onLog);
|
|
12734
|
+
}
|
|
12735
|
+
if (sandbox.controllerImage) {
|
|
12736
|
+
log2(options.onLog, `[sandbox] Setting controller image ${sandbox.controllerImage}`);
|
|
12737
|
+
await client.execOrThrow(
|
|
12738
|
+
asRoot2(
|
|
12739
|
+
`k3s kubectl -n agent-sandbox-system set image deployment/agent-sandbox-controller agent-sandbox-controller=${shellQuote2(sandbox.controllerImage)}`
|
|
12740
|
+
),
|
|
12741
|
+
{ errorMessage: "Failed to set agent-sandbox controller image" }
|
|
12742
|
+
);
|
|
12743
|
+
}
|
|
12744
|
+
}
|
|
12745
|
+
await createOrVerifyRuntimeClass(client, {
|
|
12746
|
+
name: sandbox.runtimeClassName,
|
|
12747
|
+
handler: sandbox.runtimeClassHandler,
|
|
12748
|
+
create: sandbox.createRuntimeClass,
|
|
12749
|
+
onLog: options.onLog
|
|
12750
|
+
});
|
|
12751
|
+
await waitForAgentSandboxReady2(client, sandbox.waitTimeoutSeconds, options.onLog);
|
|
12752
|
+
await labelConfiguredNodes(client, options.config, true, options.onLog);
|
|
12753
|
+
if (sandbox.smokeTest) {
|
|
12754
|
+
await runAgentSandboxSmoke(client, {
|
|
12755
|
+
runtimeClassName: sandbox.runtimeClassName,
|
|
12756
|
+
image: sandbox.smokeImage,
|
|
12757
|
+
timeoutSeconds: sandbox.waitTimeoutSeconds,
|
|
12758
|
+
onLog: options.onLog
|
|
12759
|
+
});
|
|
12760
|
+
}
|
|
12761
|
+
log2(
|
|
12762
|
+
options.onLog,
|
|
12763
|
+
`[sandbox] Ready with RuntimeClass ${sandbox.runtimeClassName}; new deployments can use agent-sandbox`
|
|
12764
|
+
);
|
|
12765
|
+
return true;
|
|
12766
|
+
} catch (err) {
|
|
12767
|
+
if (sandbox.required) throw err;
|
|
12768
|
+
log2(
|
|
12769
|
+
options.onLog,
|
|
12770
|
+
`[sandbox] Not ready; deployment fallback remains available: ${err.message}`
|
|
12771
|
+
);
|
|
12772
|
+
return false;
|
|
12773
|
+
} finally {
|
|
12774
|
+
await client.dispose();
|
|
12775
|
+
}
|
|
12776
|
+
}
|
|
12777
|
+
|
|
12778
|
+
// src/cluster/init.ts
|
|
12779
|
+
var K3S_INSTALL_URL = "https://get.k3s.io";
|
|
12780
|
+
var CN_PAUSE_IMAGE = "registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.6";
|
|
12781
|
+
var CN_SYSTEM_DEFAULT_REGISTRY = "registry.cn-hangzhou.aliyuncs.com";
|
|
12782
|
+
function log3(onLog, msg) {
|
|
12783
|
+
onLog?.(msg);
|
|
12784
|
+
}
|
|
12785
|
+
function shellQuote3(value) {
|
|
12786
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
12787
|
+
}
|
|
12788
|
+
function asRoot3(shellCommand) {
|
|
12789
|
+
const quoted = shellQuote3(shellCommand);
|
|
12790
|
+
return `if [ "$(id -u)" -eq 0 ]; then sh -c ${quoted}; else sudo -n sh -c ${quoted}; fi`;
|
|
12791
|
+
}
|
|
12792
|
+
function resolveEnvTemplates(value) {
|
|
12793
|
+
if (typeof value === "string") {
|
|
12794
|
+
return value.replace(/\$\{env:([^}]+)\}/g, (_match, envKey) => {
|
|
12795
|
+
const envVal = process.env[envKey];
|
|
12796
|
+
if (envVal === void 0) {
|
|
12797
|
+
throw new Error(`Environment variable "${envKey}" is not set (required by cluster.json)`);
|
|
12798
|
+
}
|
|
12799
|
+
return envVal;
|
|
12800
|
+
});
|
|
12801
|
+
}
|
|
12802
|
+
if (Array.isArray(value)) return value.map(resolveEnvTemplates);
|
|
12803
|
+
if (value && typeof value === "object") {
|
|
12804
|
+
return Object.fromEntries(
|
|
12805
|
+
Object.entries(value).map(([key, child]) => [key, resolveEnvTemplates(child)])
|
|
12806
|
+
);
|
|
12807
|
+
}
|
|
12808
|
+
return value;
|
|
12809
|
+
}
|
|
12810
|
+
function clusterConfigHash(config) {
|
|
12811
|
+
return createHash2("sha256").update(stableStringify(config)).digest("hex");
|
|
12812
|
+
}
|
|
12813
|
+
function stableStringify(value) {
|
|
12814
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
12815
|
+
if (value && typeof value === "object") {
|
|
12816
|
+
return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, child]) => `${JSON.stringify(key)}:${stableStringify(child)}`).join(",")}}`;
|
|
12817
|
+
}
|
|
12818
|
+
return JSON.stringify(value);
|
|
12819
|
+
}
|
|
12820
|
+
async function ensureK3sRegistriesConfig(client, install2, serviceName, onLog) {
|
|
12821
|
+
if (!install2?.registries) return;
|
|
12822
|
+
const registries = JSON.stringify(resolveEnvTemplates(install2.registries), null, 2);
|
|
12823
|
+
log3(onLog, `[${serviceName}] Writing k3s containerd registries.yaml`);
|
|
12824
|
+
await client.execOrThrow(
|
|
12825
|
+
asRoot3(
|
|
12826
|
+
[
|
|
12827
|
+
"mkdir -p /etc/rancher/k3s",
|
|
12828
|
+
`cat > /etc/rancher/k3s/registries.yaml <<'EOF'
|
|
12829
|
+
${registries}
|
|
12830
|
+
EOF`,
|
|
12831
|
+
`if command -v systemctl >/dev/null 2>&1 && systemctl is-active --quiet ${serviceName}; then systemctl restart ${serviceName}; fi`
|
|
12832
|
+
].join("\n")
|
|
12833
|
+
),
|
|
12834
|
+
{ errorMessage: `Failed to write k3s registries.yaml for ${serviceName}` }
|
|
12835
|
+
);
|
|
12836
|
+
}
|
|
11836
12837
|
function resolveK3sMirror(install2) {
|
|
11837
12838
|
return process.env.INSTALL_K3S_MIRROR ?? process.env.K3S_INSTALL_MIRROR ?? install2?.k3sMirror;
|
|
11838
12839
|
}
|
|
@@ -11901,7 +12902,7 @@ function k3sInstallEnv(extra, install2) {
|
|
|
11901
12902
|
INSTALL_K3S_VERSION: version,
|
|
11902
12903
|
...extra
|
|
11903
12904
|
};
|
|
11904
|
-
return Object.entries(env).filter((entry) => Boolean(entry[1])).map(([key, value]) => `${key}=${
|
|
12905
|
+
return Object.entries(env).filter((entry) => Boolean(entry[1])).map(([key, value]) => `${key}=${shellQuote3(value)}`).join(" ");
|
|
11905
12906
|
}
|
|
11906
12907
|
async function isK3sInstalled(client) {
|
|
11907
12908
|
const result = await client.exec("which k3s");
|
|
@@ -11910,25 +12911,26 @@ async function isK3sInstalled(client) {
|
|
|
11910
12911
|
async function installMaster(master, install2, force, onLog) {
|
|
11911
12912
|
const creds = resolveNodeCredentials(master);
|
|
11912
12913
|
const client = new SSHClient();
|
|
11913
|
-
|
|
12914
|
+
log3(onLog, `[master ${creds.host}] Connecting via SSH...`);
|
|
11914
12915
|
await client.connect(creds);
|
|
11915
12916
|
try {
|
|
12917
|
+
await ensureK3sRegistriesConfig(client, install2, "k3s", onLog);
|
|
11916
12918
|
const alreadyInstalled = await isK3sInstalled(client);
|
|
11917
12919
|
if (alreadyInstalled && !force) {
|
|
11918
|
-
|
|
12920
|
+
log3(
|
|
11919
12921
|
onLog,
|
|
11920
12922
|
`[master ${creds.host}] k3s already installed \u2014 skipping install (use --force to reinstall)`
|
|
11921
12923
|
);
|
|
11922
|
-
|
|
12924
|
+
log3(onLog, `[master ${creds.host}] Reading existing token and kubeconfig...`);
|
|
11923
12925
|
} else {
|
|
11924
12926
|
if (alreadyInstalled && force) {
|
|
11925
|
-
|
|
11926
|
-
await client.exec(
|
|
12927
|
+
log3(onLog, `[master ${creds.host}] k3s already installed \u2014 reinstalling (--force)`);
|
|
12928
|
+
await client.exec(asRoot3("/usr/local/bin/k3s-uninstall.sh 2>/dev/null || true"));
|
|
11927
12929
|
}
|
|
11928
|
-
|
|
12930
|
+
log3(onLog, `[master ${creds.host}] Installing k3s server...`);
|
|
11929
12931
|
await client.execOrThrow(
|
|
11930
|
-
|
|
11931
|
-
`curl -sfL ${
|
|
12932
|
+
asRoot3(
|
|
12933
|
+
`curl -sfL ${shellQuote3(K3S_INSTALL_URL)} | ${k3sInstallEnv(
|
|
11932
12934
|
{
|
|
11933
12935
|
INSTALL_K3S_EXEC: k3sExec(["server", "--tls-san", creds.host], install2)
|
|
11934
12936
|
},
|
|
@@ -11936,28 +12938,28 @@ async function installMaster(master, install2, force, onLog) {
|
|
|
11936
12938
|
)} sh -`
|
|
11937
12939
|
),
|
|
11938
12940
|
{
|
|
11939
|
-
onStdout: (c) =>
|
|
11940
|
-
onStderr: (c) =>
|
|
12941
|
+
onStdout: (c) => log3(onLog, `[master] ${c.trimEnd()}`),
|
|
12942
|
+
onStderr: (c) => log3(onLog, `[master] ${c.trimEnd()}`),
|
|
11941
12943
|
errorMessage: `Failed to install k3s on master ${creds.host}`
|
|
11942
12944
|
}
|
|
11943
12945
|
);
|
|
11944
12946
|
}
|
|
11945
|
-
|
|
12947
|
+
log3(onLog, `[master ${creds.host}] Waiting for k3s to be ready...`);
|
|
11946
12948
|
await client.execOrThrow(
|
|
11947
|
-
|
|
12949
|
+
asRoot3(`timeout 120 sh -c 'until k3s kubectl get nodes > /dev/null 2>&1; do sleep 3; done'`),
|
|
11948
12950
|
{ errorMessage: "k3s master did not become ready within 120s" }
|
|
11949
12951
|
);
|
|
11950
|
-
|
|
12952
|
+
log3(onLog, `[master ${creds.host}] Reading node token...`);
|
|
11951
12953
|
const tokenResult = await client.execOrThrow(
|
|
11952
|
-
|
|
12954
|
+
asRoot3("cat /var/lib/rancher/k3s/server/node-token"),
|
|
11953
12955
|
{
|
|
11954
12956
|
errorMessage: "Failed to read k3s node token"
|
|
11955
12957
|
}
|
|
11956
12958
|
);
|
|
11957
12959
|
const token = tokenResult.stdout.trim();
|
|
11958
12960
|
if (!token) throw new Error("k3s node token is empty");
|
|
11959
|
-
|
|
11960
|
-
const kubeconfigResult = await client.execOrThrow(
|
|
12961
|
+
log3(onLog, `[master ${creds.host}] Reading kubeconfig...`);
|
|
12962
|
+
const kubeconfigResult = await client.execOrThrow(asRoot3("cat /etc/rancher/k3s/k3s.yaml"), {
|
|
11961
12963
|
errorMessage: "Failed to read k3s kubeconfig"
|
|
11962
12964
|
});
|
|
11963
12965
|
return { token, kubeconfig: kubeconfigResult.stdout };
|
|
@@ -11968,25 +12970,26 @@ async function installMaster(master, install2, force, onLog) {
|
|
|
11968
12970
|
async function installWorker(worker, masterHost, token, install2, force, onLog) {
|
|
11969
12971
|
const creds = resolveNodeCredentials(worker);
|
|
11970
12972
|
const client = new SSHClient();
|
|
11971
|
-
|
|
12973
|
+
log3(onLog, `[worker ${creds.host}] Connecting via SSH...`);
|
|
11972
12974
|
await client.connect(creds);
|
|
11973
12975
|
try {
|
|
12976
|
+
await ensureK3sRegistriesConfig(client, install2, "k3s-agent", onLog);
|
|
11974
12977
|
const alreadyInstalled = await isK3sInstalled(client);
|
|
11975
12978
|
if (alreadyInstalled && !force) {
|
|
11976
|
-
|
|
12979
|
+
log3(
|
|
11977
12980
|
onLog,
|
|
11978
12981
|
`[worker ${creds.host}] k3s already installed \u2014 skipping install (use --force to reinstall)`
|
|
11979
12982
|
);
|
|
11980
12983
|
return;
|
|
11981
12984
|
}
|
|
11982
12985
|
if (alreadyInstalled && force) {
|
|
11983
|
-
|
|
11984
|
-
await client.exec(
|
|
12986
|
+
log3(onLog, `[worker ${creds.host}] k3s already installed \u2014 reinstalling (--force)`);
|
|
12987
|
+
await client.exec(asRoot3("/usr/local/bin/k3s-agent-uninstall.sh 2>/dev/null || true"));
|
|
11985
12988
|
}
|
|
11986
|
-
|
|
12989
|
+
log3(onLog, `[worker ${creds.host}] Installing k3s agent and joining cluster...`);
|
|
11987
12990
|
await client.execOrThrow(
|
|
11988
|
-
|
|
11989
|
-
`curl -sfL ${
|
|
12991
|
+
asRoot3(
|
|
12992
|
+
`curl -sfL ${shellQuote3(K3S_INSTALL_URL)} | ${k3sInstallEnv(
|
|
11990
12993
|
{
|
|
11991
12994
|
K3S_URL: `https://${masterHost}:6443`,
|
|
11992
12995
|
K3S_TOKEN: token,
|
|
@@ -11996,12 +12999,12 @@ async function installWorker(worker, masterHost, token, install2, force, onLog)
|
|
|
11996
12999
|
)} sh -`
|
|
11997
13000
|
),
|
|
11998
13001
|
{
|
|
11999
|
-
onStdout: (c) =>
|
|
12000
|
-
onStderr: (c) =>
|
|
13002
|
+
onStdout: (c) => log3(onLog, `[worker ${creds.host}] ${c.trimEnd()}`),
|
|
13003
|
+
onStderr: (c) => log3(onLog, `[worker ${creds.host}] ${c.trimEnd()}`),
|
|
12001
13004
|
errorMessage: `Failed to install k3s agent on worker ${creds.host}`
|
|
12002
13005
|
}
|
|
12003
13006
|
);
|
|
12004
|
-
|
|
13007
|
+
log3(onLog, `[worker ${creds.host}] Agent joined cluster \u2713`);
|
|
12005
13008
|
} finally {
|
|
12006
13009
|
await client.dispose();
|
|
12007
13010
|
}
|
|
@@ -12010,17 +13013,39 @@ async function initCluster(options) {
|
|
|
12010
13013
|
const { config, force = false, onLog } = options;
|
|
12011
13014
|
const master = getMasterNode(config);
|
|
12012
13015
|
const workers = getWorkerNodes(config);
|
|
12013
|
-
|
|
12014
|
-
const
|
|
13016
|
+
log3(onLog, `Initializing cluster "${config.name}" with ${config.nodes.length} nodes...`);
|
|
13017
|
+
const masterInstall = resolveNodeInstallConfig(config.install, master);
|
|
13018
|
+
const { token, kubeconfig } = await installMaster(master, masterInstall, force, onLog);
|
|
12015
13019
|
if (workers.length > 0) {
|
|
12016
|
-
|
|
13020
|
+
log3(onLog, `Installing ${workers.length} worker(s) in parallel...`);
|
|
12017
13021
|
await Promise.all(
|
|
12018
|
-
workers.map(
|
|
13022
|
+
workers.map(
|
|
13023
|
+
(worker) => installWorker(
|
|
13024
|
+
worker,
|
|
13025
|
+
master.host,
|
|
13026
|
+
token,
|
|
13027
|
+
resolveNodeInstallConfig(config.install, worker),
|
|
13028
|
+
force,
|
|
13029
|
+
onLog
|
|
13030
|
+
)
|
|
13031
|
+
)
|
|
12019
13032
|
);
|
|
12020
13033
|
}
|
|
12021
|
-
const
|
|
12022
|
-
|
|
12023
|
-
|
|
13034
|
+
const sandboxConfig = resolveClusterSandboxConfig(config);
|
|
13035
|
+
const sandboxEnabled = await installClusterSandbox({ config, onLog });
|
|
13036
|
+
const meta = storeKubeconfig(config.name, kubeconfig, master.host, config.nodes.length, {
|
|
13037
|
+
configHash: clusterConfigHash(config),
|
|
13038
|
+
features: {
|
|
13039
|
+
sandbox: sandboxConfig ? {
|
|
13040
|
+
enabled: sandboxEnabled,
|
|
13041
|
+
version: sandboxConfig.version,
|
|
13042
|
+
runtimeClassName: sandboxConfig.runtimeClassName,
|
|
13043
|
+
nodeSelector: sandboxConfig.nodeSelector
|
|
13044
|
+
} : { enabled: false }
|
|
13045
|
+
}
|
|
13046
|
+
});
|
|
13047
|
+
log3(onLog, `Kubeconfig stored at ${meta.kubeconfigPath}`);
|
|
13048
|
+
log3(onLog, `Cluster "${config.name}" is ready. Use: shadowob-cloud up --cluster ${config.name}`);
|
|
12024
13049
|
return meta;
|
|
12025
13050
|
}
|
|
12026
13051
|
|
|
@@ -12139,7 +13164,7 @@ var ConfigService = class {
|
|
|
12139
13164
|
};
|
|
12140
13165
|
|
|
12141
13166
|
// src/services/deploy.service.ts
|
|
12142
|
-
import { existsSync as
|
|
13167
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
12143
13168
|
import { dirname as dirname3, resolve as resolve16 } from "path";
|
|
12144
13169
|
function deploymentReadyTimeoutMs() {
|
|
12145
13170
|
const raw = Number(process.env.CLOUD_DEPLOYMENT_READY_TIMEOUT_MS);
|
|
@@ -12148,13 +13173,22 @@ function deploymentReadyTimeoutMs() {
|
|
|
12148
13173
|
function isAgentSandboxBackend(config) {
|
|
12149
13174
|
return (config.deployments?.backend ?? "agent-sandbox") === "agent-sandbox";
|
|
12150
13175
|
}
|
|
13176
|
+
function workloadBackendPolicy(config) {
|
|
13177
|
+
if (config.deployments?.backendPolicy) return config.deployments.backendPolicy;
|
|
13178
|
+
return isAgentSandboxBackend(config) ? "sandbox-required" : "deployment-only";
|
|
13179
|
+
}
|
|
13180
|
+
function sandboxRuntimeClassNames(config) {
|
|
13181
|
+
const deployments = config.deployments;
|
|
13182
|
+
if (!deployments) return [];
|
|
13183
|
+
const defaultRuntimeClassName = deployments.sandbox?.runtimeClassName ?? "gvisor";
|
|
13184
|
+
const names = deployments.agents.map(
|
|
13185
|
+
(agent) => agent.sandbox?.runtimeClassName ?? defaultRuntimeClassName
|
|
13186
|
+
);
|
|
13187
|
+
return [...new Set(names)];
|
|
13188
|
+
}
|
|
12151
13189
|
function readKubeconfigForRuntimeWait(kubeConfigPath) {
|
|
12152
13190
|
if (!kubeConfigPath) return void 0;
|
|
12153
|
-
|
|
12154
|
-
return readFileSync8(kubeConfigPath, "utf8");
|
|
12155
|
-
} catch {
|
|
12156
|
-
return void 0;
|
|
12157
|
-
}
|
|
13191
|
+
return readKubeconfigFile(kubeConfigPath);
|
|
12158
13192
|
}
|
|
12159
13193
|
function resolveStackName(namespace, stack) {
|
|
12160
13194
|
return stack ?? `dev-${namespace}`;
|
|
@@ -12196,7 +13230,7 @@ function configUsesPlugins(value, depth = 0) {
|
|
|
12196
13230
|
}
|
|
12197
13231
|
async function ensureBuiltInPluginsLoaded() {
|
|
12198
13232
|
try {
|
|
12199
|
-
const { loadAllPlugins, getPluginRegistry: getPluginRegistry2 } = await import("./plugins-
|
|
13233
|
+
const { loadAllPlugins, getPluginRegistry: getPluginRegistry2 } = await import("./plugins-2MITZ4ZD.js");
|
|
12200
13234
|
const registry = getPluginRegistry2();
|
|
12201
13235
|
if (registry.size === 0) await loadAllPlugins(registry);
|
|
12202
13236
|
} catch {
|
|
@@ -12205,7 +13239,7 @@ async function ensureBuiltInPluginsLoaded() {
|
|
|
12205
13239
|
function readKubeconfigCurrentContext(kubeConfigPath) {
|
|
12206
13240
|
if (!kubeConfigPath) return void 0;
|
|
12207
13241
|
try {
|
|
12208
|
-
return
|
|
13242
|
+
return readKubeconfigFile(kubeConfigPath).match(/current-context:\s*(\S+)/)?.[1];
|
|
12209
13243
|
} catch {
|
|
12210
13244
|
return void 0;
|
|
12211
13245
|
}
|
|
@@ -12218,6 +13252,39 @@ function summarizeK8sTarget(options, kubeConfigPath) {
|
|
|
12218
13252
|
const kubeconfig = kubeConfigPath ?? process.env.KUBECONFIG ?? "~/.kube/config";
|
|
12219
13253
|
return `Kubernetes target: cluster=${cluster} context=${context} kubeconfig=${kubeconfig}`;
|
|
12220
13254
|
}
|
|
13255
|
+
function applyManagedClusterDefaults(config, clusterName) {
|
|
13256
|
+
if (!clusterName || !config.deployments) return;
|
|
13257
|
+
const sandbox = loadClusterMeta(clusterName)?.features?.sandbox;
|
|
13258
|
+
if (!config.deployments.backendPolicy) {
|
|
13259
|
+
config.deployments.backendPolicy = sandbox?.enabled ? "sandbox-preferred" : config.deployments.backend === "agent-sandbox" ? "sandbox-required" : "deployment-only";
|
|
13260
|
+
}
|
|
13261
|
+
if (!config.deployments.backend) {
|
|
13262
|
+
config.deployments.backend = config.deployments.backendPolicy === "deployment-only" ? "deployment" : sandbox?.enabled ? "agent-sandbox" : "deployment";
|
|
13263
|
+
}
|
|
13264
|
+
if (config.deployments.backend === "agent-sandbox" && sandbox?.enabled && sandbox.runtimeClassName) {
|
|
13265
|
+
config.deployments.sandbox = {
|
|
13266
|
+
...config.deployments.sandbox ?? {},
|
|
13267
|
+
runtimeClassName: config.deployments.sandbox?.runtimeClassName ?? sandbox.runtimeClassName
|
|
13268
|
+
};
|
|
13269
|
+
}
|
|
13270
|
+
if (config.deployments.backend === "agent-sandbox" && sandbox?.enabled && sandbox.nodeSelector) {
|
|
13271
|
+
config.deployments.scheduling = {
|
|
13272
|
+
...config.deployments.scheduling ?? {},
|
|
13273
|
+
nodeSelector: {
|
|
13274
|
+
...sandbox.nodeSelector,
|
|
13275
|
+
...config.deployments.scheduling?.nodeSelector ?? {}
|
|
13276
|
+
}
|
|
13277
|
+
};
|
|
13278
|
+
}
|
|
13279
|
+
}
|
|
13280
|
+
function describeSandboxPreflightFailure(missing, warnings) {
|
|
13281
|
+
return [
|
|
13282
|
+
"agent-sandbox preflight failed.",
|
|
13283
|
+
missing.length > 0 ? `Missing: ${missing.join(", ")}.` : "",
|
|
13284
|
+
warnings.length > 0 ? `Warnings: ${warnings.join(", ")}.` : "",
|
|
13285
|
+
'Run "shadowob-cloud cluster apply --config cluster.json" to install/verify sandbox, or set deployments.backendPolicy="deployment-only" for fallback.'
|
|
13286
|
+
].filter(Boolean).join(" ");
|
|
13287
|
+
}
|
|
12221
13288
|
var DeployService = class {
|
|
12222
13289
|
constructor(configService, manifestService, k8s6, logger) {
|
|
12223
13290
|
this.configService = configService;
|
|
@@ -12237,10 +13304,13 @@ var DeployService = class {
|
|
|
12237
13304
|
});
|
|
12238
13305
|
const runtimeContext = normalizeDeploymentRuntimeContext(options.runtimeContext);
|
|
12239
13306
|
const effectiveEnv = buildEffectiveEnv(options.runtimeEnvVars, runtimeContext);
|
|
12240
|
-
if (!
|
|
13307
|
+
if (!existsSync19(filePath)) {
|
|
12241
13308
|
throw new Error(`Config file not found: ${filePath}`);
|
|
12242
13309
|
}
|
|
12243
13310
|
const kubeConfigPath = options.kubeConfigPath ?? (options.cluster ? loadKubeconfigPath(options.cluster) : void 0);
|
|
13311
|
+
if (kubeConfigPath) {
|
|
13312
|
+
assertReadableKubeconfigFile(kubeConfigPath);
|
|
13313
|
+
}
|
|
12244
13314
|
const k8sTargetSummary = summarizeK8sTarget(options, kubeConfigPath);
|
|
12245
13315
|
this.logger.info(k8sTargetSummary);
|
|
12246
13316
|
emit(`${k8sTargetSummary}
|
|
@@ -12253,6 +13323,7 @@ var DeployService = class {
|
|
|
12253
13323
|
await this.configService.parseFile(filePath),
|
|
12254
13324
|
runtimeContext
|
|
12255
13325
|
);
|
|
13326
|
+
applyManagedClusterDefaults(config, options.cluster);
|
|
12256
13327
|
const namespace = options.namespace ?? config.deployments?.namespace ?? "shadowob-cloud";
|
|
12257
13328
|
const stackName = resolveStackName(namespace, options.stack);
|
|
12258
13329
|
const agents = config.deployments?.agents ?? [];
|
|
@@ -12297,10 +13368,31 @@ var DeployService = class {
|
|
|
12297
13368
|
const usesPlugins = configUsesPlugins(config);
|
|
12298
13369
|
if (usesPlugins) await ensureBuiltInPluginsLoaded();
|
|
12299
13370
|
const resolved = await this.configService.resolve(config, configCwd, { env: effectiveEnv });
|
|
13371
|
+
if (resolved.deployments && isAgentSandboxBackend(resolved)) {
|
|
13372
|
+
const policy = workloadBackendPolicy(resolved);
|
|
13373
|
+
const preflight = this.k8s.checkAgentSandboxPreflight({
|
|
13374
|
+
kubeconfig: readKubeconfigForRuntimeWait(kubeConfigPath),
|
|
13375
|
+
runtimeClassNames: sandboxRuntimeClassNames(resolved)
|
|
13376
|
+
});
|
|
13377
|
+
if (!preflight.ok) {
|
|
13378
|
+
const message = describeSandboxPreflightFailure(preflight.missing, preflight.warnings);
|
|
13379
|
+
if (policy === "sandbox-preferred") {
|
|
13380
|
+
this.logger.warn(`${message} Falling back to Kubernetes Deployment.`);
|
|
13381
|
+
emit(`${message} Falling back to Kubernetes Deployment.
|
|
13382
|
+
`);
|
|
13383
|
+
resolved.deployments.backend = "deployment";
|
|
13384
|
+
resolved.deployments.backendPolicy = "deployment-only";
|
|
13385
|
+
} else {
|
|
13386
|
+
throw new Error(message);
|
|
13387
|
+
}
|
|
13388
|
+
} else if (preflight.warnings.length > 0) {
|
|
13389
|
+
this.logger.warn(`agent-sandbox preflight warnings: ${preflight.warnings.join(", ")}`);
|
|
13390
|
+
}
|
|
13391
|
+
}
|
|
12300
13392
|
if (usesPlugins) await ensureBuiltInPluginsLoaded();
|
|
12301
13393
|
if (!options.skipProvision) {
|
|
12302
13394
|
try {
|
|
12303
|
-
const { executePluginProvisions, getPluginRegistry: getPluginRegistry2 } = await import("./plugins-
|
|
13395
|
+
const { executePluginProvisions, getPluginRegistry: getPluginRegistry2 } = await import("./plugins-2MITZ4ZD.js");
|
|
12304
13396
|
for (const agent of agents) {
|
|
12305
13397
|
const provisionResults = await executePluginProvisions(
|
|
12306
13398
|
agent,
|
|
@@ -12315,6 +13407,9 @@ var DeployService = class {
|
|
|
12315
13407
|
for (const e of provisionResults.errors) {
|
|
12316
13408
|
this.logger.warn(`Plugin provision error (${e.pluginId}): ${e.error}`);
|
|
12317
13409
|
}
|
|
13410
|
+
throw new Error(
|
|
13411
|
+
`Plugin provisioning failed: ${provisionResults.errors.map((e) => `${e.pluginId}: ${e.error}`).join("; ")}`
|
|
13412
|
+
);
|
|
12318
13413
|
}
|
|
12319
13414
|
if (Object.keys(provisionResults.secrets).length > 0) {
|
|
12320
13415
|
agent.env = { ...agent.env ?? {}, ...provisionResults.secrets };
|
|
@@ -12338,7 +13433,10 @@ var DeployService = class {
|
|
|
12338
13433
|
await options.onProvisionState?.(merged);
|
|
12339
13434
|
}
|
|
12340
13435
|
}
|
|
12341
|
-
} catch {
|
|
13436
|
+
} catch (err) {
|
|
13437
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
13438
|
+
this.logger.warn(`Plugin provisioning failed; aborting deploy: ${message}`);
|
|
13439
|
+
throw err;
|
|
12342
13440
|
}
|
|
12343
13441
|
}
|
|
12344
13442
|
const k8sShadowUrl = options.k8sShadowUrl ?? effectiveEnv.SHADOW_AGENT_SERVER_URL ?? options.shadowUrl ?? effectiveEnv.K8S_SHADOW_URL ?? effectiveEnv.SHADOW_SERVER_URL;
|
|
@@ -12411,11 +13509,11 @@ var DeployService = class {
|
|
|
12411
13509
|
emit("Lock canceled, retrying...\n");
|
|
12412
13510
|
}
|
|
12413
13511
|
} catch {
|
|
12414
|
-
const { join:
|
|
13512
|
+
const { join: join8 } = await import("path");
|
|
12415
13513
|
const { homedir: homedir6 } = await import("os");
|
|
12416
|
-
const { rmSync: rmSync4, existsSync:
|
|
12417
|
-
const lockDir =
|
|
12418
|
-
if (
|
|
13514
|
+
const { rmSync: rmSync4, existsSync: existsSync20 } = await import("fs");
|
|
13515
|
+
const lockDir = join8(homedir6(), ".shadowob", "pulumi", ".pulumi", "locks");
|
|
13516
|
+
if (existsSync20(lockDir)) {
|
|
12419
13517
|
try {
|
|
12420
13518
|
rmSync4(lockDir, { recursive: true });
|
|
12421
13519
|
this.logger.info("Lock files removed, retrying...");
|
|
@@ -12515,6 +13613,9 @@ var DeployService = class {
|
|
|
12515
13613
|
`Cannot destroy namespace "${namespace}" without a Pulumi config snapshot. Destroy must run through the deployment stack state.`
|
|
12516
13614
|
);
|
|
12517
13615
|
}
|
|
13616
|
+
if (options.kubeConfigPath) {
|
|
13617
|
+
assertReadableKubeconfigFile(options.kubeConfigPath);
|
|
13618
|
+
}
|
|
12518
13619
|
const stack = await this.k8s.getOrCreateStack({
|
|
12519
13620
|
stackName,
|
|
12520
13621
|
config: options.config,
|
|
@@ -12856,7 +13957,7 @@ function rolloutUndoAll(namespace) {
|
|
|
12856
13957
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
12857
13958
|
import { mkdir as mkdir3 } from "fs/promises";
|
|
12858
13959
|
import { homedir as homedir5 } from "os";
|
|
12859
|
-
import { delimiter as delimiter3, join as
|
|
13960
|
+
import { delimiter as delimiter3, join as join7 } from "path";
|
|
12860
13961
|
import * as automation from "@pulumi/pulumi/automation/index.js";
|
|
12861
13962
|
import { PulumiCommand } from "@pulumi/pulumi/automation/index.js";
|
|
12862
13963
|
|
|
@@ -13077,6 +14178,29 @@ function baseVolumes(configMapName) {
|
|
|
13077
14178
|
{ name: RUNNER_AGENTS_VOLUME_NAME, emptyDir: {} }
|
|
13078
14179
|
];
|
|
13079
14180
|
}
|
|
14181
|
+
function isAgentSandboxBackend2(config) {
|
|
14182
|
+
return (config.deployments?.backend ?? "agent-sandbox") === "agent-sandbox";
|
|
14183
|
+
}
|
|
14184
|
+
function hasKeys(value) {
|
|
14185
|
+
return Boolean(value && typeof value === "object" && Object.keys(value).length > 0);
|
|
14186
|
+
}
|
|
14187
|
+
function resolveSchedulingConfig(config, agent) {
|
|
14188
|
+
const defaults = isAgentSandboxBackend2(config) ? { nodeSelector: { "shadowob.com/sandbox-ready": "true" } } : {};
|
|
14189
|
+
const globalScheduling = config.deployments?.scheduling ?? {};
|
|
14190
|
+
const agentScheduling = agent.scheduling ?? {};
|
|
14191
|
+
const nodeSelector = {
|
|
14192
|
+
...defaults.nodeSelector ?? {},
|
|
14193
|
+
...globalScheduling.nodeSelector ?? {},
|
|
14194
|
+
...agentScheduling.nodeSelector ?? {}
|
|
14195
|
+
};
|
|
14196
|
+
const affinity = agentScheduling.affinity ?? globalScheduling.affinity;
|
|
14197
|
+
const tolerations = agentScheduling.tolerations ?? globalScheduling.tolerations;
|
|
14198
|
+
return {
|
|
14199
|
+
...Object.keys(nodeSelector).length > 0 ? { nodeSelector } : {},
|
|
14200
|
+
...hasKeys(affinity) ? { affinity } : {},
|
|
14201
|
+
...tolerations && tolerations.length > 0 ? { tolerations } : {}
|
|
14202
|
+
};
|
|
14203
|
+
}
|
|
13080
14204
|
function buildAgentPodSpec(options) {
|
|
13081
14205
|
const runtime = getRuntime(options.agent.runtime);
|
|
13082
14206
|
const image = options.agent.image ?? runtime.defaultImage;
|
|
@@ -13171,6 +14295,7 @@ function buildAgentPodSpec(options) {
|
|
|
13171
14295
|
initContainers,
|
|
13172
14296
|
containers,
|
|
13173
14297
|
volumes,
|
|
14298
|
+
scheduling: resolveSchedulingConfig(options.config, options.agent),
|
|
13174
14299
|
pluginArtifacts
|
|
13175
14300
|
};
|
|
13176
14301
|
}
|
|
@@ -13268,6 +14393,9 @@ function createAgentDeployment(options) {
|
|
|
13268
14393
|
securityContext: buildSecurityContext(),
|
|
13269
14394
|
containers: pod.containers,
|
|
13270
14395
|
volumes: pod.volumes,
|
|
14396
|
+
nodeSelector: pod.scheduling.nodeSelector,
|
|
14397
|
+
affinity: pod.scheduling.affinity,
|
|
14398
|
+
tolerations: pod.scheduling.tolerations,
|
|
13271
14399
|
restartPolicy: "Always"
|
|
13272
14400
|
}
|
|
13273
14401
|
}
|
|
@@ -13473,6 +14601,9 @@ function buildAgentSandboxTemplateManifest(options) {
|
|
|
13473
14601
|
securityContext: buildSecurityContext(),
|
|
13474
14602
|
containers: options.pod.containers,
|
|
13475
14603
|
volumes: options.pod.volumes,
|
|
14604
|
+
nodeSelector: options.pod.scheduling.nodeSelector,
|
|
14605
|
+
affinity: options.pod.scheduling.affinity,
|
|
14606
|
+
tolerations: options.pod.scheduling.tolerations,
|
|
13476
14607
|
restartPolicy: "Always"
|
|
13477
14608
|
}
|
|
13478
14609
|
},
|
|
@@ -13560,18 +14691,18 @@ function createConfigResources(options) {
|
|
|
13560
14691
|
}
|
|
13561
14692
|
|
|
13562
14693
|
// src/infra/hash.ts
|
|
13563
|
-
import { createHash as
|
|
13564
|
-
function
|
|
14694
|
+
import { createHash as createHash3 } from "crypto";
|
|
14695
|
+
function stableStringify2(value) {
|
|
13565
14696
|
if (Array.isArray(value)) {
|
|
13566
|
-
return `[${value.map((item) =>
|
|
14697
|
+
return `[${value.map((item) => stableStringify2(item)).join(",")}]`;
|
|
13567
14698
|
}
|
|
13568
14699
|
if (value && typeof value === "object") {
|
|
13569
|
-
return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => `${JSON.stringify(key)}:${
|
|
14700
|
+
return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => `${JSON.stringify(key)}:${stableStringify2(item)}`).join(",")}}`;
|
|
13570
14701
|
}
|
|
13571
14702
|
return JSON.stringify(value);
|
|
13572
14703
|
}
|
|
13573
14704
|
function stableHash(value) {
|
|
13574
|
-
return
|
|
14705
|
+
return createHash3("sha256").update(stableStringify2(value)).digest("hex");
|
|
13575
14706
|
}
|
|
13576
14707
|
|
|
13577
14708
|
// src/infra/networking.ts
|
|
@@ -13671,6 +14802,9 @@ function classifyEnv(registrySecretEnv, mergedEnv) {
|
|
|
13671
14802
|
}
|
|
13672
14803
|
return { plainEnv, secretData };
|
|
13673
14804
|
}
|
|
14805
|
+
function omitEnvKeys(env, keys) {
|
|
14806
|
+
for (const key of keys) delete env[key];
|
|
14807
|
+
}
|
|
13674
14808
|
function runtimePackageEnvDefaults(options) {
|
|
13675
14809
|
const env = {};
|
|
13676
14810
|
if (!options.currentEnv.SHADOW_SLASH_COMMANDS_PATH) {
|
|
@@ -13693,6 +14827,7 @@ function buildAgentRuntimePackage(options) {
|
|
|
13693
14827
|
...agent.env ?? {},
|
|
13694
14828
|
...extraEnv ?? {}
|
|
13695
14829
|
};
|
|
14830
|
+
const runtimeEnvOmitKeys = collectPluginRuntimeEnvOmitKeys(agent, config, cwd, runtimeEnv);
|
|
13696
14831
|
const runtimeExtensions = collectPluginRuntimeExtensions(agent, config, cwd, runtimeEnv);
|
|
13697
14832
|
const mergedEnv = {
|
|
13698
14833
|
...collectPluginBuildEnvVars(agent, config, cwd, runtimeEnv),
|
|
@@ -13718,6 +14853,7 @@ function buildAgentRuntimePackage(options) {
|
|
|
13718
14853
|
if (runtimeArtifacts.provisionSecrets) {
|
|
13719
14854
|
Object.assign(mergedEnv, runtimeArtifacts.provisionSecrets);
|
|
13720
14855
|
}
|
|
14856
|
+
omitEnvKeys(mergedEnv, runtimeEnvOmitKeys);
|
|
13721
14857
|
const { plainEnv, secretData } = classifyEnv(registrySecretEnv, mergedEnv);
|
|
13722
14858
|
return {
|
|
13723
14859
|
runtimeKind: runtime.runtimeKind,
|
|
@@ -13730,10 +14866,9 @@ function buildAgentRuntimePackage(options) {
|
|
|
13730
14866
|
}
|
|
13731
14867
|
|
|
13732
14868
|
// src/infra/shared.ts
|
|
13733
|
-
import { readFileSync as readFileSync9 } from "fs";
|
|
13734
14869
|
import * as k8s5 from "@pulumi/kubernetes";
|
|
13735
14870
|
function createSharedResources(options) {
|
|
13736
|
-
const providerConfig = options.kubeConfigPath ? { kubeconfig:
|
|
14871
|
+
const providerConfig = options.kubeConfigPath ? { kubeconfig: readKubeconfigFile(options.kubeConfigPath) } : {
|
|
13737
14872
|
context: options.kubeContext ?? process.env.KUBECONFIG_CONTEXT ?? process.env.K8S_CONTEXT ?? "rancher-desktop"
|
|
13738
14873
|
};
|
|
13739
14874
|
const provider = new k8s5.Provider("k8s-provider", providerConfig);
|
|
@@ -14125,13 +15260,13 @@ function getNonEmptyEnv(name) {
|
|
|
14125
15260
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
14126
15261
|
}
|
|
14127
15262
|
function getDefaultStateDir() {
|
|
14128
|
-
return getNonEmptyEnv("PULUMI_BACKEND_URL") ? "" :
|
|
15263
|
+
return getNonEmptyEnv("PULUMI_BACKEND_URL") ? "" : join7(homedir5(), ".shadowob", "pulumi");
|
|
14129
15264
|
}
|
|
14130
15265
|
function resolvePulumiBackendUrl(stateDir) {
|
|
14131
15266
|
return getNonEmptyEnv("PULUMI_BACKEND_URL") ?? (stateDir ? `file://${stateDir}` : void 0);
|
|
14132
15267
|
}
|
|
14133
15268
|
function ensurePulumiCliOnPath(cliRoot) {
|
|
14134
|
-
const binDir =
|
|
15269
|
+
const binDir = join7(cliRoot, "bin");
|
|
14135
15270
|
const currentPath = process.env.PATH ?? "";
|
|
14136
15271
|
const parts = currentPath.split(delimiter3).filter(Boolean);
|
|
14137
15272
|
if (!parts.includes(binDir)) {
|
|
@@ -14161,8 +15296,8 @@ ${stderr}` : ""}`
|
|
|
14161
15296
|
}
|
|
14162
15297
|
}
|
|
14163
15298
|
async function getOrCreateStack(options) {
|
|
14164
|
-
const cliRoot =
|
|
14165
|
-
const pulumiHome =
|
|
15299
|
+
const cliRoot = join7(homedir5(), ".shadowob", "pulumi", "cli");
|
|
15300
|
+
const pulumiHome = join7(homedir5(), ".shadowob", "pulumi", "home");
|
|
14166
15301
|
const infraOpts = {
|
|
14167
15302
|
config: options.config,
|
|
14168
15303
|
namespace: options.namespace,
|
|
@@ -14358,6 +15493,9 @@ var K8sService = class {
|
|
|
14358
15493
|
async waitForAgentSandboxPaused(options) {
|
|
14359
15494
|
return waitForAgentSandboxPaused(options);
|
|
14360
15495
|
}
|
|
15496
|
+
checkAgentSandboxPreflight(options) {
|
|
15497
|
+
return checkAgentSandboxPreflight(options);
|
|
15498
|
+
}
|
|
14361
15499
|
async restorePvcFromVolumeSnapshot(options) {
|
|
14362
15500
|
await restorePvcFromVolumeSnapshot(options);
|
|
14363
15501
|
}
|