create-nextblock 0.2.77 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (413) hide show
  1. package/bin/create-nextblock.js +740 -459
  2. package/package.json +1 -2
  3. package/scripts/sync-template.js +18 -1
  4. package/templates/nextblock-template/.browserslistrc +11 -0
  5. package/templates/nextblock-template/.swcrc +30 -30
  6. package/templates/nextblock-template/README.md +23 -114
  7. package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +27 -28
  8. package/templates/nextblock-template/app/(auth-pages)/sign-in/page.tsx +50 -25
  9. package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +111 -56
  10. package/templates/nextblock-template/app/(auth-pages)/two-factor/actions.ts +91 -0
  11. package/templates/nextblock-template/app/(auth-pages)/two-factor/components/TwoFactorForm.tsx +118 -0
  12. package/templates/nextblock-template/app/(auth-pages)/two-factor/page.tsx +51 -0
  13. package/templates/nextblock-template/app/.well-known/ucp/route.ts +16 -0
  14. package/templates/nextblock-template/app/[slug]/PageClientContent.tsx +48 -28
  15. package/templates/nextblock-template/app/[slug]/page.tsx +63 -6
  16. package/templates/nextblock-template/app/[slug]/page.utils.ts +374 -157
  17. package/templates/nextblock-template/app/[slug]/pageClientActions.ts +7 -0
  18. package/templates/nextblock-template/app/actions/consent.ts +57 -0
  19. package/templates/nextblock-template/app/actions/formActions.ts +130 -11
  20. package/templates/nextblock-template/app/actions/languageActions.ts +31 -30
  21. package/templates/nextblock-template/app/actions/package-actions.ts +183 -0
  22. package/templates/nextblock-template/app/actions/postActions.ts +146 -48
  23. package/templates/nextblock-template/app/actions/twoFactorEmail.ts +21 -0
  24. package/templates/nextblock-template/app/actions/visualEditingActions.test.ts +179 -0
  25. package/templates/nextblock-template/app/actions/visualEditingActions.ts +345 -0
  26. package/templates/nextblock-template/app/actions.ts +67 -12
  27. package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +153 -0
  28. package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +96 -0
  29. package/templates/nextblock-template/app/api/ai/global-agent/route.ts +965 -0
  30. package/templates/nextblock-template/app/api/checkout/freemius/sync/route.ts +29 -0
  31. package/templates/nextblock-template/app/api/checkout/route.ts +146 -0
  32. package/templates/nextblock-template/app/api/cms/full-backup/export/route.ts +33 -0
  33. package/templates/nextblock-template/app/api/cms/full-backup/restore/route.ts +63 -0
  34. package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +3413 -17
  35. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +7830 -0
  36. package/templates/nextblock-template/app/api/cron/sync-currencies/route.ts +35 -0
  37. package/templates/nextblock-template/app/api/custom-blocks/db-relations/route.ts +92 -0
  38. package/templates/nextblock-template/app/api/custom-blocks/editor-definitions/route.ts +43 -0
  39. package/templates/nextblock-template/app/api/draft/disable/route.ts +25 -0
  40. package/templates/nextblock-template/app/api/draft/route.ts +93 -0
  41. package/templates/nextblock-template/app/api/draft/start/route.ts +77 -0
  42. package/templates/nextblock-template/app/api/media/library/route.ts +65 -0
  43. package/templates/nextblock-template/app/api/media/r2-presigned/route.ts +53 -0
  44. package/templates/nextblock-template/app/api/media/record/route.ts +160 -0
  45. package/templates/nextblock-template/app/api/search/route.ts +43 -0
  46. package/templates/nextblock-template/app/api/visual-editing/block-draft/route.ts +47 -0
  47. package/templates/nextblock-template/app/api/visual-editing/product-draft/route.ts +47 -0
  48. package/templates/nextblock-template/app/api/webhooks/freemius/route.ts +34 -0
  49. package/templates/nextblock-template/app/api/webhooks/stripe/route.ts +27 -0
  50. package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +392 -128
  51. package/templates/nextblock-template/app/article/[slug]/page.tsx +179 -127
  52. package/templates/nextblock-template/app/article/[slug]/page.utils.ts +262 -77
  53. package/templates/nextblock-template/app/auth/callback/route.ts +31 -58
  54. package/templates/nextblock-template/app/cart/page.tsx +7 -0
  55. package/templates/nextblock-template/app/checkout/UcpCartHydrator.tsx +20 -0
  56. package/templates/nextblock-template/app/checkout/page.tsx +52 -0
  57. package/templates/nextblock-template/app/checkout/success/actions.ts +136 -0
  58. package/templates/nextblock-template/app/checkout/success/page.tsx +186 -0
  59. package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +163 -33
  60. package/templates/nextblock-template/app/cms/blocks/actions.ts +424 -235
  61. package/templates/nextblock-template/app/cms/blocks/components/BackgroundSelector.tsx +212 -151
  62. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorArea.tsx +41 -20
  63. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +152 -19
  64. package/templates/nextblock-template/app/cms/blocks/components/BlockTypeCard.tsx +25 -17
  65. package/templates/nextblock-template/app/cms/blocks/components/BlockTypeSelector.tsx +200 -18
  66. package/templates/nextblock-template/app/cms/blocks/components/ColumnEditor.tsx +33 -16
  67. package/templates/nextblock-template/app/cms/blocks/components/CustomBlockEditorPreview.tsx +160 -0
  68. package/templates/nextblock-template/app/cms/blocks/components/EditableBlock.tsx +37 -18
  69. package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +149 -67
  70. package/templates/nextblock-template/app/cms/blocks/components/SectionConfigPanel.tsx +108 -31
  71. package/templates/nextblock-template/app/cms/blocks/editors/DynamicCustomBlockEditor.tsx +167 -0
  72. package/templates/nextblock-template/app/cms/blocks/editors/FeaturedProductBlockEditor.tsx +31 -0
  73. package/templates/nextblock-template/app/cms/blocks/editors/FormBlockEditor.tsx +2 -2
  74. package/templates/nextblock-template/app/cms/blocks/editors/HeadingBlockEditor.tsx +1 -1
  75. package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +29 -29
  76. package/templates/nextblock-template/app/cms/blocks/editors/PostsGridBlockEditor.tsx +14 -18
  77. package/templates/nextblock-template/app/cms/blocks/editors/ProductGridBlockEditor.tsx +41 -0
  78. package/templates/nextblock-template/app/cms/blocks/editors/SectionBlockEditor.tsx +318 -118
  79. package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +98 -21
  80. package/templates/nextblock-template/app/cms/blocks/editors/VideoEmbedBlockEditor.tsx +1 -1
  81. package/templates/nextblock-template/app/cms/components/ContentLanguageSwitcher.tsx +27 -9
  82. package/templates/nextblock-template/app/cms/components/CopyContentFromLanguage.tsx +1 -1
  83. package/templates/nextblock-template/app/cms/components/CortexAiActiveContext.tsx +23 -0
  84. package/templates/nextblock-template/app/cms/components/CortexAiPageContext.tsx +58 -0
  85. package/templates/nextblock-template/app/cms/components/CortexGlobalAgentChat.tsx +1507 -0
  86. package/templates/nextblock-template/app/cms/components/DraftStatusActions.tsx +145 -0
  87. package/templates/nextblock-template/app/cms/components/FeatureImageField.tsx +244 -0
  88. package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +38 -24
  89. package/templates/nextblock-template/app/cms/coupons/[id]/edit/page.tsx +16 -0
  90. package/templates/nextblock-template/app/cms/coupons/page.tsx +16 -0
  91. package/templates/nextblock-template/app/cms/custom-blocks/[id]/edit/page.tsx +66 -0
  92. package/templates/nextblock-template/app/cms/custom-blocks/actions.ts +519 -0
  93. package/templates/nextblock-template/app/cms/custom-blocks/components/BlockComposer.tsx +1522 -0
  94. package/templates/nextblock-template/app/cms/custom-blocks/components/BlocksLibraryTransferControls.tsx +256 -0
  95. package/templates/nextblock-template/app/cms/custom-blocks/components/DBRelationSelect.tsx +384 -0
  96. package/templates/nextblock-template/app/cms/custom-blocks/components/ImageR2Picker.tsx +221 -0
  97. package/templates/nextblock-template/app/cms/custom-blocks/new/page.tsx +12 -0
  98. package/templates/nextblock-template/app/cms/custom-blocks/page.tsx +438 -0
  99. package/templates/nextblock-template/app/cms/dashboard/actions.ts +228 -98
  100. package/templates/nextblock-template/app/cms/dashboard/components/DashboardComponents.tsx +200 -0
  101. package/templates/nextblock-template/app/cms/dashboard/page.tsx +191 -151
  102. package/templates/nextblock-template/app/cms/import-export/ContentTransferControls.tsx +391 -0
  103. package/templates/nextblock-template/app/cms/import-export/actions.ts +226 -0
  104. package/templates/nextblock-template/app/cms/layout.tsx +29 -10
  105. package/templates/nextblock-template/app/cms/media/UploadFolderContext.tsx +22 -22
  106. package/templates/nextblock-template/app/cms/media/actions.ts +45 -124
  107. package/templates/nextblock-template/app/cms/media/components/DeleteMediaButtonClient.tsx +1 -1
  108. package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +26 -26
  109. package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +69 -64
  110. package/templates/nextblock-template/app/cms/media/components/MediaPickerDialog.tsx +227 -158
  111. package/templates/nextblock-template/app/cms/media/components/MediaUploadForm.tsx +101 -89
  112. package/templates/nextblock-template/app/cms/media/page.tsx +1 -1
  113. package/templates/nextblock-template/app/cms/navigation/components/NavigationItemForm.tsx +2 -2
  114. package/templates/nextblock-template/app/cms/orders/[id]/MarkPaidButton.tsx +44 -0
  115. package/templates/nextblock-template/app/cms/orders/[id]/page.tsx +16 -0
  116. package/templates/nextblock-template/app/cms/orders/actions.ts +201 -0
  117. package/templates/nextblock-template/app/cms/orders/page.tsx +20 -0
  118. package/templates/nextblock-template/app/cms/orders/types.ts +20 -0
  119. package/templates/nextblock-template/app/cms/pages/[id]/edit/EditPageClient.tsx +156 -121
  120. package/templates/nextblock-template/app/cms/pages/[id]/edit/page.tsx +79 -26
  121. package/templates/nextblock-template/app/cms/pages/actions.ts +54 -38
  122. package/templates/nextblock-template/app/cms/pages/components/DeletePageButtonClient.tsx +1 -1
  123. package/templates/nextblock-template/app/cms/pages/components/PageForm.tsx +267 -116
  124. package/templates/nextblock-template/app/cms/pages/page.tsx +25 -18
  125. package/templates/nextblock-template/app/cms/payments/page.tsx +16 -0
  126. package/templates/nextblock-template/app/cms/posts/[id]/edit/page.tsx +132 -90
  127. package/templates/nextblock-template/app/cms/posts/actions.ts +71 -72
  128. package/templates/nextblock-template/app/cms/posts/components/DeletePostButtonClient.tsx +1 -1
  129. package/templates/nextblock-template/app/cms/posts/components/PostForm.tsx +256 -245
  130. package/templates/nextblock-template/app/cms/posts/new/page.tsx +1 -1
  131. package/templates/nextblock-template/app/cms/posts/page.tsx +20 -13
  132. package/templates/nextblock-template/app/cms/products/ClientNotionEditor.tsx +16 -0
  133. package/templates/nextblock-template/app/cms/products/ProductFormClientShell.tsx +56 -0
  134. package/templates/nextblock-template/app/cms/products/[id]/edit/page.tsx +292 -0
  135. package/templates/nextblock-template/app/cms/products/attributes/page.tsx +12 -0
  136. package/templates/nextblock-template/app/cms/products/categories/page.tsx +12 -0
  137. package/templates/nextblock-template/app/cms/products/inventory/page.tsx +13 -0
  138. package/templates/nextblock-template/app/cms/products/new/page.tsx +143 -0
  139. package/templates/nextblock-template/app/cms/products/page.tsx +42 -0
  140. package/templates/nextblock-template/app/cms/products/productFormData.ts +133 -0
  141. package/templates/nextblock-template/app/cms/products/settings/page.tsx +5 -0
  142. package/templates/nextblock-template/app/cms/promotions/PromotionsWorkspace.tsx +456 -0
  143. package/templates/nextblock-template/app/cms/promotions/actions.ts +115 -0
  144. package/templates/nextblock-template/app/cms/promotions/page.tsx +31 -0
  145. package/templates/nextblock-template/app/cms/revisions/RevisionHistoryButton.tsx +2 -2
  146. package/templates/nextblock-template/app/cms/revisions/actions.ts +285 -285
  147. package/templates/nextblock-template/app/cms/revisions/service.ts +19 -16
  148. package/templates/nextblock-template/app/cms/revisions/utils.ts +8 -3
  149. package/templates/nextblock-template/app/cms/settings/backup-restore/BackupRestoreWorkspace.tsx +1004 -0
  150. package/templates/nextblock-template/app/cms/settings/backup-restore/page.tsx +29 -0
  151. package/templates/nextblock-template/app/cms/settings/bot-protection/actions.ts +93 -0
  152. package/templates/nextblock-template/app/cms/settings/bot-protection/components/BotProtectionForm.tsx +129 -0
  153. package/templates/nextblock-template/app/cms/settings/bot-protection/page.tsx +24 -0
  154. package/templates/nextblock-template/app/cms/settings/copyright/actions.ts +1 -1
  155. package/templates/nextblock-template/app/cms/settings/copyright/components/CopyrightForm.tsx +2 -2
  156. package/templates/nextblock-template/app/cms/settings/copyright/page.tsx +1 -1
  157. package/templates/nextblock-template/app/cms/settings/cortex-ai/SandboxCortexAiSettingsClient.tsx +496 -0
  158. package/templates/nextblock-template/app/cms/settings/cortex-ai/StoredCortexAiSettingsClient.tsx +410 -0
  159. package/templates/nextblock-template/app/cms/settings/cortex-ai/actions.ts +248 -0
  160. package/templates/nextblock-template/app/cms/settings/cortex-ai/page.tsx +80 -0
  161. package/templates/nextblock-template/app/cms/settings/currencies/actions.ts +331 -0
  162. package/templates/nextblock-template/app/cms/settings/currencies/page.tsx +494 -0
  163. package/templates/nextblock-template/app/cms/settings/extra-translations/ExtraTranslationsWorkspace.tsx +767 -0
  164. package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +203 -44
  165. package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +93 -242
  166. package/templates/nextblock-template/app/cms/settings/global-css/actions.ts +65 -0
  167. package/templates/nextblock-template/app/cms/settings/global-css/components/GlobalCssForm.tsx +46 -0
  168. package/templates/nextblock-template/app/cms/settings/global-css/page.tsx +24 -0
  169. package/templates/nextblock-template/app/cms/settings/languages/components/DeleteLanguageButton.tsx +1 -1
  170. package/templates/nextblock-template/app/cms/settings/languages/components/LanguageForm.tsx +2 -2
  171. package/templates/nextblock-template/app/cms/settings/languages/page.tsx +1 -1
  172. package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +7 -7
  173. package/templates/nextblock-template/app/cms/settings/logos/actions.ts +82 -6
  174. package/templates/nextblock-template/app/cms/settings/logos/components/BrandingSettingsForm.tsx +339 -0
  175. package/templates/nextblock-template/app/cms/settings/logos/components/DeleteLogoButton.tsx +21 -18
  176. package/templates/nextblock-template/app/cms/settings/logos/components/LogoForm.tsx +20 -16
  177. package/templates/nextblock-template/app/cms/settings/logos/components/SiteSeoSettingsForm.tsx +133 -0
  178. package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +8 -8
  179. package/templates/nextblock-template/app/cms/settings/logos/page.tsx +120 -82
  180. package/templates/nextblock-template/app/cms/settings/logos/types.ts +8 -8
  181. package/templates/nextblock-template/app/cms/settings/packages/activation-form.tsx +84 -0
  182. package/templates/nextblock-template/app/cms/settings/packages/package-card.tsx +122 -0
  183. package/templates/nextblock-template/app/cms/settings/packages/page.tsx +49 -0
  184. package/templates/nextblock-template/app/cms/settings/privacy/actions.ts +53 -0
  185. package/templates/nextblock-template/app/cms/settings/privacy/components/PrivacyForm.tsx +196 -0
  186. package/templates/nextblock-template/app/cms/settings/privacy/page.tsx +26 -0
  187. package/templates/nextblock-template/app/cms/settings/security/actions.ts +251 -0
  188. package/templates/nextblock-template/app/cms/settings/security/components/SecurityPanel.tsx +453 -0
  189. package/templates/nextblock-template/app/cms/settings/security/page.tsx +13 -0
  190. package/templates/nextblock-template/app/cms/settings/taxes/page.tsx +21 -0
  191. package/templates/nextblock-template/app/cms/shipping/page.tsx +20 -0
  192. package/templates/nextblock-template/app/cms/users/[id]/edit/page.tsx +28 -23
  193. package/templates/nextblock-template/app/cms/users/actions.ts +105 -40
  194. package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +1 -1
  195. package/templates/nextblock-template/app/cms/users/components/UserForm.tsx +65 -152
  196. package/templates/nextblock-template/app/cms/users/page.tsx +15 -10
  197. package/templates/nextblock-template/app/globals.css +9 -0
  198. package/templates/nextblock-template/app/layout.tsx +372 -116
  199. package/templates/nextblock-template/app/lib/seo.test.ts +52 -0
  200. package/templates/nextblock-template/app/lib/seo.ts +279 -0
  201. package/templates/nextblock-template/app/lib/site-settings.ts +87 -0
  202. package/templates/nextblock-template/app/lib/sitemap-utils.ts +224 -39
  203. package/templates/nextblock-template/app/lib/ucp/protocol.ts +190 -0
  204. package/templates/nextblock-template/app/lib/ucp/server.test.ts +56 -0
  205. package/templates/nextblock-template/app/lib/ucp/server.ts +1914 -0
  206. package/templates/nextblock-template/app/page.tsx +165 -73
  207. package/templates/nextblock-template/app/product/[slug]/page.tsx +433 -0
  208. package/templates/nextblock-template/app/profile/ProfileAccountSidebar.tsx +73 -0
  209. package/templates/nextblock-template/app/profile/ProfilePageHeader.tsx +16 -0
  210. package/templates/nextblock-template/app/profile/ProfilePageMissingState.tsx +9 -0
  211. package/templates/nextblock-template/app/profile/account-data.ts +37 -0
  212. package/templates/nextblock-template/app/profile/account-links.ts +22 -0
  213. package/templates/nextblock-template/app/profile/account-types.ts +11 -0
  214. package/templates/nextblock-template/app/profile/orders/CustomerOrdersPageClient.tsx +124 -0
  215. package/templates/nextblock-template/app/profile/orders/[id]/CustomerOrderDetailPageClient.tsx +79 -0
  216. package/templates/nextblock-template/app/profile/orders/[id]/page.tsx +32 -0
  217. package/templates/nextblock-template/app/profile/orders/page.tsx +19 -0
  218. package/templates/nextblock-template/app/profile/page.tsx +51 -0
  219. package/templates/nextblock-template/app/profile/password/PasswordSettingsPageClient.tsx +128 -0
  220. package/templates/nextblock-template/app/profile/password/actions.ts +59 -0
  221. package/templates/nextblock-template/app/profile/password/page.tsx +27 -0
  222. package/templates/nextblock-template/app/providers.tsx +55 -17
  223. package/templates/nextblock-template/app/robots.txt/route.ts +11 -1
  224. package/templates/nextblock-template/app/sitemap.ts +128 -0
  225. package/templates/nextblock-template/app/ucp/v1/carts/[id]/cancel/route.ts +38 -0
  226. package/templates/nextblock-template/app/ucp/v1/carts/[id]/route.ts +68 -0
  227. package/templates/nextblock-template/app/ucp/v1/carts/route.ts +35 -0
  228. package/templates/nextblock-template/app/ucp/v1/catalog/lookup/route.ts +35 -0
  229. package/templates/nextblock-template/app/ucp/v1/catalog/product/route.ts +35 -0
  230. package/templates/nextblock-template/app/ucp/v1/catalog/search/route.ts +34 -0
  231. package/templates/nextblock-template/components/AppShell.tsx +154 -0
  232. package/templates/nextblock-template/components/BlockRenderer.tsx +210 -64
  233. package/templates/nextblock-template/components/CartDrawerLoader.tsx +7 -0
  234. package/templates/nextblock-template/components/CartTranslator.tsx +210 -0
  235. package/templates/nextblock-template/components/CurrentContentSetter.tsx +25 -0
  236. package/templates/nextblock-template/components/DeferredCartDrawer.tsx +23 -0
  237. package/templates/nextblock-template/components/DeferredCartTranslator.tsx +51 -0
  238. package/templates/nextblock-template/components/DeferredGlobalSearch.tsx +68 -0
  239. package/templates/nextblock-template/components/DeferredGoogleTagManager.tsx +70 -0
  240. package/templates/nextblock-template/components/DeferredSpeedInsights.tsx +69 -0
  241. package/templates/nextblock-template/components/FeatureImageHero.tsx +47 -0
  242. package/templates/nextblock-template/components/GitHubLoginButton.tsx +36 -0
  243. package/templates/nextblock-template/components/GlobalSearch.tsx +557 -0
  244. package/templates/nextblock-template/components/Header.tsx +49 -41
  245. package/templates/nextblock-template/components/LanguageSwitcher.tsx +55 -32
  246. package/templates/nextblock-template/components/ResponsiveNav.tsx +138 -43
  247. package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +12 -8
  248. package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -55
  249. package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +42 -37
  250. package/templates/nextblock-template/components/blocks/TestimonialBlock.tsx +6 -2
  251. package/templates/nextblock-template/components/blocks/ecommerceRendererLoaders.ts +23 -0
  252. package/templates/nextblock-template/components/blocks/publicRendererLoaders.ts +25 -0
  253. package/templates/nextblock-template/components/blocks/renderers/ButtonBlockRenderer.tsx +92 -84
  254. package/templates/nextblock-template/components/blocks/renderers/CartBlockRenderer.tsx +17 -0
  255. package/templates/nextblock-template/components/blocks/renderers/CheckoutBlockRenderer.tsx +19 -0
  256. package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +262 -8
  257. package/templates/nextblock-template/components/blocks/renderers/FeaturedProductBlockRenderer.tsx +22 -0
  258. package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +320 -37
  259. package/templates/nextblock-template/components/blocks/renderers/HeadingBlockRenderer.tsx +11 -8
  260. package/templates/nextblock-template/components/blocks/renderers/ImageBlockRenderer.tsx +12 -3
  261. package/templates/nextblock-template/components/blocks/renderers/PostsGridBlockRenderer.tsx +18 -13
  262. package/templates/nextblock-template/components/blocks/renderers/ProductDetailsBlockRenderer.tsx +90 -0
  263. package/templates/nextblock-template/components/blocks/renderers/ProductGridBlockRenderer.tsx +31 -0
  264. package/templates/nextblock-template/components/blocks/renderers/SectionBlockRenderer.tsx +424 -55
  265. package/templates/nextblock-template/components/blocks/renderers/SectionSlider.tsx +137 -0
  266. package/templates/nextblock-template/components/blocks/renderers/TestimonialBlockRenderer.tsx +57 -0
  267. package/templates/nextblock-template/components/blocks/renderers/TextBlockRenderer.tsx +37 -22
  268. package/templates/nextblock-template/components/blocks/renderers/VideoEmbedBlockRenderer.tsx +23 -15
  269. package/templates/nextblock-template/components/blocks/renderers/inline/AlertWidgetRenderer.tsx +1 -3
  270. package/templates/nextblock-template/components/blocks/renderers/inline/CtaWidgetRenderer.tsx +1 -3
  271. package/templates/nextblock-template/components/blocks/types.ts +7 -6
  272. package/templates/nextblock-template/components/env-var-warning.tsx +3 -3
  273. package/templates/nextblock-template/components/form-message.tsx +32 -26
  274. package/templates/nextblock-template/components/header-auth.tsx +69 -17
  275. package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +127 -0
  276. package/templates/nextblock-template/components/privacy/ConsentGatedAnalytics.tsx +59 -0
  277. package/templates/nextblock-template/components/renderers/CachedDynamicLayoutEngine.tsx +28 -0
  278. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.test.tsx +166 -0
  279. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.tsx +464 -0
  280. package/templates/nextblock-template/components/theme-switcher.tsx +8 -8
  281. package/templates/nextblock-template/components/visual-editing/DeferredVisualEditing.tsx +21 -0
  282. package/templates/nextblock-template/components/visual-editing/NextblockVisualEditing.tsx +1172 -0
  283. package/templates/nextblock-template/context/AuthContext.tsx +23 -90
  284. package/templates/nextblock-template/context/CurrentContentContext.tsx +10 -4
  285. package/templates/nextblock-template/context/LanguageContext.tsx +16 -16
  286. package/templates/nextblock-template/context/language-rest-client.ts +31 -0
  287. package/templates/nextblock-template/docs/01-PROJECT-OVERVIEW.md +94 -0
  288. package/templates/nextblock-template/docs/02-ECOMMERCE-CAPABILITIES.md +364 -0
  289. package/templates/nextblock-template/docs/03-CMS-AND-EDITOR.md +202 -0
  290. package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +252 -0
  291. package/templates/nextblock-template/docs/05-DEVELOPER-GUIDE.md +238 -0
  292. package/templates/nextblock-template/docs/06-CLI-AND-SCAFFOLDING.md +125 -0
  293. package/templates/nextblock-template/docs/07-BLOCK-SDK-AND-EXTENSIBILITY.md +146 -0
  294. package/templates/nextblock-template/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md +1319 -0
  295. package/templates/nextblock-template/docs/09-LIVE-DRAFT-MODE.md +104 -0
  296. package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +222 -0
  297. package/templates/nextblock-template/docs/README.md +34 -0
  298. package/templates/nextblock-template/docs/TECHNICAL_SPECIFICATION.md +12507 -0
  299. package/templates/nextblock-template/hooks/use-hotkeys.ts +21 -14
  300. package/templates/nextblock-template/hooks/useGlobalSearch.ts +101 -0
  301. package/templates/nextblock-template/index.d.ts +2 -0
  302. package/templates/nextblock-template/lib/ai-block-generation.ts +339 -0
  303. package/templates/nextblock-template/lib/ai-client.ts +247 -0
  304. package/templates/nextblock-template/lib/ai-config.ts +81 -0
  305. package/templates/nextblock-template/lib/ai-cortex-widget-builder.ts +125 -0
  306. package/templates/nextblock-template/lib/ai-global-agent-custom-block-tools.ts +363 -0
  307. package/templates/nextblock-template/lib/ai-global-agent-db-tools.test.ts +405 -0
  308. package/templates/nextblock-template/lib/ai-global-agent-db-tools.ts +1228 -0
  309. package/templates/nextblock-template/lib/ai-global-agent-ecommerce.ts +5 -0
  310. package/templates/nextblock-template/lib/ai-global-agent-tools-stats.test.ts +223 -0
  311. package/templates/nextblock-template/lib/ai-global-agent-tools.test.ts +2183 -0
  312. package/templates/nextblock-template/lib/ai-global-agent-tools.ts +4807 -0
  313. package/templates/nextblock-template/lib/ai-key-crypto.test.ts +70 -0
  314. package/templates/nextblock-template/lib/ai-key-crypto.ts +132 -0
  315. package/templates/nextblock-template/lib/ai-model-catalog.test.ts +49 -0
  316. package/templates/nextblock-template/lib/ai-model-catalog.ts +41 -0
  317. package/templates/nextblock-template/lib/ai-model-registry.test.ts +231 -0
  318. package/templates/nextblock-template/lib/ai-model-registry.ts +522 -0
  319. package/templates/nextblock-template/lib/auth/cookies.ts +47 -0
  320. package/templates/nextblock-template/lib/auth/crypto.ts +42 -0
  321. package/templates/nextblock-template/lib/auth/trustedDevices.ts +92 -0
  322. package/templates/nextblock-template/lib/auth/twoFactor.ts +167 -0
  323. package/templates/nextblock-template/lib/auth-redirects.ts +46 -0
  324. package/templates/nextblock-template/lib/blocks/FeaturedProductBlock.tsx +94 -0
  325. package/templates/nextblock-template/lib/blocks/ProductGridBlock.tsx +137 -0
  326. package/templates/nextblock-template/lib/blocks/README.md +13 -670
  327. package/templates/nextblock-template/lib/blocks/blockRegistry.ts +138 -56
  328. package/templates/nextblock-template/lib/blocks/blockTypes.ts +18 -0
  329. package/templates/nextblock-template/lib/blocks/ecommerce-block-schemas.ts +31 -0
  330. package/templates/nextblock-template/lib/cms-transfer/csv.test.ts +77 -0
  331. package/templates/nextblock-template/lib/cms-transfer/csv.ts +399 -0
  332. package/templates/nextblock-template/lib/cms-transfer/server.ts +2243 -0
  333. package/templates/nextblock-template/lib/cms-transfer/types.ts +145 -0
  334. package/templates/nextblock-template/lib/cortex-widget-registry.test.ts +199 -0
  335. package/templates/nextblock-template/lib/cortex-widget-registry.ts +88 -0
  336. package/templates/nextblock-template/lib/cortex-widget-schema.test.tsx +237 -0
  337. package/templates/nextblock-template/lib/cortex-widget-schema.ts +393 -0
  338. package/templates/nextblock-template/lib/custom-block-definitions.ts +87 -0
  339. package/templates/nextblock-template/lib/custom-block-r2-upload-shared.ts +178 -0
  340. package/templates/nextblock-template/lib/custom-block-r2-upload.test.ts +140 -0
  341. package/templates/nextblock-template/lib/custom-block-r2-upload.ts +68 -0
  342. package/templates/nextblock-template/lib/custom-block-relation-registry.ts +256 -0
  343. package/templates/nextblock-template/lib/custom-block-relations.test.ts +227 -0
  344. package/templates/nextblock-template/lib/custom-block-relations.ts +279 -0
  345. package/templates/nextblock-template/lib/custom-block-safelist.ts +14 -0
  346. package/templates/nextblock-template/lib/editor/dynamic-extension-core.test.ts +172 -0
  347. package/templates/nextblock-template/lib/editor/dynamic-extension-core.ts +213 -0
  348. package/templates/nextblock-template/lib/editor/dynamic-extension-loader.ts +22 -0
  349. package/templates/nextblock-template/lib/editor/dynamic-extensions.tsx +193 -0
  350. package/templates/nextblock-template/lib/full-backup/manifest.test.ts +121 -0
  351. package/templates/nextblock-template/lib/full-backup/manifest.ts +206 -0
  352. package/templates/nextblock-template/lib/full-backup/server.ts +743 -0
  353. package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +45 -0
  354. package/templates/nextblock-template/lib/posts/readTime.ts +60 -0
  355. package/templates/nextblock-template/lib/privacy/consent-client.ts +57 -0
  356. package/templates/nextblock-template/lib/privacy/settings.ts +103 -0
  357. package/templates/nextblock-template/lib/privacy/types.ts +67 -0
  358. package/templates/nextblock-template/lib/promotions/server.test.ts +74 -0
  359. package/templates/nextblock-template/lib/promotions/server.ts +741 -0
  360. package/templates/nextblock-template/lib/resolve-block-relations.test.ts +142 -0
  361. package/templates/nextblock-template/lib/resolve-block-relations.ts +255 -0
  362. package/templates/nextblock-template/lib/search/server.ts +585 -0
  363. package/templates/nextblock-template/lib/search/types.ts +27 -0
  364. package/templates/nextblock-template/lib/visual-editing/draft-content.test.ts +105 -0
  365. package/templates/nextblock-template/lib/visual-editing/draft-content.ts +380 -0
  366. package/templates/nextblock-template/lib/visual-editing/draft-route.test.ts +42 -0
  367. package/templates/nextblock-template/lib/visual-editing/draft-route.ts +82 -0
  368. package/templates/nextblock-template/lib/visual-editing/edit-info.test.ts +143 -0
  369. package/templates/nextblock-template/lib/visual-editing/edit-info.ts +94 -0
  370. package/templates/nextblock-template/lib/visual-editing/mutations.ts +190 -0
  371. package/templates/nextblock-template/lib/visual-editing/product-drafts.test.ts +81 -0
  372. package/templates/nextblock-template/lib/visual-editing/product-drafts.ts +511 -0
  373. package/templates/nextblock-template/lib/visual-editing/types.ts +122 -0
  374. package/templates/nextblock-template/lib/zod-config.ts +5 -0
  375. package/templates/nextblock-template/next.config.js +190 -66
  376. package/templates/nextblock-template/package.json +34 -30
  377. package/templates/nextblock-template/proxy.ts +435 -253
  378. package/templates/nextblock-template/public/images/NBcover.webp +0 -0
  379. package/templates/nextblock-template/public/images/cap.webp +0 -0
  380. package/templates/nextblock-template/public/images/commerce-plan.webp +0 -0
  381. package/templates/nextblock-template/public/images/commerce-square.webp +0 -0
  382. package/templates/nextblock-template/public/images/commerce-wide.webp +0 -0
  383. package/templates/nextblock-template/public/images/cortex-ai-square.webp +0 -0
  384. package/templates/nextblock-template/public/images/cortex-ai.webp +0 -0
  385. package/templates/nextblock-template/public/images/extensibility.webp +0 -0
  386. package/templates/nextblock-template/public/images/goals.webp +0 -0
  387. package/templates/nextblock-template/public/images/included.webp +0 -0
  388. package/templates/nextblock-template/public/images/nx-graph.webp +0 -0
  389. package/templates/nextblock-template/public/images/pants.webp +0 -0
  390. package/templates/nextblock-template/public/images/t-shirt.webp +0 -0
  391. package/templates/nextblock-template/scripts/validate-editor-block-schema.ts +112 -0
  392. package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +100 -0
  393. package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +62 -0
  394. package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +537 -0
  395. package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +58 -0
  396. package/templates/nextblock-template/scripts/verify-custom-block-definitions.ts +188 -0
  397. package/templates/nextblock-template/scripts/verify-dynamic-custom-block-extensions.ts +123 -0
  398. package/templates/nextblock-template/scripts/verify-dynamic-layout-engine.tsx +133 -0
  399. package/templates/nextblock-template/scripts/verify-milestone-2-custom-blocks.ts +65 -0
  400. package/templates/nextblock-template/tailwind.config.js +1 -0
  401. package/templates/nextblock-template/tools/configure-supabase-auth.js +282 -0
  402. package/templates/nextblock-template/tools/deploy-supabase.js +69 -71
  403. package/templates/nextblock-template/tsconfig.json +52 -66
  404. package/templates/nextblock-template/tsconfig.tsbuildinfo +1 -1
  405. package/templates/nextblock-template/types/jsdom.d.ts +6 -0
  406. package/templates/nextblock-template/app/force-styles.tsx +0 -31
  407. package/templates/nextblock-template/app/sitemap.xml/route.ts +0 -63
  408. package/templates/nextblock-template/components/blocks/renderers/HeroBlockRenderer.tsx +0 -273
  409. package/templates/nextblock-template/docs/How to Create a Custom Block.md +0 -149
  410. package/templates/nextblock-template/docs/cms-application-overview.md +0 -56
  411. package/templates/nextblock-template/docs/cms-architecture-overview.md +0 -73
  412. package/templates/nextblock-template/docs/files-structure.md +0 -426
  413. package/templates/nextblock-template/docs/tiptap-bundle-optimization-summary.md +0 -174
@@ -0,0 +1,1228 @@
1
+ import { tool } from 'ai';
2
+ import { z } from './zod-config';
3
+
4
+ type SupabaseLike = {
5
+ from: (table: string) => any;
6
+ };
7
+
8
+ type ToolExecutionContext = {
9
+ actorUserId?: string | null;
10
+ latestUserMessage?: string | null;
11
+ skipAudit?: boolean;
12
+ skipConfirmation?: boolean;
13
+ supabase?: SupabaseLike;
14
+ };
15
+
16
+ type TableConfig = {
17
+ columns: readonly string[];
18
+ description: string;
19
+ primaryKey: readonly string[];
20
+ readOnly?: boolean;
21
+ };
22
+
23
+ const MAX_READ_LIMIT = 100;
24
+ const DEFAULT_READ_LIMIT = 20;
25
+ const MAX_MUTATION_TARGETS = 100;
26
+ const MAX_MUTATION_ROWS = 20;
27
+ const REDACTED = '[REDACTED]';
28
+ const PROTECTED_CORTEX_KEY = 'cortex_ai_openrouter_api_key';
29
+
30
+ const tableConfigs = {
31
+ blocks: {
32
+ columns: ['id', 'page_id', 'post_id', 'language_id', 'block_type', 'content', 'order', 'created_at', 'updated_at'],
33
+ description: 'CMS page/post content blocks.',
34
+ primaryKey: ['id'],
35
+ },
36
+ coupon_freemius_mappings: {
37
+ columns: [
38
+ 'id',
39
+ 'coupon_id',
40
+ 'product_id',
41
+ 'freemius_product_id',
42
+ 'freemius_coupon_id',
43
+ 'freemius_coupon_code',
44
+ 'sync_status',
45
+ 'sync_error',
46
+ 'remote_payload',
47
+ 'last_synced_at',
48
+ 'created_at',
49
+ 'updated_at',
50
+ ],
51
+ description: 'Freemius coupon sync mappings.',
52
+ primaryKey: ['id'],
53
+ },
54
+ coupon_products: {
55
+ columns: ['coupon_id', 'product_id', 'created_at'],
56
+ description: 'Coupon product allow-list rows.',
57
+ primaryKey: ['coupon_id', 'product_id'],
58
+ },
59
+ coupon_redemptions: {
60
+ columns: [
61
+ 'id',
62
+ 'coupon_id',
63
+ 'order_id',
64
+ 'coupon_code',
65
+ 'provider',
66
+ 'discount_total',
67
+ 'user_id',
68
+ 'customer_email',
69
+ 'metadata',
70
+ 'redeemed_at',
71
+ ],
72
+ description: 'Coupon redemption records.',
73
+ primaryKey: ['id'],
74
+ },
75
+ coupons: {
76
+ columns: [
77
+ 'id',
78
+ 'code',
79
+ 'name',
80
+ 'internal_note',
81
+ 'provider_scope',
82
+ 'discount_type',
83
+ 'discount_amount',
84
+ 'is_active',
85
+ 'starts_at',
86
+ 'ends_at',
87
+ 'redemption_limit',
88
+ 'redemptions_count',
89
+ 'freemius_sync_status',
90
+ 'freemius_sync_error',
91
+ 'created_at',
92
+ 'updated_at',
93
+ ],
94
+ description: 'Commerce coupons.',
95
+ primaryKey: ['id'],
96
+ },
97
+ cortex_ai_db_mutation_audit: {
98
+ columns: [
99
+ 'id',
100
+ 'actor_user_id',
101
+ 'tool_name',
102
+ 'action_name',
103
+ 'target_tables',
104
+ 'operation_summary',
105
+ 'payload_hash',
106
+ 'payload',
107
+ 'preview',
108
+ 'status',
109
+ 'error_message',
110
+ 'created_at',
111
+ ],
112
+ description: 'Read-only Cortex AI database mutation audit trail.',
113
+ primaryKey: ['id'],
114
+ readOnly: true,
115
+ },
116
+ currencies: {
117
+ columns: [
118
+ 'id',
119
+ 'code',
120
+ 'symbol',
121
+ 'exchange_rate',
122
+ 'is_default',
123
+ 'is_active',
124
+ 'rounding_mode',
125
+ 'rounding_increment',
126
+ 'rounding_charm_amount',
127
+ 'auto_update_exchange_rate',
128
+ 'exchange_rate_updated_at',
129
+ 'exchange_rate_source',
130
+ 'auto_sync_product_prices',
131
+ 'created_at',
132
+ 'updated_at',
133
+ ],
134
+ description: 'Storefront currencies and FX settings.',
135
+ primaryKey: ['id'],
136
+ },
137
+ freemius_plans: {
138
+ columns: ['id', 'product_id', 'name', 'title', 'created_at', 'updated_at'],
139
+ description: 'Freemius plan metadata.',
140
+ primaryKey: ['id'],
141
+ },
142
+ freemius_pricing: {
143
+ columns: [
144
+ 'id',
145
+ 'plan_id',
146
+ 'api_monthly_price',
147
+ 'api_annual_price',
148
+ 'api_lifetime_price',
149
+ 'override_monthly_price',
150
+ 'override_annual_price',
151
+ 'override_lifetime_price',
152
+ 'license_quota',
153
+ 'is_active',
154
+ 'created_at',
155
+ 'updated_at',
156
+ ],
157
+ description: 'Freemius pricing metadata.',
158
+ primaryKey: ['id'],
159
+ },
160
+ inventory_items: {
161
+ columns: ['sku', 'quantity', 'created_at', 'updated_at'],
162
+ description: 'Source-of-truth inventory by SKU.',
163
+ primaryKey: ['sku'],
164
+ },
165
+ languages: {
166
+ columns: ['id', 'code', 'name', 'is_default', 'is_active', 'created_at', 'updated_at'],
167
+ description: 'CMS languages/locales.',
168
+ primaryKey: ['id'],
169
+ },
170
+ logos: {
171
+ columns: ['id', 'name', 'media_id', 'created_at'],
172
+ description: 'Brand logos.',
173
+ primaryKey: ['id'],
174
+ },
175
+ media: {
176
+ columns: [
177
+ 'id',
178
+ 'uploader_id',
179
+ 'file_name',
180
+ 'object_key',
181
+ 'file_type',
182
+ 'size_bytes',
183
+ 'description',
184
+ 'width',
185
+ 'height',
186
+ 'blur_data_url',
187
+ 'variants',
188
+ 'file_path',
189
+ 'folder',
190
+ 'created_at',
191
+ 'updated_at',
192
+ ],
193
+ description: 'Media metadata for uploaded assets.',
194
+ primaryKey: ['id'],
195
+ },
196
+ navigation_items: {
197
+ columns: [
198
+ 'id',
199
+ 'language_id',
200
+ 'menu_key',
201
+ 'label',
202
+ 'url',
203
+ 'parent_id',
204
+ 'order',
205
+ 'page_id',
206
+ 'translation_group_id',
207
+ 'created_at',
208
+ 'updated_at',
209
+ ],
210
+ description: 'CMS navigation tree items.',
211
+ primaryKey: ['id'],
212
+ },
213
+ order_items: {
214
+ columns: ['id', 'order_id', 'product_id', 'variant_id', 'quantity', 'price_at_purchase'],
215
+ description: 'Commerce order line items.',
216
+ primaryKey: ['id'],
217
+ },
218
+ orders: {
219
+ columns: [
220
+ 'id',
221
+ 'user_id',
222
+ 'status',
223
+ 'total',
224
+ 'stripe_session_id',
225
+ 'payment_intent_id',
226
+ 'customer_details',
227
+ 'provider',
228
+ 'freemius_product_id',
229
+ 'freemius_plan_id',
230
+ 'freemius_license_id',
231
+ 'freemius_subscription_id',
232
+ 'freemius_trial_id',
233
+ 'freemius_user_id',
234
+ 'freemius_trial_ends_at',
235
+ 'freemius_last_event_type',
236
+ 'freemius_last_synced_at',
237
+ 'currency',
238
+ 'subtotal',
239
+ 'shipping_total',
240
+ 'tax_total',
241
+ 'tax_details',
242
+ 'exchange_rate_at_purchase',
243
+ 'inventory_deducted_at',
244
+ 'invoice_number',
245
+ 'paid_at',
246
+ 'created_at',
247
+ 'coupon_id',
248
+ 'coupon_code',
249
+ 'discount_total',
250
+ 'discount_details',
251
+ ],
252
+ description: 'Commerce orders.',
253
+ primaryKey: ['id'],
254
+ },
255
+ package_activations: {
256
+ columns: ['id', 'license_key', 'instance_name', 'package_id', 'status', 'meta', 'last_validated_at', 'created_at'],
257
+ description: 'NextBlock package activations.',
258
+ primaryKey: ['id'],
259
+ },
260
+ page_revisions: {
261
+ columns: ['id', 'page_id', 'author_id', 'version', 'revision_type', 'content', 'created_at'],
262
+ description: 'Page revision history.',
263
+ primaryKey: ['id'],
264
+ },
265
+ pages: {
266
+ columns: [
267
+ 'id',
268
+ 'language_id',
269
+ 'author_id',
270
+ 'title',
271
+ 'slug',
272
+ 'status',
273
+ 'meta_title',
274
+ 'meta_description',
275
+ 'feature_image_id',
276
+ 'version',
277
+ 'translation_group_id',
278
+ 'created_at',
279
+ 'updated_at',
280
+ ],
281
+ description: 'CMS pages.',
282
+ primaryKey: ['id'],
283
+ },
284
+ post_revisions: {
285
+ columns: ['id', 'post_id', 'author_id', 'version', 'revision_type', 'content', 'created_at'],
286
+ description: 'Post revision history.',
287
+ primaryKey: ['id'],
288
+ },
289
+ posts: {
290
+ columns: [
291
+ 'id',
292
+ 'language_id',
293
+ 'author_id',
294
+ 'title',
295
+ 'slug',
296
+ 'label',
297
+ 'excerpt',
298
+ 'subtitle',
299
+ 'status',
300
+ 'published_at',
301
+ 'meta_title',
302
+ 'meta_description',
303
+ 'feature_image_id',
304
+ 'version',
305
+ 'translation_group_id',
306
+ 'created_at',
307
+ 'updated_at',
308
+ ],
309
+ description: 'CMS posts/articles.',
310
+ primaryKey: ['id'],
311
+ },
312
+ product_attribute_terms: {
313
+ columns: ['id', 'attribute_id', 'value', 'slug', 'sort_order', 'value_translations', 'created_at', 'updated_at'],
314
+ description: 'Product attribute terms.',
315
+ primaryKey: ['id'],
316
+ },
317
+ product_attributes: {
318
+ columns: ['id', 'name', 'slug', 'name_translations', 'created_at', 'updated_at'],
319
+ description: 'Product attribute definitions.',
320
+ primaryKey: ['id'],
321
+ },
322
+ product_media: {
323
+ columns: ['product_id', 'media_id', 'sort_order'],
324
+ description: 'Product media join rows.',
325
+ primaryKey: ['product_id', 'media_id'],
326
+ },
327
+ product_variants: {
328
+ columns: [
329
+ 'id',
330
+ 'product_id',
331
+ 'sku',
332
+ 'price_adjustment',
333
+ 'price',
334
+ 'prices',
335
+ 'sale_price',
336
+ 'sale_prices',
337
+ 'stock_quantity',
338
+ 'upc',
339
+ 'main_media_id',
340
+ 'created_at',
341
+ 'updated_at',
342
+ ],
343
+ description: 'Sellable product variants.',
344
+ primaryKey: ['id'],
345
+ },
346
+ products: {
347
+ columns: [
348
+ 'id',
349
+ 'language_id',
350
+ 'translation_group_id',
351
+ 'sku',
352
+ 'title',
353
+ 'slug',
354
+ 'product_type',
355
+ 'payment_provider',
356
+ 'price',
357
+ 'prices',
358
+ 'sale_price',
359
+ 'sale_prices',
360
+ 'stock',
361
+ 'status',
362
+ 'meta_title',
363
+ 'meta_description',
364
+ 'short_description',
365
+ 'description_json',
366
+ 'metadata',
367
+ 'freemius_plan_id',
368
+ 'freemius_product_id',
369
+ 'trial_period_days',
370
+ 'trial_requires_payment_method',
371
+ 'upc',
372
+ 'is_taxable',
373
+ 'created_at',
374
+ 'updated_at',
375
+ ],
376
+ description: 'Product catalog records.',
377
+ primaryKey: ['id'],
378
+ },
379
+ profiles: {
380
+ columns: ['id', 'updated_at', 'full_name', 'avatar_url', 'website', 'github_username', 'phone', 'role'],
381
+ description: 'Read-only user profiles. Never mutate through Cortex AI.',
382
+ primaryKey: ['id'],
383
+ readOnly: true,
384
+ },
385
+ shipping_zone_locations: {
386
+ columns: ['id', 'zone_id', 'country_code', 'state_code', 'postal_code', 'created_at'],
387
+ description: 'Shipping zone location match rules.',
388
+ primaryKey: ['id'],
389
+ },
390
+ shipping_zone_methods: {
391
+ columns: [
392
+ 'id',
393
+ 'zone_id',
394
+ 'method_type',
395
+ 'cost_amount',
396
+ 'cost_currency',
397
+ 'min_order_amount',
398
+ 'name',
399
+ 'name_translations',
400
+ 'currency_pricing_mode',
401
+ 'cost_amounts',
402
+ 'min_order_amounts',
403
+ 'created_at',
404
+ 'updated_at',
405
+ ],
406
+ description: 'Shipping methods/rates.',
407
+ primaryKey: ['id'],
408
+ },
409
+ shipping_zones: {
410
+ columns: ['id', 'name', 'priority_order', 'created_at', 'updated_at'],
411
+ description: 'Shipping zones.',
412
+ primaryKey: ['id'],
413
+ },
414
+ site_settings: {
415
+ columns: ['key', 'value'],
416
+ description: 'Global site settings. Cortex AI API-key setting row is protected.',
417
+ primaryKey: ['key'],
418
+ },
419
+ tax_rates: {
420
+ columns: ['id', 'country_code', 'state_code', 'tax_name', 'tax_rate', 'created_at', 'updated_at'],
421
+ description: 'Manual tax rates.',
422
+ primaryKey: ['id'],
423
+ },
424
+ translations: {
425
+ columns: ['key', 'translations', 'created_at', 'updated_at'],
426
+ description: 'Shared translation strings.',
427
+ primaryKey: ['key'],
428
+ },
429
+ user_addresses: {
430
+ columns: [
431
+ 'id',
432
+ 'user_id',
433
+ 'address_type',
434
+ 'is_default',
435
+ 'recipient_name',
436
+ 'company_name',
437
+ 'line1',
438
+ 'line2',
439
+ 'city',
440
+ 'state',
441
+ 'postal_code',
442
+ 'country_code',
443
+ 'created_at',
444
+ 'updated_at',
445
+ ],
446
+ description: 'Read-only user addresses. Never mutate through Cortex AI.',
447
+ primaryKey: ['id'],
448
+ readOnly: true,
449
+ },
450
+ variant_attribute_mapping: {
451
+ columns: ['variant_id', 'attribute_term_id'],
452
+ description: 'Variant to attribute-term join rows.',
453
+ primaryKey: ['variant_id', 'attribute_term_id'],
454
+ },
455
+ } as const satisfies Record<string, TableConfig>;
456
+
457
+ type TableName = keyof typeof tableConfigs;
458
+ type DatabaseMutationOperation = 'delete' | 'insert' | 'update' | 'upsert';
459
+ type AuditWriteResult = {
460
+ auditError?: string;
461
+ auditLogged: boolean;
462
+ };
463
+
464
+ const filterOperatorSchema = z.enum(['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'ilike', 'in', 'is']);
465
+
466
+ const databaseFilterSchema = z.strictObject({
467
+ column: z.string().trim().min(1).max(120),
468
+ operator: filterOperatorSchema.default('eq'),
469
+ value: z.unknown(),
470
+ });
471
+
472
+ const databaseOrderSchema = z.strictObject({
473
+ ascending: z.boolean().default(true),
474
+ column: z.string().trim().min(1).max(120),
475
+ });
476
+
477
+ export const describeDatabaseSchemaInputSchema = z.strictObject({
478
+ includeReadOnly: z.boolean().default(true),
479
+ table: z.string().trim().min(1).max(120).optional(),
480
+ });
481
+
482
+ export const readDatabaseRecordsInputSchema = z.strictObject({
483
+ columns: z.array(z.string().trim().min(1).max(120)).min(1).max(40).optional(),
484
+ filters: z.array(databaseFilterSchema).max(12).default([]),
485
+ limit: z.number().int().min(1).max(MAX_READ_LIMIT).default(DEFAULT_READ_LIMIT),
486
+ offset: z.number().int().min(0).max(10000).default(0),
487
+ orderBy: databaseOrderSchema.optional(),
488
+ table: z.string().trim().min(1).max(120),
489
+ });
490
+
491
+ export const executeDatabaseMutationInputSchema = z.strictObject({
492
+ filters: z.array(databaseFilterSchema).max(12).optional(),
493
+ operation: z.enum(['delete', 'insert', 'update', 'upsert']),
494
+ rows: z.array(z.record(z.string(), z.unknown())).min(1).max(MAX_MUTATION_ROWS).optional(),
495
+ summary: z.string().trim().min(1).max(500).optional(),
496
+ table: z.string().trim().min(1).max(120),
497
+ values: z.record(z.string(), z.unknown()).optional(),
498
+ });
499
+
500
+ export const executeDatabaseActionPlanInputSchema = z.strictObject({
501
+ actions: z.array(executeDatabaseMutationInputSchema).min(1).max(8),
502
+ summary: z.string().trim().min(1).max(500).optional(),
503
+ });
504
+
505
+ export type ExecuteDatabaseMutationInput = z.input<typeof executeDatabaseMutationInputSchema>;
506
+ export type ExecuteDatabaseActionPlanInput = z.input<typeof executeDatabaseActionPlanInputSchema>;
507
+
508
+ function getSupabase(context?: ToolExecutionContext) {
509
+ if (!context?.supabase) {
510
+ throw new Error('Cortex AI database tools require a Supabase client.');
511
+ }
512
+
513
+ return context.supabase;
514
+ }
515
+
516
+ function serializeError(error: unknown) {
517
+ if (!error) {
518
+ return 'Unknown database error.';
519
+ }
520
+
521
+ if (typeof error === 'object' && 'message' in error) {
522
+ return String((error as { message?: unknown }).message || 'Unknown database error.');
523
+ }
524
+
525
+ return String(error);
526
+ }
527
+
528
+ function stableStringify(value: unknown): string {
529
+ if (value === null || typeof value !== 'object') {
530
+ return JSON.stringify(value);
531
+ }
532
+
533
+ if (Array.isArray(value)) {
534
+ return `[${value.map((item) => stableStringify(item)).join(',')}]`;
535
+ }
536
+
537
+ return `{${Object.keys(value as Record<string, unknown>)
538
+ .sort()
539
+ .map((key) => `${JSON.stringify(key)}:${stableStringify((value as Record<string, unknown>)[key])}`)
540
+ .join(',')}}`;
541
+ }
542
+
543
+ function hashPayload(value: unknown) {
544
+ let hash = 0x811c9dc5;
545
+ const serialized = stableStringify(value);
546
+
547
+ for (let index = 0; index < serialized.length; index++) {
548
+ hash ^= serialized.charCodeAt(index);
549
+ hash = Math.imul(hash, 0x01000193);
550
+ }
551
+
552
+ return (hash >>> 0).toString(16).padStart(8, '0');
553
+ }
554
+
555
+ function normalizeConfirmationToken(value: string) {
556
+ return value.replace(/\s+/g, ' ').trim().toUpperCase();
557
+ }
558
+
559
+ function buildConfirmationPhrase(action: string, subject: string, payload: unknown) {
560
+ return `${normalizeConfirmationToken(`CONFIRM ${action} ${subject}`)} #${hashPayload(payload)}`;
561
+ }
562
+
563
+ function buildConfirmationPreview(params: {
564
+ action: string;
565
+ payload: unknown;
566
+ preview: Record<string, unknown>;
567
+ subject: string;
568
+ }) {
569
+ return {
570
+ confirmationPhrase: buildConfirmationPhrase(params.action, params.subject, params.payload),
571
+ mutationExecuted: false,
572
+ preview: params.preview,
573
+ requiresConfirmation: true,
574
+ success: true,
575
+ };
576
+ }
577
+
578
+ function getConfirmationPreview(params: {
579
+ action: string;
580
+ context?: ToolExecutionContext;
581
+ payload: unknown;
582
+ preview: Record<string, unknown>;
583
+ subject: string;
584
+ }) {
585
+ if (params.context?.skipConfirmation) {
586
+ return null;
587
+ }
588
+
589
+ const preview = buildConfirmationPreview(params);
590
+ const latestUserMessage = normalizeConfirmationToken(params.context?.latestUserMessage || '');
591
+ const expectedPhrase = normalizeConfirmationToken(preview.confirmationPhrase);
592
+
593
+ if (latestUserMessage && !latestUserMessage.includes(expectedPhrase)) {
594
+ return {
595
+ ...preview,
596
+ message: 'The database target changed or the confirmation did not match. Please review and confirm again.',
597
+ };
598
+ }
599
+
600
+ return latestUserMessage.includes(expectedPhrase) ? null : preview;
601
+ }
602
+
603
+ function isTableName(value: string): value is TableName {
604
+ return Object.prototype.hasOwnProperty.call(tableConfigs, value);
605
+ }
606
+
607
+ function normalizeTableName(value: string): TableName {
608
+ const table = value.trim();
609
+
610
+ if (table.includes('.') || table.toLowerCase().startsWith('auth')) {
611
+ throw new Error('Cortex AI cannot access auth schema tables.');
612
+ }
613
+
614
+ if (!isTableName(table)) {
615
+ throw new Error(`Table "${table}" is not available to Cortex AI database tools.`);
616
+ }
617
+
618
+ return table;
619
+ }
620
+
621
+ function getTableConfig(table: TableName): TableConfig {
622
+ return tableConfigs[table];
623
+ }
624
+
625
+ function isSensitiveKey(key: string) {
626
+ return /(?:password|token|secret|api[_-]?key|private[_-]?key|credential)/i.test(key);
627
+ }
628
+
629
+ function assertSafeColumn(table: TableName, column: string) {
630
+ const config = getTableConfig(table);
631
+
632
+ if (!(config.columns as readonly string[]).includes(column)) {
633
+ throw new Error(`Column "${column}" is not available on table "${table}".`);
634
+ }
635
+
636
+ if (isSensitiveKey(column)) {
637
+ throw new Error(`Column "${column}" is protected and cannot be used through Cortex AI.`);
638
+ }
639
+ }
640
+
641
+ function assertNoSensitivePayload(value: unknown, path = 'payload') {
642
+ if (Array.isArray(value)) {
643
+ value.forEach((item, index) => assertNoSensitivePayload(item, `${path}[${index}]`));
644
+ return;
645
+ }
646
+
647
+ if (!value || typeof value !== 'object') {
648
+ return;
649
+ }
650
+
651
+ for (const [key, nestedValue] of Object.entries(value as Record<string, unknown>)) {
652
+ if (isSensitiveKey(key)) {
653
+ throw new Error(`Protected field "${path}.${key}" cannot be written through Cortex AI.`);
654
+ }
655
+
656
+ assertNoSensitivePayload(nestedValue, `${path}.${key}`);
657
+ }
658
+ }
659
+
660
+ function redactValue(value: unknown): unknown {
661
+ if (Array.isArray(value)) {
662
+ return value.map((item) => redactValue(item));
663
+ }
664
+
665
+ if (!value || typeof value !== 'object') {
666
+ return value;
667
+ }
668
+
669
+ const output: Record<string, unknown> = {};
670
+
671
+ for (const [key, nestedValue] of Object.entries(value as Record<string, unknown>)) {
672
+ output[key] = isSensitiveKey(key) ? REDACTED : redactValue(nestedValue);
673
+ }
674
+
675
+ return output;
676
+ }
677
+
678
+ function redactRows(rows: unknown[]) {
679
+ return rows.map((row) => {
680
+ if (
681
+ row &&
682
+ typeof row === 'object' &&
683
+ !Array.isArray(row) &&
684
+ (row as Record<string, unknown>).key === PROTECTED_CORTEX_KEY
685
+ ) {
686
+ return { key: PROTECTED_CORTEX_KEY, value: REDACTED };
687
+ }
688
+
689
+ return redactValue(row);
690
+ });
691
+ }
692
+
693
+ function assertNoProtectedSiteSetting(table: TableName, input: unknown) {
694
+ if (table !== 'site_settings') {
695
+ return;
696
+ }
697
+
698
+ const serialized = JSON.stringify(input).toLowerCase();
699
+
700
+ if (serialized.includes(PROTECTED_CORTEX_KEY)) {
701
+ throw new Error('The Cortex AI OpenRouter API key site setting is protected.');
702
+ }
703
+ }
704
+
705
+ function applyFilter(query: any, filter: z.infer<typeof databaseFilterSchema>) {
706
+ switch (filter.operator) {
707
+ case 'eq':
708
+ return query.eq(filter.column, filter.value);
709
+ case 'neq':
710
+ return query.neq(filter.column, filter.value);
711
+ case 'gt':
712
+ return query.gt(filter.column, filter.value);
713
+ case 'gte':
714
+ return query.gte(filter.column, filter.value);
715
+ case 'lt':
716
+ return query.lt(filter.column, filter.value);
717
+ case 'lte':
718
+ return query.lte(filter.column, filter.value);
719
+ case 'ilike':
720
+ if (typeof filter.value !== 'string') {
721
+ throw new Error(`Filter "${filter.column}" with ilike requires a string value.`);
722
+ }
723
+ return query.ilike(filter.column, filter.value);
724
+ case 'in':
725
+ if (!Array.isArray(filter.value)) {
726
+ throw new Error(`Filter "${filter.column}" with in requires an array value.`);
727
+ }
728
+ return query.in(filter.column, filter.value);
729
+ case 'is':
730
+ return query.is(filter.column, filter.value);
731
+ }
732
+ }
733
+
734
+ function applyFilters(table: TableName, query: any, filters: Array<z.infer<typeof databaseFilterSchema>>) {
735
+ let nextQuery = query;
736
+
737
+ for (const filter of filters) {
738
+ assertSafeColumn(table, filter.column);
739
+ nextQuery = applyFilter(nextQuery, filter);
740
+ }
741
+
742
+ if (table === 'site_settings') {
743
+ nextQuery = nextQuery.neq('key', PROTECTED_CORTEX_KEY);
744
+ }
745
+
746
+ return nextQuery;
747
+ }
748
+
749
+ function selectColumns(table: TableName, columns?: string[]) {
750
+ const config = getTableConfig(table);
751
+ const selected = columns?.length ? columns : config.columns.filter((column) => !isSensitiveKey(column));
752
+
753
+ selected.forEach((column) => assertSafeColumn(table, column));
754
+
755
+ return selected.join(',');
756
+ }
757
+
758
+ function getTargetId(row: Record<string, unknown>, primaryKey: readonly string[]) {
759
+ return primaryKey.map((column) => `${column}=${String(row[column] ?? '')}`).join('|');
760
+ }
761
+
762
+ async function fetchMutationTargets(
763
+ supabase: SupabaseLike,
764
+ table: TableName,
765
+ filters: Array<z.infer<typeof databaseFilterSchema>>
766
+ ) {
767
+ const config = getTableConfig(table);
768
+ let query = supabase
769
+ .from(table)
770
+ .select(config.primaryKey.join(','))
771
+ .limit(MAX_MUTATION_TARGETS + 1);
772
+
773
+ query = applyFilters(table, query, filters);
774
+
775
+ const { data, error } = await query;
776
+
777
+ if (error) {
778
+ throw new Error(`Failed to preview database mutation: ${serializeError(error)}`);
779
+ }
780
+
781
+ const rows = Array.isArray(data) ? data : [];
782
+
783
+ if (rows.length > MAX_MUTATION_TARGETS) {
784
+ throw new Error(`Refusing to mutate more than ${MAX_MUTATION_TARGETS} rows.`);
785
+ }
786
+
787
+ return rows as Array<Record<string, unknown>>;
788
+ }
789
+
790
+ function validateMutationInput(input: z.infer<typeof executeDatabaseMutationInputSchema>) {
791
+ const table = normalizeTableName(input.table);
792
+ const config = getTableConfig(table);
793
+ const operation = input.operation as DatabaseMutationOperation;
794
+
795
+ if (config.readOnly) {
796
+ throw new Error(`Table "${table}" is read-only for Cortex AI.`);
797
+ }
798
+
799
+ assertNoProtectedSiteSetting(table, input);
800
+
801
+ if ((operation === 'update' || operation === 'delete') && (!input.filters || input.filters.length === 0)) {
802
+ throw new Error(`${operation} operations require at least one filter.`);
803
+ }
804
+
805
+ if ((operation === 'insert' || operation === 'upsert') && (!input.rows || input.rows.length === 0)) {
806
+ throw new Error(`${operation} operations require rows.`);
807
+ }
808
+
809
+ if (operation === 'update' && (!input.values || Object.keys(input.values).length === 0)) {
810
+ throw new Error('update operations require values.');
811
+ }
812
+
813
+ if (input.rows && input.rows.length > MAX_MUTATION_ROWS) {
814
+ throw new Error(`Refusing to mutate more than ${MAX_MUTATION_ROWS} input rows.`);
815
+ }
816
+
817
+ const payloads = input.rows ?? (input.values ? [input.values] : []);
818
+
819
+ for (const payload of payloads) {
820
+ assertNoSensitivePayload(payload);
821
+ for (const column of Object.keys(payload)) {
822
+ assertSafeColumn(table, column);
823
+ }
824
+ }
825
+
826
+ for (const filter of input.filters ?? []) {
827
+ assertSafeColumn(table, filter.column);
828
+ }
829
+
830
+ return table;
831
+ }
832
+
833
+ function buildMutationPreview(params: {
834
+ input: z.infer<typeof executeDatabaseMutationInputSchema>;
835
+ table: TableName;
836
+ targetRows: Array<Record<string, unknown>>;
837
+ }) {
838
+ const { input, table, targetRows } = params;
839
+ const config = getTableConfig(table);
840
+ const targetIds = targetRows.map((row) => getTargetId(row, config.primaryKey)).sort();
841
+ const inputRowCount = input.rows?.length ?? 0;
842
+ const affectedCount =
843
+ input.operation === 'insert' || input.operation === 'upsert' ? inputRowCount : targetRows.length;
844
+ const summary =
845
+ input.summary ||
846
+ `${input.operation} ${affectedCount} ${affectedCount === 1 ? 'row' : 'rows'} in ${table}.`;
847
+
848
+ return {
849
+ affectedCount,
850
+ operation: input.operation,
851
+ sampleTargetIds: targetIds.slice(0, 10),
852
+ summary,
853
+ table,
854
+ targetIds,
855
+ };
856
+ }
857
+
858
+ async function writeAudit(params: {
859
+ context?: ToolExecutionContext;
860
+ errorMessage?: string | null;
861
+ input: unknown;
862
+ preview: Record<string, unknown>;
863
+ status: 'failure' | 'success';
864
+ summary: string;
865
+ targetTables: string[];
866
+ toolName: string;
867
+ }): Promise<AuditWriteResult> {
868
+ const actorUserId = params.context?.actorUserId;
869
+
870
+ if (!actorUserId) {
871
+ return { auditError: 'Missing actor user id.', auditLogged: false };
872
+ }
873
+
874
+ try {
875
+ const { error } = await getSupabase(params.context)
876
+ .from('cortex_ai_db_mutation_audit')
877
+ .insert({
878
+ action_name: params.toolName,
879
+ actor_user_id: actorUserId,
880
+ error_message: params.errorMessage ?? null,
881
+ operation_summary: params.summary,
882
+ payload: redactValue(params.input),
883
+ payload_hash: hashPayload(params.input),
884
+ preview: redactValue(params.preview),
885
+ status: params.status,
886
+ target_tables: params.targetTables,
887
+ tool_name: params.toolName,
888
+ });
889
+
890
+ if (error) {
891
+ return { auditError: serializeError(error), auditLogged: false };
892
+ }
893
+
894
+ return { auditLogged: true };
895
+ } catch (error) {
896
+ return { auditError: serializeError(error), auditLogged: false };
897
+ }
898
+ }
899
+
900
+ async function executePreparedMutation(
901
+ input: z.infer<typeof executeDatabaseMutationInputSchema>,
902
+ table: TableName,
903
+ supabase: SupabaseLike
904
+ ) {
905
+ const config = getTableConfig(table);
906
+ const select = config.primaryKey.join(',');
907
+ let query: any;
908
+
909
+ if (input.operation === 'insert') {
910
+ query = supabase.from(table).insert(input.rows).select(select);
911
+ } else if (input.operation === 'upsert') {
912
+ query = supabase.from(table).upsert(input.rows).select(select);
913
+ } else if (input.operation === 'update') {
914
+ query = supabase.from(table).update(input.values);
915
+ query = applyFilters(table, query, input.filters ?? []);
916
+ query = query.select(select);
917
+ } else {
918
+ query = supabase.from(table).delete();
919
+ query = applyFilters(table, query, input.filters ?? []);
920
+ query = query.select(select);
921
+ }
922
+
923
+ const { data, error } = await query;
924
+
925
+ if (error) {
926
+ throw new Error(serializeError(error));
927
+ }
928
+
929
+ return Array.isArray(data) ? data : [];
930
+ }
931
+
932
+ export async function executeDescribeDatabaseSchema(
933
+ input: z.input<typeof describeDatabaseSchemaInputSchema>
934
+ ) {
935
+ const parsed = describeDatabaseSchemaInputSchema.parse(input);
936
+ const tableNames = parsed.table ? [normalizeTableName(parsed.table)] : (Object.keys(tableConfigs) as TableName[]);
937
+ const tables = tableNames
938
+ .filter((table) => parsed.includeReadOnly || !getTableConfig(table).readOnly)
939
+ .map((table) => {
940
+ const config = getTableConfig(table);
941
+ return {
942
+ columns: config.columns.filter((column) => !isSensitiveKey(column)),
943
+ description: config.description,
944
+ primaryKey: config.primaryKey,
945
+ readOnly: Boolean(config.readOnly),
946
+ table,
947
+ };
948
+ });
949
+
950
+ return {
951
+ safetyNotes: [
952
+ 'Use read_database_records for reads and execute_database_mutation/action_plan for writes.',
953
+ 'No auth schema access, no arbitrary SQL, no password/secret/API-key fields.',
954
+ 'profiles, user_addresses, and cortex_ai_db_mutation_audit are read-only.',
955
+ `site_settings.${PROTECTED_CORTEX_KEY} is protected.`,
956
+ ],
957
+ success: true,
958
+ tables,
959
+ };
960
+ }
961
+
962
+ export async function executeReadDatabaseRecords(
963
+ input: z.input<typeof readDatabaseRecordsInputSchema>,
964
+ context?: ToolExecutionContext
965
+ ) {
966
+ const parsed = readDatabaseRecordsInputSchema.parse(input);
967
+ const table = normalizeTableName(parsed.table);
968
+ const columns = selectColumns(table, parsed.columns);
969
+ let query = getSupabase(context)
970
+ .from(table)
971
+ .select(columns)
972
+ .range(parsed.offset, parsed.offset + parsed.limit - 1);
973
+
974
+ query = applyFilters(table, query, parsed.filters);
975
+
976
+ if (parsed.orderBy) {
977
+ assertSafeColumn(table, parsed.orderBy.column);
978
+ query = query.order(parsed.orderBy.column, { ascending: parsed.orderBy.ascending });
979
+ }
980
+
981
+ const { data, error } = await query;
982
+
983
+ if (error) {
984
+ throw new Error(`Failed to read database records: ${serializeError(error)}`);
985
+ }
986
+
987
+ const rows = Array.isArray(data) ? data : [];
988
+
989
+ return {
990
+ columns: columns.split(','),
991
+ limit: parsed.limit,
992
+ offset: parsed.offset,
993
+ rows: redactRows(rows),
994
+ success: true,
995
+ table,
996
+ };
997
+ }
998
+
999
+ export async function executeDatabaseMutation(
1000
+ input: ExecuteDatabaseMutationInput,
1001
+ context?: ToolExecutionContext
1002
+ ) {
1003
+ const parsed = executeDatabaseMutationInputSchema.parse(input);
1004
+ const table = validateMutationInput(parsed);
1005
+ const supabase = getSupabase(context);
1006
+ const targetRows =
1007
+ parsed.operation === 'update' || parsed.operation === 'delete'
1008
+ ? await fetchMutationTargets(supabase, table, parsed.filters ?? [])
1009
+ : [];
1010
+ const preview = buildMutationPreview({ input: parsed, table, targetRows });
1011
+ const confirmationPayload = {
1012
+ input: parsed,
1013
+ targetIds: preview.targetIds,
1014
+ };
1015
+ const confirmation = getConfirmationPreview({
1016
+ action: `DATABASE ${parsed.operation}`,
1017
+ context,
1018
+ payload: confirmationPayload,
1019
+ preview,
1020
+ subject: table,
1021
+ });
1022
+
1023
+ if (confirmation) {
1024
+ return confirmation;
1025
+ }
1026
+
1027
+ try {
1028
+ const rows = await executePreparedMutation(parsed, table, supabase);
1029
+ const audit: AuditWriteResult = context?.skipAudit
1030
+ ? { auditLogged: false }
1031
+ : await writeAudit({
1032
+ context,
1033
+ input: confirmationPayload,
1034
+ preview,
1035
+ status: 'success',
1036
+ summary: String(preview.summary),
1037
+ targetTables: [table],
1038
+ toolName: 'execute_database_mutation',
1039
+ });
1040
+
1041
+ return {
1042
+ affectedCount: rows.length || preview.affectedCount,
1043
+ auditLogged: audit.auditLogged,
1044
+ ...(audit.auditError ? { auditError: audit.auditError } : {}),
1045
+ mutationExecuted: true,
1046
+ operation: parsed.operation,
1047
+ sampleTargetIds: rows
1048
+ .map((row: Record<string, unknown>) => getTargetId(row, getTableConfig(table).primaryKey))
1049
+ .slice(0, 10),
1050
+ success: true,
1051
+ summary: preview.summary,
1052
+ table,
1053
+ };
1054
+ } catch (error) {
1055
+ const message = serializeError(error);
1056
+ const audit: AuditWriteResult = context?.skipAudit
1057
+ ? { auditLogged: false }
1058
+ : await writeAudit({
1059
+ context,
1060
+ errorMessage: message,
1061
+ input: confirmationPayload,
1062
+ preview,
1063
+ status: 'failure',
1064
+ summary: String(preview.summary),
1065
+ targetTables: [table],
1066
+ toolName: 'execute_database_mutation',
1067
+ });
1068
+
1069
+ return {
1070
+ auditLogged: audit.auditLogged,
1071
+ ...(audit.auditError ? { auditError: audit.auditError } : {}),
1072
+ message: `Database mutation failed: ${message}`,
1073
+ mutationExecuted: false,
1074
+ operation: parsed.operation,
1075
+ success: false,
1076
+ table,
1077
+ };
1078
+ }
1079
+ }
1080
+
1081
+ export async function executeDatabaseActionPlan(
1082
+ input: ExecuteDatabaseActionPlanInput,
1083
+ context?: ToolExecutionContext
1084
+ ) {
1085
+ const parsed = executeDatabaseActionPlanInputSchema.parse(input);
1086
+ const prepared: Array<{
1087
+ input: z.infer<typeof executeDatabaseMutationInputSchema>;
1088
+ preview: ReturnType<typeof buildMutationPreview>;
1089
+ table: TableName;
1090
+ }> = [];
1091
+
1092
+ for (const action of parsed.actions) {
1093
+ const table = validateMutationInput(action);
1094
+ const targetRows =
1095
+ action.operation === 'update' || action.operation === 'delete'
1096
+ ? await fetchMutationTargets(getSupabase(context), table, action.filters ?? [])
1097
+ : [];
1098
+ prepared.push({
1099
+ input: action,
1100
+ preview: buildMutationPreview({ input: action, table, targetRows }),
1101
+ table,
1102
+ });
1103
+ }
1104
+
1105
+ const actionSummaries = prepared.map(({ preview }) => String(preview.summary));
1106
+ const preview = {
1107
+ actionCount: prepared.length,
1108
+ actionSummaries,
1109
+ summary: parsed.summary || `Run ${prepared.length} confirmed database actions.`,
1110
+ tables: Array.from(new Set(prepared.map((action) => action.table))),
1111
+ };
1112
+ const confirmationPayload = prepared.map(({ input, preview: actionPreview, table }) => ({
1113
+ input,
1114
+ table,
1115
+ targetIds: actionPreview.targetIds,
1116
+ }));
1117
+ const confirmation = getConfirmationPreview({
1118
+ action: 'DATABASE ACTION PLAN',
1119
+ context,
1120
+ payload: confirmationPayload,
1121
+ preview,
1122
+ subject: `${prepared.length} actions`,
1123
+ });
1124
+
1125
+ if (confirmation) {
1126
+ return confirmation;
1127
+ }
1128
+
1129
+ const results: unknown[] = [];
1130
+ let mutationExecuted = false;
1131
+
1132
+ for (const [index, action] of prepared.entries()) {
1133
+ const result = await executeDatabaseMutation(action.input, {
1134
+ ...context,
1135
+ skipAudit: true,
1136
+ skipConfirmation: true,
1137
+ });
1138
+
1139
+ results.push(result);
1140
+
1141
+ if (result && typeof result === 'object' && (result as any).mutationExecuted === true) {
1142
+ mutationExecuted = true;
1143
+ }
1144
+
1145
+ if (!result || typeof result !== 'object' || (result as any).success === false) {
1146
+ const audit = await writeAudit({
1147
+ context,
1148
+ errorMessage:
1149
+ result && typeof result === 'object' && typeof (result as any).message === 'string'
1150
+ ? (result as any).message
1151
+ : `Database action ${index + 1} failed.`,
1152
+ input: confirmationPayload,
1153
+ preview,
1154
+ status: 'failure',
1155
+ summary: String(preview.summary),
1156
+ targetTables: preview.tables,
1157
+ toolName: 'execute_database_action_plan',
1158
+ });
1159
+
1160
+ return {
1161
+ actionCount: prepared.length,
1162
+ auditLogged: audit.auditLogged,
1163
+ ...(audit.auditError ? { auditError: audit.auditError } : {}),
1164
+ failedActionIndex: index,
1165
+ message:
1166
+ result && typeof result === 'object' && typeof (result as any).message === 'string'
1167
+ ? (result as any).message
1168
+ : `Database action ${index + 1} failed.`,
1169
+ mutationExecuted,
1170
+ results,
1171
+ success: false,
1172
+ };
1173
+ }
1174
+ }
1175
+
1176
+ const audit = await writeAudit({
1177
+ context,
1178
+ input: confirmationPayload,
1179
+ preview,
1180
+ status: 'success',
1181
+ summary: String(preview.summary),
1182
+ targetTables: preview.tables,
1183
+ toolName: 'execute_database_action_plan',
1184
+ });
1185
+
1186
+ return {
1187
+ actionCount: prepared.length,
1188
+ auditLogged: audit.auditLogged,
1189
+ ...(audit.auditError ? { auditError: audit.auditError } : {}),
1190
+ mutationExecuted,
1191
+ results,
1192
+ success: true,
1193
+ summary: parsed.summary ?? null,
1194
+ };
1195
+ }
1196
+
1197
+ export function createCortexDatabaseAgentTools(context?: ToolExecutionContext) {
1198
+ return {
1199
+ describe_database_schema: tool({
1200
+ description:
1201
+ 'Describe the public database tables Cortex AI can read or mutate, including read-only tables, primary keys, and writable columns. Use before broad database tasks when unsure about schema.',
1202
+ execute: (input) => executeDescribeDatabaseSchema(input),
1203
+ inputSchema: describeDatabaseSchemaInputSchema,
1204
+ strict: true,
1205
+ }),
1206
+ execute_database_action_plan: tool({
1207
+ description:
1208
+ 'Execute up to 8 typed database mutations as one confirmed plan. Mutating: first returns one combined confirmation preview and Confirm button; after confirmation, runs each action in order and stops on the first failure.',
1209
+ execute: (input) => executeDatabaseActionPlan(input, context),
1210
+ inputSchema: executeDatabaseActionPlanInputSchema,
1211
+ strict: true,
1212
+ }),
1213
+ execute_database_mutation: tool({
1214
+ description:
1215
+ 'Run one typed insert, update, upsert, or delete against an allowed public database table. Mutating: always previews affected rows and requires exact user confirmation before execution. Does not support arbitrary SQL.',
1216
+ execute: (input) => executeDatabaseMutation(input, context),
1217
+ inputSchema: executeDatabaseMutationInputSchema,
1218
+ strict: true,
1219
+ }),
1220
+ read_database_records: tool({
1221
+ description:
1222
+ 'Read records from an allowed public database table with validated columns, filters, ordering, limit, and offset. Read-only and redacts protected secret-like values.',
1223
+ execute: (input) => executeReadDatabaseRecords(input, context),
1224
+ inputSchema: readDatabaseRecordsInputSchema,
1225
+ strict: true,
1226
+ }),
1227
+ };
1228
+ }