@better-openclaw/core 1.0.23 → 1.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/dist/bare-metal-partition.test.cjs +3 -4
  2. package/dist/bare-metal-partition.test.cjs.map +1 -1
  3. package/dist/bare-metal-partition.test.mjs +3 -4
  4. package/dist/bare-metal-partition.test.mjs.map +1 -1
  5. package/dist/composer.cjs +13 -1
  6. package/dist/composer.cjs.map +1 -1
  7. package/dist/composer.d.cts.map +1 -1
  8. package/dist/composer.d.mts.map +1 -1
  9. package/dist/composer.mjs +13 -1
  10. package/dist/composer.mjs.map +1 -1
  11. package/dist/composer.snapshot.test.cjs +1 -1
  12. package/dist/composer.snapshot.test.mjs +1 -1
  13. package/dist/composer.test.cjs +3 -2
  14. package/dist/composer.test.cjs.map +1 -1
  15. package/dist/composer.test.mjs +3 -2
  16. package/dist/composer.test.mjs.map +1 -1
  17. package/dist/deployers/strip-host-ports.test.cjs +1 -1
  18. package/dist/deployers/strip-host-ports.test.mjs +1 -1
  19. package/dist/generate.cjs +6 -2
  20. package/dist/generate.cjs.map +1 -1
  21. package/dist/generate.d.cts.map +1 -1
  22. package/dist/generate.d.mts.map +1 -1
  23. package/dist/generate.mjs +6 -2
  24. package/dist/generate.mjs.map +1 -1
  25. package/dist/generate.test.cjs +2 -2
  26. package/dist/generate.test.cjs.map +1 -1
  27. package/dist/generate.test.mjs +2 -2
  28. package/dist/generate.test.mjs.map +1 -1
  29. package/dist/generators/bare-metal-install.test.cjs +1 -1
  30. package/dist/generators/bare-metal-install.test.mjs +1 -1
  31. package/dist/generators/caddy.test.cjs +1 -1
  32. package/dist/generators/caddy.test.mjs +1 -1
  33. package/dist/generators/clone-repos.cjs +140 -0
  34. package/dist/generators/clone-repos.cjs.map +1 -0
  35. package/dist/generators/clone-repos.d.cts +11 -0
  36. package/dist/generators/clone-repos.d.cts.map +1 -0
  37. package/dist/generators/clone-repos.d.mts +11 -0
  38. package/dist/generators/clone-repos.d.mts.map +1 -0
  39. package/dist/generators/clone-repos.mjs +139 -0
  40. package/dist/generators/clone-repos.mjs.map +1 -0
  41. package/dist/generators/clone-repos.test.cjs +140 -0
  42. package/dist/generators/clone-repos.test.cjs.map +1 -0
  43. package/dist/generators/clone-repos.test.d.cts +1 -0
  44. package/dist/generators/clone-repos.test.d.mts +1 -0
  45. package/dist/generators/clone-repos.test.mjs +141 -0
  46. package/dist/generators/clone-repos.test.mjs.map +1 -0
  47. package/dist/generators/env.test.cjs +1 -1
  48. package/dist/generators/env.test.mjs +1 -1
  49. package/dist/generators/health-check.test.cjs +1 -1
  50. package/dist/generators/health-check.test.mjs +1 -1
  51. package/dist/generators/postgres-init.cjs +20 -0
  52. package/dist/generators/postgres-init.cjs.map +1 -1
  53. package/dist/generators/postgres-init.d.cts.map +1 -1
  54. package/dist/generators/postgres-init.d.mts.map +1 -1
  55. package/dist/generators/postgres-init.mjs +20 -0
  56. package/dist/generators/postgres-init.mjs.map +1 -1
  57. package/dist/generators/scripts.cjs +332 -3
  58. package/dist/generators/scripts.cjs.map +1 -1
  59. package/dist/generators/scripts.d.cts +3 -1
  60. package/dist/generators/scripts.d.cts.map +1 -1
  61. package/dist/generators/scripts.d.mts +3 -1
  62. package/dist/generators/scripts.d.mts.map +1 -1
  63. package/dist/generators/scripts.mjs +332 -3
  64. package/dist/generators/scripts.mjs.map +1 -1
  65. package/dist/generators/scripts.test.cjs +39 -5
  66. package/dist/generators/scripts.test.cjs.map +1 -1
  67. package/dist/generators/scripts.test.mjs +39 -5
  68. package/dist/generators/scripts.test.mjs.map +1 -1
  69. package/dist/generators/stack-manifest.cjs +1 -0
  70. package/dist/generators/stack-manifest.cjs.map +1 -1
  71. package/dist/generators/stack-manifest.d.cts +3 -2
  72. package/dist/generators/stack-manifest.d.cts.map +1 -1
  73. package/dist/generators/stack-manifest.d.mts +3 -2
  74. package/dist/generators/stack-manifest.d.mts.map +1 -1
  75. package/dist/generators/stack-manifest.mjs +1 -0
  76. package/dist/generators/stack-manifest.mjs.map +1 -1
  77. package/dist/generators/traefik.test.cjs +1 -1
  78. package/dist/generators/traefik.test.mjs +1 -1
  79. package/dist/index.cjs +8 -1
  80. package/dist/index.d.cts +5 -3
  81. package/dist/index.d.mts +5 -3
  82. package/dist/index.mjs +5 -3
  83. package/dist/migrations.test.cjs +1 -1
  84. package/dist/migrations.test.mjs +1 -1
  85. package/dist/presets/registry.cjs.map +1 -1
  86. package/dist/presets/registry.d.cts.map +1 -1
  87. package/dist/presets/registry.d.mts.map +1 -1
  88. package/dist/presets/registry.mjs.map +1 -1
  89. package/dist/presets/registry.test.cjs +1 -1
  90. package/dist/presets/registry.test.mjs +1 -1
  91. package/dist/resolver.cjs +8 -0
  92. package/dist/resolver.cjs.map +1 -1
  93. package/dist/resolver.mjs +9 -1
  94. package/dist/resolver.mjs.map +1 -1
  95. package/dist/resolver.test.cjs +47 -12
  96. package/dist/resolver.test.cjs.map +1 -1
  97. package/dist/resolver.test.mjs +47 -12
  98. package/dist/resolver.test.mjs.map +1 -1
  99. package/dist/{schema-B4c64P8N.d.cts → schema-eX44HhRp.d.mts} +62 -8
  100. package/dist/schema-eX44HhRp.d.mts.map +1 -0
  101. package/dist/{schema-CXNhYci1.d.mts → schema-tn5RK8CM.d.cts} +62 -8
  102. package/dist/schema-tn5RK8CM.d.cts.map +1 -0
  103. package/dist/schema.cjs +22 -4
  104. package/dist/schema.cjs.map +1 -1
  105. package/dist/schema.d.cts +2 -2
  106. package/dist/schema.d.mts +2 -2
  107. package/dist/schema.mjs +21 -5
  108. package/dist/schema.mjs.map +1 -1
  109. package/dist/schema.test.cjs +1 -1
  110. package/dist/schema.test.mjs +1 -1
  111. package/dist/services/definitions/apptension-saas.cjs +87 -0
  112. package/dist/services/definitions/apptension-saas.cjs.map +1 -0
  113. package/dist/services/definitions/apptension-saas.d.cts +7 -0
  114. package/dist/services/definitions/apptension-saas.d.cts.map +1 -0
  115. package/dist/services/definitions/apptension-saas.d.mts +7 -0
  116. package/dist/services/definitions/apptension-saas.d.mts.map +1 -0
  117. package/dist/services/definitions/apptension-saas.mjs +86 -0
  118. package/dist/services/definitions/apptension-saas.mjs.map +1 -0
  119. package/dist/services/definitions/boxyhq-saas.cjs +88 -0
  120. package/dist/services/definitions/boxyhq-saas.cjs.map +1 -0
  121. package/dist/services/definitions/boxyhq-saas.d.cts +7 -0
  122. package/dist/services/definitions/boxyhq-saas.d.cts.map +1 -0
  123. package/dist/services/definitions/boxyhq-saas.d.mts +7 -0
  124. package/dist/services/definitions/boxyhq-saas.d.mts.map +1 -0
  125. package/dist/services/definitions/boxyhq-saas.mjs +87 -0
  126. package/dist/services/definitions/boxyhq-saas.mjs.map +1 -0
  127. package/dist/services/definitions/cmsaas-starter.cjs +86 -0
  128. package/dist/services/definitions/cmsaas-starter.cjs.map +1 -0
  129. package/dist/services/definitions/cmsaas-starter.d.cts +7 -0
  130. package/dist/services/definitions/cmsaas-starter.d.cts.map +1 -0
  131. package/dist/services/definitions/cmsaas-starter.d.mts +7 -0
  132. package/dist/services/definitions/cmsaas-starter.d.mts.map +1 -0
  133. package/dist/services/definitions/cmsaas-starter.mjs +85 -0
  134. package/dist/services/definitions/cmsaas-starter.mjs.map +1 -0
  135. package/dist/services/definitions/index.cjs +51 -36
  136. package/dist/services/definitions/index.cjs.map +1 -1
  137. package/dist/services/definitions/index.d.cts +30 -25
  138. package/dist/services/definitions/index.d.cts.map +1 -1
  139. package/dist/services/definitions/index.d.mts +30 -25
  140. package/dist/services/definitions/index.d.mts.map +1 -1
  141. package/dist/services/definitions/index.mjs +47 -37
  142. package/dist/services/definitions/index.mjs.map +1 -1
  143. package/dist/services/definitions/ixartz-saas.cjs +88 -0
  144. package/dist/services/definitions/ixartz-saas.cjs.map +1 -0
  145. package/dist/services/definitions/ixartz-saas.d.cts +7 -0
  146. package/dist/services/definitions/ixartz-saas.d.cts.map +1 -0
  147. package/dist/services/definitions/ixartz-saas.d.mts +7 -0
  148. package/dist/services/definitions/ixartz-saas.d.mts.map +1 -0
  149. package/dist/services/definitions/ixartz-saas.mjs +87 -0
  150. package/dist/services/definitions/ixartz-saas.mjs.map +1 -0
  151. package/dist/services/definitions/mission-control.cjs +16 -2
  152. package/dist/services/definitions/mission-control.cjs.map +1 -1
  153. package/dist/services/definitions/mission-control.mjs +16 -2
  154. package/dist/services/definitions/mission-control.mjs.map +1 -1
  155. package/dist/services/definitions/open-saas.cjs +81 -0
  156. package/dist/services/definitions/open-saas.cjs.map +1 -0
  157. package/dist/services/definitions/open-saas.d.cts +7 -0
  158. package/dist/services/definitions/open-saas.d.cts.map +1 -0
  159. package/dist/services/definitions/open-saas.d.mts +7 -0
  160. package/dist/services/definitions/open-saas.d.mts.map +1 -0
  161. package/dist/services/definitions/open-saas.mjs +80 -0
  162. package/dist/services/definitions/open-saas.mjs.map +1 -0
  163. package/dist/services/registry.cjs +3 -0
  164. package/dist/services/registry.cjs.map +1 -1
  165. package/dist/services/registry.d.cts.map +1 -1
  166. package/dist/services/registry.d.mts.map +1 -1
  167. package/dist/services/registry.mjs +3 -0
  168. package/dist/services/registry.mjs.map +1 -1
  169. package/dist/services/registry.test.cjs +8 -1
  170. package/dist/services/registry.test.cjs.map +1 -1
  171. package/dist/services/registry.test.mjs +8 -1
  172. package/dist/services/registry.test.mjs.map +1 -1
  173. package/dist/{skill-manifest-BVUXU0__.mjs → skill-manifest-6XhrhWsG.mjs} +49 -1
  174. package/dist/{skill-manifest--IgY9REK.cjs.map → skill-manifest-6XhrhWsG.mjs.map} +1 -1
  175. package/dist/{skill-manifest--IgY9REK.cjs → skill-manifest-B8znSsym.cjs} +49 -1
  176. package/dist/{skill-manifest-BVUXU0__.mjs.map → skill-manifest-B8znSsym.cjs.map} +1 -1
  177. package/dist/skills/registry.cjs +3 -3
  178. package/dist/skills/registry.cjs.map +1 -1
  179. package/dist/skills/registry.mjs +3 -3
  180. package/dist/skills/registry.mjs.map +1 -1
  181. package/dist/skills/skill-manifest.cjs +1 -1
  182. package/dist/skills/skill-manifest.mjs +1 -1
  183. package/dist/track-analytics.cjs +50 -0
  184. package/dist/track-analytics.cjs.map +1 -0
  185. package/dist/track-analytics.d.cts +34 -0
  186. package/dist/track-analytics.d.cts.map +1 -0
  187. package/dist/track-analytics.d.mts +34 -0
  188. package/dist/track-analytics.d.mts.map +1 -0
  189. package/dist/track-analytics.mjs +48 -0
  190. package/dist/track-analytics.mjs.map +1 -0
  191. package/dist/track-analytics.test.cjs +91 -0
  192. package/dist/track-analytics.test.cjs.map +1 -0
  193. package/dist/track-analytics.test.d.cts +1 -0
  194. package/dist/track-analytics.test.d.mts +1 -0
  195. package/dist/track-analytics.test.mjs +92 -0
  196. package/dist/track-analytics.test.mjs.map +1 -0
  197. package/dist/types.cjs +7 -0
  198. package/dist/types.cjs.map +1 -1
  199. package/dist/types.d.cts +4 -2
  200. package/dist/types.d.cts.map +1 -1
  201. package/dist/types.d.mts +4 -2
  202. package/dist/types.d.mts.map +1 -1
  203. package/dist/types.mjs +7 -0
  204. package/dist/types.mjs.map +1 -1
  205. package/dist/validator.test.cjs +1 -1
  206. package/dist/validator.test.mjs +1 -1
  207. package/dist/version-manager.cjs +1 -1
  208. package/dist/version-manager.cjs.map +1 -1
  209. package/dist/version-manager.mjs +1 -1
  210. package/dist/version-manager.mjs.map +1 -1
  211. package/dist/version-manager.test.cjs +7 -5
  212. package/dist/version-manager.test.cjs.map +1 -1
  213. package/dist/version-manager.test.mjs +7 -5
  214. package/dist/version-manager.test.mjs.map +1 -1
  215. package/dist/{vi.2VT5v0um-DvC3SVNc.mjs → vi.2VT5v0um-C_jmO7m2.mjs} +5 -5
  216. package/dist/{vi.2VT5v0um-DvC3SVNc.mjs.map → vi.2VT5v0um-C_jmO7m2.mjs.map} +1 -1
  217. package/dist/{vi.2VT5v0um-CRqXre87.cjs → vi.2VT5v0um-iVBt6Fyq.cjs} +5 -5
  218. package/dist/{vi.2VT5v0um-CRqXre87.cjs.map → vi.2VT5v0um-iVBt6Fyq.cjs.map} +1 -1
  219. package/package.json +1 -1
  220. package/src/__snapshots__/composer.snapshot.test.ts.snap +155 -0
  221. package/src/bare-metal-partition.test.ts +4 -3
  222. package/src/composer.test.ts +4 -2
  223. package/src/composer.ts +20 -1
  224. package/src/generate.test.ts +2 -1
  225. package/src/generate.ts +10 -1
  226. package/src/generators/clone-repos.test.ts +154 -0
  227. package/src/generators/clone-repos.ts +159 -0
  228. package/src/generators/postgres-init.ts +17 -0
  229. package/src/generators/scripts.test.ts +52 -4
  230. package/src/generators/scripts.ts +351 -3
  231. package/src/generators/stack-manifest.ts +4 -2
  232. package/src/index.ts +8 -0
  233. package/src/presets/registry.ts +241 -329
  234. package/src/resolver.test.ts +53 -15
  235. package/src/resolver.ts +13 -1
  236. package/src/schema.ts +33 -4
  237. package/src/services/definitions/apptension-saas.ts +84 -0
  238. package/src/services/definitions/boxyhq-saas.ts +84 -0
  239. package/src/services/definitions/cmsaas-starter.ts +84 -0
  240. package/src/services/definitions/index.ts +90 -70
  241. package/src/services/definitions/ixartz-saas.ts +84 -0
  242. package/src/services/definitions/mission-control.ts +19 -2
  243. package/src/services/definitions/open-saas.ts +79 -0
  244. package/src/services/registry.test.ts +8 -0
  245. package/src/services/registry.ts +7 -0
  246. package/src/skills/manifest.json +64 -0
  247. package/src/skills/registry.ts +3 -3
  248. package/src/track-analytics.test.ts +82 -0
  249. package/src/track-analytics.ts +76 -0
  250. package/src/types.ts +11 -0
  251. package/src/version-manager.test.ts +10 -5
  252. package/src/version-manager.ts +1 -1
  253. package/dist/schema-B4c64P8N.d.cts.map +0 -1
  254. package/dist/schema-CXNhYci1.d.mts.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-openclaw/core",
3
- "version": "1.0.23",
3
+ "version": "1.0.24",
4
4
  "private": false,
5
5
  "description": "Core logic for better-openclaw: schemas, service registry, resolver, composer, validators and generators",
6
6
  "author": "bidew.io <bachir@bidew.io>",
@@ -63,10 +63,39 @@ exports[`compose snapshot tests > creator preset (ffmpeg + remotion + minio + re
63
63
  retries: 5
64
64
  start_period: 20s
65
65
  depends_on:
66
+ convex:
67
+ condition: service_healthy
66
68
  minio:
67
69
  condition: service_healthy
68
70
  redis:
69
71
  condition: service_healthy
72
+ convex:
73
+ image: ghcr.io/get-convex/convex-backend:latest
74
+ environment:
75
+ CONVEX_CLOUD_ORIGIN: http://127.0.0.1:3210
76
+ CONVEX_SITE_ORIGIN: http://127.0.0.1:3211
77
+ INSTANCE_NAME: openclaw-convex
78
+ INSTANCE_SECRET: \${INSTANCE_SECRET}
79
+ DATABASE_URL: \${DATABASE_URL}
80
+ RUST_LOG: info
81
+ DISABLE_BEACON: ""
82
+ CONVEX_SELF_HOSTED_ADMIN_KEY: \${CONVEX_SELF_HOSTED_ADMIN_KEY}
83
+ ports:
84
+ - \${CONVEX_EXTERNAL_PORT_0:-3210}:3210
85
+ - \${CONVEX_EXTERNAL_PORT_1:-3211}:3211
86
+ volumes:
87
+ - convex-data:/convex/data
88
+ healthcheck:
89
+ test:
90
+ - CMD-SHELL
91
+ - curl -f http://localhost:3210/version
92
+ interval: 5s
93
+ timeout: 5s
94
+ retries: 5
95
+ start_period: 10s
96
+ restart: unless-stopped
97
+ networks:
98
+ - openclaw-network
70
99
  minio:
71
100
  image: minio/minio:RELEASE.2025-04-22T22-12-26Z
72
101
  environment:
@@ -146,9 +175,11 @@ exports[`compose snapshot tests > creator preset (ffmpeg + remotion + minio + re
146
175
  depends_on:
147
176
  - openclaw-gateway
148
177
  volumes:
178
+ convex-data:
149
179
  ffmpeg-shared:
150
180
  minio-data:
151
181
  redis-data:
182
+ tailscale-state:
152
183
  networks:
153
184
  openclaw-network:
154
185
  driver: bridge
@@ -246,12 +277,41 @@ exports[`compose snapshot tests > devops preset (n8n + postgresql + redis + moni
246
277
  retries: 5
247
278
  start_period: 20s
248
279
  depends_on:
280
+ convex:
281
+ condition: service_healthy
249
282
  postgresql:
250
283
  condition: service_healthy
251
284
  redis:
252
285
  condition: service_healthy
253
286
  n8n:
254
287
  condition: service_healthy
288
+ convex:
289
+ image: ghcr.io/get-convex/convex-backend:latest
290
+ environment:
291
+ CONVEX_CLOUD_ORIGIN: http://127.0.0.1:3210
292
+ CONVEX_SITE_ORIGIN: http://127.0.0.1:3211
293
+ INSTANCE_NAME: openclaw-convex
294
+ INSTANCE_SECRET: \${INSTANCE_SECRET}
295
+ DATABASE_URL: \${DATABASE_URL}
296
+ RUST_LOG: info
297
+ DISABLE_BEACON: ""
298
+ CONVEX_SELF_HOSTED_ADMIN_KEY: \${CONVEX_SELF_HOSTED_ADMIN_KEY}
299
+ ports:
300
+ - \${CONVEX_EXTERNAL_PORT_0:-3210}:3210
301
+ - \${CONVEX_EXTERNAL_PORT_1:-3211}:3211
302
+ volumes:
303
+ - convex-data:/convex/data
304
+ healthcheck:
305
+ test:
306
+ - CMD-SHELL
307
+ - curl -f http://localhost:3210/version
308
+ interval: 5s
309
+ timeout: 5s
310
+ retries: 5
311
+ start_period: 10s
312
+ restart: unless-stopped
313
+ networks:
314
+ - openclaw-network
255
315
  postgresql:
256
316
  image: postgres:17-alpine
257
317
  environment:
@@ -403,11 +463,13 @@ exports[`compose snapshot tests > devops preset (n8n + postgresql + redis + moni
403
463
  depends_on:
404
464
  - openclaw-gateway
405
465
  volumes:
466
+ convex-data:
406
467
  grafana-data:
407
468
  n8n-data:
408
469
  postgres-data:
409
470
  prometheus-data:
410
471
  redis-data:
472
+ tailscale-state:
411
473
  uptime-kuma-data:
412
474
  networks:
413
475
  openclaw-network:
@@ -580,6 +642,8 @@ exports[`compose snapshot tests > full preset (many services) 1`] = `
580
642
  condition: service_healthy
581
643
  caddy:
582
644
  condition: service_healthy
645
+ convex:
646
+ condition: service_healthy
583
647
  meilisearch:
584
648
  condition: service_healthy
585
649
  minio:
@@ -631,6 +695,33 @@ exports[`compose snapshot tests > full preset (many services) 1`] = `
631
695
  restart: unless-stopped
632
696
  networks:
633
697
  - openclaw-network
698
+ convex:
699
+ image: ghcr.io/get-convex/convex-backend:latest
700
+ environment:
701
+ CONVEX_CLOUD_ORIGIN: http://127.0.0.1:3210
702
+ CONVEX_SITE_ORIGIN: http://127.0.0.1:3211
703
+ INSTANCE_NAME: openclaw-convex
704
+ INSTANCE_SECRET: \${INSTANCE_SECRET}
705
+ DATABASE_URL: \${DATABASE_URL}
706
+ RUST_LOG: info
707
+ DISABLE_BEACON: ""
708
+ CONVEX_SELF_HOSTED_ADMIN_KEY: \${CONVEX_SELF_HOSTED_ADMIN_KEY}
709
+ ports:
710
+ - \${CONVEX_EXTERNAL_PORT_0:-3210}:3210
711
+ - \${CONVEX_EXTERNAL_PORT_1:-3211}:3211
712
+ volumes:
713
+ - convex-data:/convex/data
714
+ healthcheck:
715
+ test:
716
+ - CMD-SHELL
717
+ - curl -f http://localhost:3210/version
718
+ interval: 5s
719
+ timeout: 5s
720
+ retries: 5
721
+ start_period: 10s
722
+ restart: unless-stopped
723
+ networks:
724
+ - openclaw-network
634
725
  meilisearch:
635
726
  image: getmeili/meilisearch:v1.35.1
636
727
  environment:
@@ -861,6 +952,7 @@ exports[`compose snapshot tests > full preset (many services) 1`] = `
861
952
  volumes:
862
953
  caddy-config:
863
954
  caddy-data:
955
+ convex-data:
864
956
  ffmpeg-shared:
865
957
  gotify-data:
866
958
  grafana-data:
@@ -873,6 +965,7 @@ volumes:
873
965
  qdrant-data:
874
966
  redis-data:
875
967
  searxng-data:
968
+ tailscale-state:
876
969
  uptime-kuma-data:
877
970
  networks:
878
971
  openclaw-network:
@@ -935,8 +1028,37 @@ exports[`compose snapshot tests > minimal preset (redis only) 1`] = `
935
1028
  retries: 5
936
1029
  start_period: 20s
937
1030
  depends_on:
1031
+ convex:
1032
+ condition: service_healthy
938
1033
  redis:
939
1034
  condition: service_healthy
1035
+ convex:
1036
+ image: ghcr.io/get-convex/convex-backend:latest
1037
+ environment:
1038
+ CONVEX_CLOUD_ORIGIN: http://127.0.0.1:3210
1039
+ CONVEX_SITE_ORIGIN: http://127.0.0.1:3211
1040
+ INSTANCE_NAME: openclaw-convex
1041
+ INSTANCE_SECRET: \${INSTANCE_SECRET}
1042
+ DATABASE_URL: \${DATABASE_URL}
1043
+ RUST_LOG: info
1044
+ DISABLE_BEACON: ""
1045
+ CONVEX_SELF_HOSTED_ADMIN_KEY: \${CONVEX_SELF_HOSTED_ADMIN_KEY}
1046
+ ports:
1047
+ - \${CONVEX_EXTERNAL_PORT_0:-3210}:3210
1048
+ - \${CONVEX_EXTERNAL_PORT_1:-3211}:3211
1049
+ volumes:
1050
+ - convex-data:/convex/data
1051
+ healthcheck:
1052
+ test:
1053
+ - CMD-SHELL
1054
+ - curl -f http://localhost:3210/version
1055
+ interval: 5s
1056
+ timeout: 5s
1057
+ retries: 5
1058
+ start_period: 10s
1059
+ restart: unless-stopped
1060
+ networks:
1061
+ - openclaw-network
940
1062
  redis:
941
1063
  image: redis:8-alpine
942
1064
  environment:
@@ -994,7 +1116,9 @@ exports[`compose snapshot tests > minimal preset (redis only) 1`] = `
994
1116
  depends_on:
995
1117
  - openclaw-gateway
996
1118
  volumes:
1119
+ convex-data:
997
1120
  redis-data:
1121
+ tailscale-state:
998
1122
  networks:
999
1123
  openclaw-network:
1000
1124
  driver: bridge
@@ -1065,6 +1189,8 @@ exports[`compose snapshot tests > researcher preset (qdrant + searxng + browserl
1065
1189
  depends_on:
1066
1190
  browserless:
1067
1191
  condition: service_healthy
1192
+ convex:
1193
+ condition: service_healthy
1068
1194
  qdrant:
1069
1195
  condition: service_healthy
1070
1196
  redis:
@@ -1089,6 +1215,33 @@ exports[`compose snapshot tests > researcher preset (qdrant + searxng + browserl
1089
1215
  restart: unless-stopped
1090
1216
  networks:
1091
1217
  - openclaw-network
1218
+ convex:
1219
+ image: ghcr.io/get-convex/convex-backend:latest
1220
+ environment:
1221
+ CONVEX_CLOUD_ORIGIN: http://127.0.0.1:3210
1222
+ CONVEX_SITE_ORIGIN: http://127.0.0.1:3211
1223
+ INSTANCE_NAME: openclaw-convex
1224
+ INSTANCE_SECRET: \${INSTANCE_SECRET}
1225
+ DATABASE_URL: \${DATABASE_URL}
1226
+ RUST_LOG: info
1227
+ DISABLE_BEACON: ""
1228
+ CONVEX_SELF_HOSTED_ADMIN_KEY: \${CONVEX_SELF_HOSTED_ADMIN_KEY}
1229
+ ports:
1230
+ - \${CONVEX_EXTERNAL_PORT_0:-3210}:3210
1231
+ - \${CONVEX_EXTERNAL_PORT_1:-3211}:3211
1232
+ volumes:
1233
+ - convex-data:/convex/data
1234
+ healthcheck:
1235
+ test:
1236
+ - CMD-SHELL
1237
+ - curl -f http://localhost:3210/version
1238
+ interval: 5s
1239
+ timeout: 5s
1240
+ retries: 5
1241
+ start_period: 10s
1242
+ restart: unless-stopped
1243
+ networks:
1244
+ - openclaw-network
1092
1245
  qdrant:
1093
1246
  image: qdrant/qdrant:v1.12.1
1094
1247
  environment:
@@ -1182,9 +1335,11 @@ exports[`compose snapshot tests > researcher preset (qdrant + searxng + browserl
1182
1335
  depends_on:
1183
1336
  - openclaw-gateway
1184
1337
  volumes:
1338
+ convex-data:
1185
1339
  qdrant-data:
1186
1340
  redis-data:
1187
1341
  searxng-data:
1342
+ tailscale-state:
1188
1343
  networks:
1189
1344
  openclaw-network:
1190
1345
  driver: bridge
@@ -25,9 +25,10 @@ describe("bare-metal partition", () => {
25
25
  });
26
26
  const result = partitionBareMetal(resolved, "linux/amd64");
27
27
  expect(result.nativeIds.has("redis")).toBe(true);
28
- expect(result.nativeServices.length).toBe(1);
29
- expect(result.nativeServices[0].definition.id).toBe("redis");
30
- expect(result.dockerOnlyServices.length).toBe(0);
28
+ const redisNative = result.nativeServices.find((s) => s.definition.id === "redis");
29
+ expect(redisNative).toBeDefined();
30
+ // Mandatory services (convex, mission-control, tailscale) are Docker-only
31
+ expect(result.dockerOnlyServices.length).toBeGreaterThanOrEqual(0);
31
32
  });
32
33
 
33
34
  it("partitions n8n as Docker-only (no native recipe)", () => {
@@ -38,8 +38,10 @@ describe("compose", () => {
38
38
  // Should have CLI companion service
39
39
  expect(parsed.services).toHaveProperty("openclaw-cli");
40
40
 
41
- // Gateway should have no depends_on (no companions)
42
- expect(parsed.services["openclaw-gateway"]).not.toHaveProperty("depends_on");
41
+ // Gateway should have depends_on for mandatory services (convex, mission-control, tailscale)
42
+ // Even with no user-selected services, mandatory services are always present
43
+ expect(parsed.services).toHaveProperty("convex");
44
+ expect(parsed.services).toHaveProperty("mission-control");
43
45
 
44
46
  // Gateway should have restart policy
45
47
  expect(parsed.services["openclaw-gateway"].restart).toBe("unless-stopped");
package/src/composer.ts CHANGED
@@ -50,6 +50,7 @@ const CATEGORY_PROFILE_MAP: Partial<Record<ServiceCategory, { file: string; prof
50
50
  "social-media": { file: "docker-compose.social.yml", profile: "social" },
51
51
  knowledge: { file: "docker-compose.knowledge.yml", profile: "knowledge" },
52
52
  communication: { file: "docker-compose.communication.yml", profile: "communication" },
53
+ "saas-boilerplate": { file: "docker-compose.saas.yml", profile: "saas" },
53
54
  };
54
55
 
55
56
  const YAML_OPTIONS = { lineWidth: 120, nullStr: "" };
@@ -230,7 +231,25 @@ function buildCompanionService(
230
231
  const svc: Record<string, unknown> = {};
231
232
  const volumeNames: string[] = [];
232
233
 
233
- svc.image = `${def.image}:${def.imageTag}`;
234
+ // Git-based services use build: context; image-based services use image:
235
+ if (def.gitSource && def.buildContext) {
236
+ const subdir = def.gitSource.subdirectory || ".";
237
+ const ctxPath = def.buildContext.context || ".";
238
+ const contextFull = subdir === "." ? `./repos/${def.id}/${ctxPath}` : `./repos/${def.id}/${subdir}/${ctxPath}`;
239
+ const buildBlock: Record<string, unknown> = { context: contextFull };
240
+ if (def.buildContext.dockerfile) {
241
+ buildBlock.dockerfile = def.buildContext.dockerfile;
242
+ }
243
+ if (def.buildContext.args && Object.keys(def.buildContext.args).length > 0) {
244
+ buildBlock.args = def.buildContext.args;
245
+ }
246
+ if (def.buildContext.target) {
247
+ buildBlock.target = def.buildContext.target;
248
+ }
249
+ svc.build = buildBlock;
250
+ } else {
251
+ svc.image = `${def.image}:${def.imageTag}`;
252
+ }
234
253
 
235
254
  if (def.environment.length > 0) {
236
255
  const env: Record<string, string> = {};
@@ -187,7 +187,8 @@ describe("generate (end-to-end)", () => {
187
187
  for (const id of lasuiteMeetServices) {
188
188
  expect(allServiceIds.has(id), `missing service ${id}`).toBe(true);
189
189
  }
190
- expect(result.metadata.serviceCount).toBe(lasuiteMeetServices.length);
190
+ // Service count includes user services + mandatory platform services (convex, mission-control, tailscale)
191
+ expect(result.metadata.serviceCount).toBeGreaterThanOrEqual(lasuiteMeetServices.length);
191
192
  });
192
193
 
193
194
  it("generates bare-metal installer for Windows (install.ps1)", () => {
package/src/generate.ts CHANGED
@@ -7,6 +7,7 @@ import { composeMultiFile } from "./composer.js";
7
7
  import { StackConfigError, ValidationError } from "./errors.js";
8
8
  import { generateBareMetalInstall } from "./generators/bare-metal-install.js";
9
9
  import { generateCaddyfile } from "./generators/caddy.js";
10
+ import { generateCloneScripts } from "./generators/clone-repos.js";
10
11
  import { generateCloudInit } from "./generators/cloud-init.js";
11
12
  import { generateEnvFiles } from "./generators/env.js";
12
13
  import { generateGsdScripts } from "./generators/get-shit-done.js";
@@ -149,6 +150,7 @@ export function generate(rawInput: GenerationInput): GenerationResult {
149
150
  ".env.*.local",
150
151
  "*.log",
151
152
  "docker-compose.override.yml",
153
+ "repos/",
152
154
  ].join("\n");
153
155
 
154
156
  // Stack manifest (consumed by Mission Control)
@@ -181,11 +183,18 @@ export function generate(rawInput: GenerationInput): GenerationResult {
181
183
  });
182
184
 
183
185
  // Scripts
184
- const scripts = generateScripts();
186
+ const hasGitServices = resolvedForCompose.services.some((s) => s.definition.gitSource);
187
+ const scripts = generateScripts({ hasGitServices });
185
188
  for (const [path, content] of Object.entries(scripts)) {
186
189
  files[path] = content;
187
190
  }
188
191
 
192
+ // Clone scripts for git-based services (SaaS boilerplates)
193
+ const cloneScripts = generateCloneScripts(resolvedForCompose);
194
+ for (const [path, content] of Object.entries(cloneScripts)) {
195
+ files[path] = content;
196
+ }
197
+
189
198
  // Health check scripts (dynamic, stack-specific)
190
199
  const healthCheckFiles = generateHealthCheck(resolved, {
191
200
  projectName: input.projectName,
@@ -0,0 +1,154 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { generateCloneScripts } from "./clone-repos.js";
3
+ import type { ResolverOutput } from "../types.js";
4
+
5
+ /** Minimal resolved output with no services */
6
+ function emptyResolved(): ResolverOutput {
7
+ return {
8
+ services: [],
9
+ addedDependencies: [],
10
+ removedConflicts: [],
11
+ warnings: [],
12
+ errors: [],
13
+ isValid: true,
14
+ estimatedMemoryMB: 0,
15
+ aiProviders: [],
16
+ gsdRuntimes: [],
17
+ };
18
+ }
19
+
20
+ /** Create a resolved output with image-based services only */
21
+ function imageOnlyResolved(): ResolverOutput {
22
+ return {
23
+ ...emptyResolved(),
24
+ services: [
25
+ {
26
+ definition: {
27
+ id: "redis",
28
+ name: "Redis",
29
+ description: "In-memory cache",
30
+ category: "database",
31
+ icon: "🔴",
32
+ image: "redis",
33
+ imageTag: "7-alpine",
34
+ ports: [],
35
+ volumes: [],
36
+ environment: [],
37
+ dependsOn: [],
38
+ restartPolicy: "unless-stopped",
39
+ networks: ["openclaw-network"],
40
+ skills: [],
41
+ openclawEnvVars: [],
42
+ docsUrl: "https://redis.io",
43
+ tags: [],
44
+ maturity: "stable",
45
+ requires: [],
46
+ recommends: [],
47
+ conflictsWith: [],
48
+ gpuRequired: false,
49
+ },
50
+ addedBy: "user",
51
+ },
52
+ ],
53
+ };
54
+ }
55
+
56
+ /** Create a resolved output with a git-based service */
57
+ function gitServiceResolved(): ResolverOutput {
58
+ return {
59
+ ...emptyResolved(),
60
+ services: [
61
+ {
62
+ definition: {
63
+ id: "open-saas",
64
+ name: "Open SaaS",
65
+ description: "SaaS boilerplate",
66
+ category: "saas-boilerplate",
67
+ icon: "🚀",
68
+ gitSource: {
69
+ repoUrl: "https://github.com/wasp-lang/open-saas.git",
70
+ branch: "main",
71
+ subdirectory: "template",
72
+ postCloneCommands: ["cp .env.example .env"],
73
+ },
74
+ buildContext: {
75
+ dockerfile: "Dockerfile",
76
+ context: ".",
77
+ },
78
+ ports: [{ host: 3100, container: 3000, description: "Web app", exposed: true }],
79
+ volumes: [],
80
+ environment: [],
81
+ dependsOn: [],
82
+ restartPolicy: "unless-stopped",
83
+ networks: ["openclaw-network"],
84
+ skills: [],
85
+ openclawEnvVars: [],
86
+ docsUrl: "https://opensaas.sh/docs",
87
+ tags: [],
88
+ maturity: "beta",
89
+ requires: [],
90
+ recommends: [],
91
+ conflictsWith: [],
92
+ gpuRequired: false,
93
+ },
94
+ addedBy: "user",
95
+ },
96
+ ],
97
+ };
98
+ }
99
+
100
+ describe("generateCloneScripts", () => {
101
+ it("returns empty object when no git-based services exist", () => {
102
+ const result = generateCloneScripts(emptyResolved());
103
+ expect(result).toEqual({});
104
+ });
105
+
106
+ it("returns empty object when only image-based services exist", () => {
107
+ const result = generateCloneScripts(imageOnlyResolved());
108
+ expect(result).toEqual({});
109
+ });
110
+
111
+ it("generates bash and PowerShell scripts for git-based services", () => {
112
+ const result = generateCloneScripts(gitServiceResolved());
113
+ expect(Object.keys(result)).toHaveLength(2);
114
+ expect(result).toHaveProperty("scripts/clone-repos.sh");
115
+ expect(result).toHaveProperty("scripts/clone-repos.ps1");
116
+ });
117
+
118
+ it("bash script contains clone command with repo URL", () => {
119
+ const result = generateCloneScripts(gitServiceResolved());
120
+ const bash = result["scripts/clone-repos.sh"];
121
+ expect(bash).toContain("https://github.com/wasp-lang/open-saas.git");
122
+ expect(bash).toContain('"open-saas"');
123
+ expect(bash).toContain('"main"');
124
+ });
125
+
126
+ it("bash script includes postCloneCommands", () => {
127
+ const result = generateCloneScripts(gitServiceResolved());
128
+ const bash = result["scripts/clone-repos.sh"];
129
+ expect(bash).toContain("cp .env.example .env");
130
+ });
131
+
132
+ it("bash script includes git check and idempotency logic", () => {
133
+ const result = generateCloneScripts(gitServiceResolved());
134
+ const bash = result["scripts/clone-repos.sh"];
135
+ expect(bash).toContain("command -v git");
136
+ expect(bash).toContain("clone_or_update");
137
+ expect(bash).toContain("pull --ff-only");
138
+ expect(bash).toContain("git clone --depth 1");
139
+ });
140
+
141
+ it("PowerShell script contains clone command with repo URL", () => {
142
+ const result = generateCloneScripts(gitServiceResolved());
143
+ const ps = result["scripts/clone-repos.ps1"];
144
+ expect(ps).toContain("https://github.com/wasp-lang/open-saas.git");
145
+ expect(ps).toContain('"open-saas"');
146
+ expect(ps).toContain("Clone-OrUpdate");
147
+ });
148
+
149
+ it("bash script is executable (starts with shebang)", () => {
150
+ const result = generateCloneScripts(gitServiceResolved());
151
+ const bash = result["scripts/clone-repos.sh"];
152
+ expect(bash).toMatch(/^#!/);
153
+ });
154
+ });
@@ -0,0 +1,159 @@
1
+ import type { ResolverOutput } from "../types.js";
2
+
3
+ /**
4
+ * Generates clone scripts for git-based services (SaaS boilerplates).
5
+ * Returns empty object if no git-based services exist in the resolved stack.
6
+ */
7
+ export function generateCloneScripts(resolved: ResolverOutput): Record<string, string> {
8
+ const gitServices = resolved.services.filter(
9
+ (s) => s.definition.gitSource && s.definition.buildContext,
10
+ );
11
+
12
+ if (gitServices.length === 0) return {};
13
+
14
+ const files: Record<string, string> = {};
15
+
16
+ // ── scripts/clone-repos.sh ─────────────────────────────────────────────
17
+
18
+ const bashEntries = gitServices
19
+ .map((s) => {
20
+ const gs = s.definition.gitSource!;
21
+ const branchArg = gs.branch ? `"${gs.branch}"` : '""';
22
+ let block = `clone_or_update "${s.definition.id}" "${gs.repoUrl}" ${branchArg}`;
23
+ if (gs.postCloneCommands && gs.postCloneCommands.length > 0) {
24
+ const cmds = gs.postCloneCommands
25
+ .map((cmd) => ` (cd "$REPOS_DIR/${s.definition.id}${gs.subdirectory ? `/${gs.subdirectory}` : ""}" && ${cmd})`)
26
+ .join("\n");
27
+ block += `\n${cmds}`;
28
+ }
29
+ return block;
30
+ })
31
+ .join("\n\n");
32
+
33
+ files["scripts/clone-repos.sh"] = `#!/usr/bin/env bash
34
+ set -euo pipefail
35
+
36
+ # ─── Clone/Update Git-Based Service Repositories ────────────────────────────
37
+ # Idempotent: clones if missing, pulls if already present.
38
+
39
+ SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
40
+ PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
41
+ REPOS_DIR="$PROJECT_DIR/repos"
42
+
43
+ # ── Colour helpers ──────────────────────────────────────────────────────────
44
+ if [ -t 1 ]; then
45
+ GREEN='\\033[0;32m'; YELLOW='\\033[1;33m'; CYAN='\\033[0;36m'; RED='\\033[0;31m'; NC='\\033[0m'
46
+ else
47
+ GREEN=''; YELLOW=''; CYAN=''; RED=''; NC=''
48
+ fi
49
+ info() { echo -e "\${CYAN}i $*\${NC}"; }
50
+ ok() { echo -e "\${GREEN}✓ $*\${NC}"; }
51
+ warn() { echo -e "\${YELLOW}⚠ $*\${NC}"; }
52
+ err() { echo -e "\${RED}✗ $*\${NC}" >&2; }
53
+
54
+ # ── Check git ───────────────────────────────────────────────────────────────
55
+ if ! command -v git &> /dev/null; then
56
+ err "git is not installed. Please install git first."
57
+ exit 1
58
+ fi
59
+
60
+ mkdir -p "$REPOS_DIR"
61
+
62
+ clone_or_update() {
63
+ local name="$1" url="$2" branch="\${3:-}"
64
+ local dir="$REPOS_DIR/$name"
65
+
66
+ if [ -d "$dir/.git" ]; then
67
+ info "Updating $name..."
68
+ git -C "$dir" pull --ff-only 2>/dev/null || warn "Could not fast-forward $name (you may have local changes)"
69
+ else
70
+ info "Cloning $name..."
71
+ if [ -n "$branch" ]; then
72
+ git clone --depth 1 --branch "$branch" "$url" "$dir"
73
+ else
74
+ git clone --depth 1 "$url" "$dir"
75
+ fi
76
+ fi
77
+ }
78
+
79
+ echo ""
80
+ info "Cloning/updating SaaS boilerplate repositories..."
81
+ echo ""
82
+
83
+ ${bashEntries}
84
+
85
+ echo ""
86
+ ok "All repositories ready."
87
+ `;
88
+
89
+ // ── scripts/clone-repos.ps1 ────────────────────────────────────────────
90
+
91
+ const psEntries = gitServices
92
+ .map((s) => {
93
+ const gs = s.definition.gitSource!;
94
+ const branchArg = gs.branch ? ` -Branch "${gs.branch}"` : "";
95
+ let block = `Clone-OrUpdate -Name "${s.definition.id}" -Url "${gs.repoUrl}"${branchArg}`;
96
+ if (gs.postCloneCommands && gs.postCloneCommands.length > 0) {
97
+ const subdir = gs.subdirectory ? `/${gs.subdirectory}` : "";
98
+ const cmds = gs.postCloneCommands
99
+ .map((cmd) => `Push-Location "$ReposDir/${s.definition.id}${subdir}"; ${cmd}; Pop-Location`)
100
+ .join("\n");
101
+ block += `\n${cmds}`;
102
+ }
103
+ return block;
104
+ })
105
+ .join("\n\n");
106
+
107
+ files["scripts/clone-repos.ps1"] = `#Requires -Version 5.1
108
+ <#
109
+ .SYNOPSIS
110
+ Clone/update git-based service repositories.
111
+ Idempotent: clones if missing, pulls if already present.
112
+ #>
113
+ $ErrorActionPreference = "Stop"
114
+
115
+ $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
116
+ $ProjectDir = Split-Path -Parent $ScriptDir
117
+ $ReposDir = Join-Path $ProjectDir "repos"
118
+
119
+ if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
120
+ Write-Error "git is not installed. Please install git first."
121
+ exit 1
122
+ }
123
+
124
+ if (-not (Test-Path $ReposDir)) { New-Item -ItemType Directory -Path $ReposDir -Force | Out-Null }
125
+
126
+ function Clone-OrUpdate {
127
+ param(
128
+ [string]$Name,
129
+ [string]$Url,
130
+ [string]$Branch = ""
131
+ )
132
+ $dir = Join-Path $ReposDir $Name
133
+
134
+ if (Test-Path (Join-Path $dir ".git")) {
135
+ Write-Host " Updating $Name..." -ForegroundColor Cyan
136
+ git -C $dir pull --ff-only 2>$null
137
+ if ($LASTEXITCODE -ne 0) { Write-Warning "Could not fast-forward $Name" }
138
+ } else {
139
+ Write-Host " Cloning $Name..." -ForegroundColor Cyan
140
+ if ($Branch) {
141
+ git clone --depth 1 --branch $Branch $Url $dir
142
+ } else {
143
+ git clone --depth 1 $Url $dir
144
+ }
145
+ }
146
+ }
147
+
148
+ Write-Host ""
149
+ Write-Host "Cloning/updating SaaS boilerplate repositories..." -ForegroundColor Cyan
150
+ Write-Host ""
151
+
152
+ ${psEntries}
153
+
154
+ Write-Host ""
155
+ Write-Host "All repositories ready." -ForegroundColor Green
156
+ `;
157
+
158
+ return files;
159
+ }