kasy-cli 1.21.9 → 1.22.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 (267) hide show
  1. package/lib/commands/add.js +93 -80
  2. package/lib/commands/configure.js +100 -32
  3. package/lib/commands/doctor.js +28 -2
  4. package/lib/commands/new.js +80 -37
  5. package/lib/commands/notifications.js +1 -1
  6. package/lib/commands/remove.js +43 -15
  7. package/lib/commands/run.js +2 -2
  8. package/lib/commands/update.js +2 -2
  9. package/lib/scaffold/CHANGELOG.json +14 -0
  10. package/lib/scaffold/backends/api/generator.js +14 -14
  11. package/lib/scaffold/backends/api/patch/README.md +83 -0
  12. package/lib/scaffold/backends/api/patch/lib/core/data/api/storage_api.dart +1 -1
  13. package/lib/scaffold/backends/api/patch/lib/core/data/entities/user_entity.dart +5 -0
  14. package/lib/scaffold/backends/api/patch/lib/{environnements.dart → environments.dart} +3 -11
  15. package/lib/scaffold/backends/api/patch/lib/features/ai_chat/api/ai_chat_api.dart +108 -0
  16. package/lib/scaffold/backends/api/patch/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +51 -0
  17. package/lib/scaffold/backends/api/patch/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +5 -5
  18. package/lib/scaffold/backends/api/patch/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +71 -38
  19. package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +2 -2
  20. package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_request_api.dart +54 -0
  21. package/lib/scaffold/backends/api/patch/lib/features/onboarding/models/user_info.dart +16 -16
  22. package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
  23. package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
  24. package/lib/scaffold/backends/api/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +100 -0
  25. package/lib/scaffold/backends/api/patch/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +13 -0
  26. package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/stripe_backend_api.dart +60 -0
  27. package/lib/scaffold/backends/api/patch/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
  28. package/lib/scaffold/backends/api/pubspec.yaml.tpl +4 -5
  29. package/lib/scaffold/backends/firebase/deploy.js +87 -13
  30. package/lib/scaffold/backends/firebase/generator.js +5 -5
  31. package/lib/scaffold/backends/firebase/tokens.js +4 -4
  32. package/lib/scaffold/backends/supabase/deploy.js +63 -11
  33. package/lib/scaffold/backends/supabase/edge-functions/admin-list-users/index.ts +149 -0
  34. package/lib/scaffold/backends/supabase/edge-functions/{llm-chat → ai-chat}/index.ts +19 -19
  35. package/lib/scaffold/backends/supabase/edge-functions/revenuecat-webhook/index.ts +2 -0
  36. package/lib/scaffold/backends/supabase/edge-functions/stripe-create-checkout-session/index.ts +123 -0
  37. package/lib/scaffold/backends/supabase/edge-functions/stripe-create-portal-session/index.ts +97 -0
  38. package/lib/scaffold/backends/supabase/edge-functions/stripe-list-prices/index.ts +83 -0
  39. package/lib/scaffold/backends/supabase/edge-functions/stripe-webhook/index.ts +138 -0
  40. package/lib/scaffold/backends/supabase/generator.js +17 -17
  41. package/lib/scaffold/backends/supabase/migrations/20240101000009_ai_messages.sql +50 -0
  42. package/lib/scaffold/backends/supabase/migrations/20240101000012_stripe_customers.sql +36 -0
  43. package/lib/scaffold/backends/supabase/migrations/20240101000013_admin_role.sql +62 -0
  44. package/lib/scaffold/backends/supabase/patch/lib/core/data/entities/user_entity.dart +4 -0
  45. package/lib/scaffold/backends/supabase/patch/lib/{environnements.dart → environments.dart} +3 -13
  46. package/lib/scaffold/backends/supabase/patch/lib/features/ai_chat/api/ai_chat_api.dart +95 -0
  47. package/lib/scaffold/backends/supabase/patch/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +52 -0
  48. package/lib/scaffold/backends/supabase/patch/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +6 -6
  49. package/lib/scaffold/backends/supabase/patch/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +63 -35
  50. package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +1 -1
  51. package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/api/feature_request_api.dart +46 -0
  52. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/models/user_info.dart +16 -16
  53. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
  54. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
  55. package/lib/scaffold/backends/supabase/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +93 -0
  56. package/lib/scaffold/backends/supabase/patch/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +13 -0
  57. package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/stripe_backend_api.dart +52 -0
  58. package/lib/scaffold/backends/supabase/patch/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
  59. package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +4 -5
  60. package/lib/scaffold/backends/supabase/tokens.js +3 -3
  61. package/lib/scaffold/catalog.js +9 -11
  62. package/lib/scaffold/generate.js +45 -31
  63. package/lib/scaffold/shared/generator-utils.js +188 -81
  64. package/lib/scaffold/shared/sort-imports.js +191 -0
  65. package/lib/scaffold/shared/template-strings.js +3 -3
  66. package/lib/utils/checks.js +2 -2
  67. package/lib/utils/i18n/messages-en.js +50 -35
  68. package/lib/utils/i18n/messages-es.js +50 -35
  69. package/lib/utils/i18n/messages-pt.js +52 -37
  70. package/lib/utils/updates.js +15 -15
  71. package/package.json +1 -1
  72. package/templates/firebase/.env.example +2 -2
  73. package/templates/firebase/android/app/src/main/res/drawable/background.png +0 -0
  74. package/templates/firebase/android/app/src/main/res/drawable-night/background.png +0 -0
  75. package/templates/firebase/android/app/src/main/res/drawable-night-v21/background.png +0 -0
  76. package/templates/firebase/android/app/src/main/res/drawable-v21/background.png +0 -0
  77. package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +1 -1
  78. package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +1 -1
  79. package/templates/firebase/assets/images/logo_wordmark_dark.png +0 -0
  80. package/templates/firebase/assets/images/logo_wordmark_light.png +0 -0
  81. package/templates/firebase/docs/revenuecat-setup.es.md +1 -1
  82. package/templates/firebase/docs/revenuecat-setup.pt.md +1 -1
  83. package/templates/firebase/firestore.rules +24 -5
  84. package/templates/firebase/functions/package-lock.json +22 -1
  85. package/templates/firebase/functions/package.json +2 -1
  86. package/templates/firebase/functions/src/admin/functions.ts +113 -0
  87. package/templates/firebase/functions/src/{llm_chat → ai_chat}/index.ts +16 -16
  88. package/templates/firebase/functions/src/index.ts +8 -2
  89. package/templates/firebase/functions/src/notifications/device_triggers.ts +2 -2
  90. package/templates/firebase/functions/src/notifications/triggers.ts +3 -3
  91. package/templates/firebase/functions/src/subscriptions/models/subscription_status.ts +2 -0
  92. package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +222 -0
  93. package/templates/firebase/functions/src/subscriptions/subscriptions_functions.ts +2 -2
  94. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png +0 -0
  95. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png +0 -0
  96. package/templates/firebase/lib/components/components.dart +4 -1
  97. package/templates/firebase/lib/components/kasy_app_bar.dart +22 -7
  98. package/templates/firebase/lib/components/kasy_avatar.dart +7 -6
  99. package/templates/firebase/lib/components/kasy_button.dart +23 -99
  100. package/templates/firebase/lib/components/kasy_dialog.dart +11 -11
  101. package/templates/firebase/lib/components/kasy_image_viewer.dart +84 -0
  102. package/templates/firebase/lib/components/{kasy_sidebar_pro.dart → kasy_sidebar.dart} +692 -425
  103. package/templates/firebase/lib/components/kasy_status_tag.dart +91 -0
  104. package/templates/firebase/lib/components/kasy_text_area.dart +1 -3
  105. package/templates/firebase/lib/components/kasy_text_field.dart +5 -6
  106. package/templates/firebase/lib/components/kasy_text_field_otp.dart +1 -3
  107. package/templates/firebase/lib/components/kasy_toast.dart +2 -2
  108. package/templates/firebase/lib/components/kasy_web_header.dart +209 -0
  109. package/templates/firebase/lib/core/ads/ads_provider.dart +1 -1
  110. package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +57 -4
  111. package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +19 -91
  112. package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +196 -96
  113. package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +41 -0
  114. package/templates/firebase/lib/core/config/app_env.dart +5 -11
  115. package/templates/firebase/lib/core/config/features.dart +5 -4
  116. package/templates/firebase/lib/core/data/api/analytics_api.dart +1 -1
  117. package/templates/firebase/lib/core/data/api/http_client.dart +1 -1
  118. package/templates/firebase/lib/core/data/entities/user_entity.dart +3 -0
  119. package/templates/firebase/lib/core/data/models/entitlement.dart +35 -0
  120. package/templates/firebase/lib/core/data/models/subscription.dart +13 -186
  121. package/templates/firebase/lib/core/data/models/user.dart +11 -0
  122. package/templates/firebase/lib/core/data/repositories/user_repository.dart +1 -1
  123. package/templates/firebase/lib/core/home_widgets/home_widget_background_task.dart +1 -1
  124. package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +1 -1
  125. package/templates/firebase/lib/core/icons/kasy_icons.dart +31 -1
  126. package/templates/firebase/lib/core/rating/api/rating_api.dart +2 -2
  127. package/templates/firebase/lib/core/states/logout_action.dart +25 -0
  128. package/templates/firebase/lib/core/states/user_state_notifier.dart +3 -3
  129. package/templates/firebase/lib/core/theme/colors.dart +488 -188
  130. package/templates/firebase/lib/core/theme/radius.dart +22 -11
  131. package/templates/firebase/lib/core/theme/shadows.dart +66 -0
  132. package/templates/firebase/lib/core/theme/texts.dart +75 -41
  133. package/templates/firebase/lib/core/theme/universal_theme.dart +9 -4
  134. package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +5 -4
  135. package/templates/firebase/lib/core/web_viewport_scale.dart +52 -0
  136. package/templates/firebase/lib/core/widgets/kasy_brand_badge.dart +118 -0
  137. package/templates/firebase/lib/core/widgets/kasy_brand_logo.dart +40 -0
  138. package/templates/firebase/lib/core/widgets/kasy_focus_ring.dart +125 -0
  139. package/templates/firebase/lib/core/widgets/kasy_hover.dart +33 -13
  140. package/templates/firebase/lib/core/widgets/kasy_pressable_depth.dart +18 -13
  141. package/templates/firebase/lib/{environnements.dart → environments.dart} +3 -14
  142. package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +159 -0
  143. package/templates/firebase/lib/features/ai_chat/api/ai_chat_api.dart +107 -0
  144. package/templates/firebase/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +64 -0
  145. package/templates/firebase/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +6 -6
  146. package/templates/firebase/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +73 -38
  147. package/templates/firebase/lib/features/ai_chat/providers/ai_conversations_notifier.dart +103 -0
  148. package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_avatars.dart → ai_chat/ui/widgets/ai_chat_avatars.dart} +13 -13
  149. package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_composer.dart → ai_chat/ui/widgets/ai_chat_composer.dart} +10 -10
  150. package/templates/firebase/lib/features/{llm_chat/llm_chat_page.dart → ai_chat/ui/widgets/ai_chat_conversation_view.dart} +80 -67
  151. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +285 -0
  152. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +163 -0
  153. package/templates/firebase/lib/features/authentication/api/authentication_api.dart +52 -13
  154. package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher.dart +12 -0
  155. package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher_web.dart +35 -0
  156. package/templates/firebase/lib/features/authentication/ui/recover_password_page.dart +108 -68
  157. package/templates/firebase/lib/features/authentication/ui/signin_page.dart +38 -51
  158. package/templates/firebase/lib/features/authentication/ui/signup_page.dart +38 -51
  159. package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +118 -0
  160. package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +61 -44
  161. package/templates/firebase/lib/features/feedbacks/api/feature_request_api.dart +32 -0
  162. package/templates/firebase/lib/features/feedbacks/models/feedback_state.dart +5 -5
  163. package/templates/firebase/lib/features/feedbacks/providers/feedback_page_notifier.dart +2 -2
  164. package/templates/firebase/lib/features/home/design_system_page.dart +808 -170
  165. package/templates/firebase/lib/features/home/home_components_page.dart +6 -3
  166. package/templates/firebase/lib/features/home/home_components_preview_page.dart +6 -6
  167. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +325 -186
  168. package/templates/firebase/lib/features/home/home_feed.dart +289 -0
  169. package/templates/firebase/lib/features/home/home_image_grid.dart +355 -0
  170. package/templates/firebase/lib/features/home/home_page.dart +11 -250
  171. package/templates/firebase/lib/features/{local_reminder → local_reminders}/providers/reminder_notifier.dart +1 -1
  172. package/templates/firebase/lib/features/{local_reminder → local_reminders}/ui/reminder_page.dart +2 -2
  173. package/templates/firebase/lib/features/notifications/shared/att_permission.dart +1 -1
  174. package/templates/firebase/lib/features/notifications/ui/request_notification_permission.dart +25 -61
  175. package/templates/firebase/lib/features/notifications/ui/widgets/permission_request_view.dart +117 -0
  176. package/templates/firebase/lib/features/onboarding/models/user_info.dart +16 -16
  177. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
  178. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_features.dart +7 -9
  179. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +71 -48
  180. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +3 -2
  181. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_questions.dart +5 -5
  182. package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +4 -4
  183. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_background.dart +4 -2
  184. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_feature.dart +39 -121
  185. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +105 -70
  186. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_module_mockups.dart +639 -0
  187. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_progress.dart +62 -50
  188. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
  189. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_step_header.dart +75 -0
  190. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_sticky_footer.dart +16 -5
  191. package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +26 -22
  192. package/templates/firebase/lib/features/settings/settings_page.dart +601 -90
  193. package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +1193 -0
  194. package/templates/firebase/lib/features/settings/ui/components/admin/admin_paywalls.dart +1 -1
  195. package/templates/firebase/lib/features/settings/ui/components/admin/admin_routes.dart +2 -3
  196. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_api.dart +86 -0
  197. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +1215 -0
  198. package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +236 -0
  199. package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +3 -3
  200. package/templates/firebase/lib/features/settings/ui/widgets/kasy_user_avatar.dart +77 -0
  201. package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +1 -0
  202. package/templates/firebase/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +17 -0
  203. package/templates/firebase/lib/features/{subscription → subscriptions}/api/inapp_subscription_api.dart +67 -46
  204. package/templates/firebase/lib/features/subscriptions/api/revenuecat_product.dart +189 -0
  205. package/templates/firebase/lib/features/subscriptions/api/stripe_backend_api.dart +55 -0
  206. package/templates/firebase/lib/features/subscriptions/api/stripe_payment_api.dart +82 -0
  207. package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +178 -0
  208. package/templates/firebase/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
  209. package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api.dart +53 -0
  210. package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api_provider.dart +21 -0
  211. package/templates/firebase/lib/features/{subscription → subscriptions}/providers/premium_page_provider.dart +9 -6
  212. package/templates/firebase/lib/features/{subscription → subscriptions}/repositories/subscription_repository.dart +26 -30
  213. package/{lib/scaffold/backends/supabase/patch/lib/features/subscription → templates/firebase/lib/features/subscriptions}/shared/maybeshow_premium.dart +0 -2
  214. package/templates/firebase/lib/features/subscriptions/shared/subscription_management.dart +45 -0
  215. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/active_premium_content.dart +28 -4
  216. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_minimal.dart +7 -7
  217. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_row.dart +8 -8
  218. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_with_switch.dart +7 -7
  219. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_content.dart +7 -7
  220. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_page_factory.dart +5 -5
  221. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/premium_page.dart +5 -5
  222. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/comparison_table.dart +1 -1
  223. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/paywall_empty_state.dart +4 -4
  224. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_bottom_menu.dart +3 -4
  225. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_col.dart +1 -1
  226. package/templates/firebase/lib/i18n/en.i18n.json +171 -46
  227. package/templates/firebase/lib/i18n/es.i18n.json +175 -50
  228. package/templates/firebase/lib/i18n/pt.i18n.json +166 -41
  229. package/templates/firebase/lib/main.dart +6 -3
  230. package/templates/firebase/lib/router.dart +15 -23
  231. package/templates/firebase/pubspec.yaml +4 -5
  232. package/templates/firebase/test/core/data/repositories/user_repository_test.dart +3 -3
  233. package/templates/firebase/test/core/states/user_state_notifier_test.dart +4 -4
  234. package/templates/firebase/test/core/widgets/focus_ring_shape_test.dart +55 -0
  235. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_inapp_subscription_api.dart +11 -4
  236. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_revenuecat_product.dart +1 -0
  237. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_subscription_api.dart +2 -2
  238. package/templates/firebase/test/features/{subscription → subscriptions}/subscription_page_test.dart +4 -4
  239. package/templates/firebase/test/test_utils.dart +6 -6
  240. package/templates/firebase/web/index.html +5 -2
  241. package/lib/scaffold/backends/api/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -58
  242. package/lib/scaffold/backends/api/patch/lib/features/subscription/shared/maybeshow_premium.dart +0 -86
  243. package/lib/scaffold/backends/supabase/migrations/20240101000009_llm_messages.sql +0 -22
  244. package/lib/scaffold/backends/supabase/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -47
  245. package/templates/firebase/assets/images/onboarding/authentication-login-template.jpg +0 -0
  246. package/templates/firebase/assets/images/onboarding/img2.jpg +0 -0
  247. package/templates/firebase/assets/images/onboarding/img3.jpg +0 -0
  248. package/templates/firebase/assets/images/onboarding/notifications.png +0 -0
  249. package/templates/firebase/assets/images/onboarding/purchase.png +0 -0
  250. package/templates/firebase/lib/core/sidebar/kasy_sidebar.dart +0 -2021
  251. package/templates/firebase/lib/features/authentication/ui/widgets/auth_brand.dart +0 -35
  252. package/templates/firebase/lib/features/home/home_features_page.dart +0 -207
  253. package/templates/firebase/lib/features/llm_chat/api/llm_chat_api.dart +0 -50
  254. package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +0 -316
  255. package/templates/firebase/lib/features/subscription/shared/maybeshow_premium.dart +0 -85
  256. /package/templates/firebase/lib/features/{local_reminder → local_reminders}/repositories/reminder_preferences.dart +0 -0
  257. /package/templates/firebase/lib/features/{subscription → subscriptions}/providers/models/premium_state.dart +0 -0
  258. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/feature_line.dart +0 -0
  259. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background.dart +0 -0
  260. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background_gradient.dart +0 -0
  261. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_banner.dart +0 -0
  262. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_card.dart +0 -0
  263. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_close_button.dart +0 -0
  264. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_feature.dart +0 -0
  265. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_row.dart +0 -0
  266. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/trial_switcher.dart +0 -0
  267. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/unsubscribe_feedback_popup.dart +0 -0
@@ -29,18 +29,18 @@ const { toPackageName, buildTokens } = require('../scaffold/backends/firebase/to
29
29
 
30
30
  const execAsync = promisify(exec);
31
31
 
32
- /** URL do endpoint LLM no app (espelha defineUpdates de llm_chat). */
33
- function resolveLlmChatEndpoint(answers, kitSetup) {
32
+ /** URL do endpoint AI no app (espelha defineUpdates de ai_chat). */
33
+ function resolveAiChatEndpoint(answers, kitSetup) {
34
34
  if (kitSetup?.backendProvider === 'api') {
35
- return answers.llmEndpoint || 'YOUR_LLM_ENDPOINT';
35
+ return answers.aiEndpoint || 'YOUR_AI_ENDPOINT';
36
36
  }
37
37
  if (kitSetup?.backendProvider === 'supabase') {
38
38
  const ref = kitSetup.supabaseProjectId || 'YOUR_PROJECT_REF';
39
- return `https://${ref}.supabase.co/functions/v1/llm-chat`;
39
+ return `https://${ref}.supabase.co/functions/v1/ai-chat`;
40
40
  }
41
41
  const projectId = kitSetup?.firebaseProjectId || 'YOUR_PROJECT_ID';
42
42
  const region = kitSetup?.functionsRegion || 'us-central1';
43
- return `https://${region}-${projectId}.cloudfunctions.net/llmChat`;
43
+ return `https://${region}-${projectId}.cloudfunctions.net/aiChat`;
44
44
  }
45
45
 
46
46
  // ── Helpers ──────────────────────────────────────────────────────────────────
@@ -54,6 +54,7 @@ async function getActiveModules(kitSetup, projectDir) {
54
54
  if (kitSetup.analyticsProvider === 'mixpanel') modules.push('analytics');
55
55
  if (kitSetup.withFacebookPixel) modules.push('facebook');
56
56
  if (kitSetup.subscriptionModule) modules.push('revenuecat');
57
+ if (kitSetup.stripeModule) modules.push('stripe');
57
58
  if (kitSetup.withOnboarding) modules.push('onboarding');
58
59
  if (kitSetup.webCompat) modules.push('web');
59
60
  if (kitSetup.feedbackModule) modules.push('feedback');
@@ -61,7 +62,7 @@ async function getActiveModules(kitSetup, projectDir) {
61
62
  const featuresPath = path.join(projectDir, 'lib', 'core', 'config', 'features.dart');
62
63
  if (await fs.pathExists(featuresPath)) {
63
64
  const content = await fs.readFile(featuresPath, 'utf8');
64
- if (/const bool withLlmChat\s*=\s*true/.test(content)) modules.push('llm_chat');
65
+ if (/const bool withAiChat\s*=\s*true/.test(content)) modules.push('ai_chat');
65
66
  }
66
67
 
67
68
  if (await fs.pathExists(path.join(projectDir, '.github', 'workflows'))) modules.push('ci');
@@ -214,9 +215,11 @@ function applyKitSetupFlag(config, module, answers) {
214
215
  case 'facebook':
215
216
  config.withFacebookPixel = true;
216
217
  break;
218
+ case 'stripe':
219
+ config.stripeModule = true;
220
+ break;
217
221
  case 'revenuecat':
218
222
  config.subscriptionModule = true;
219
- if (answers.revenuecatWeb) config.revenuecatWeb = true;
220
223
  // Track which RC keys the user configured so `kasy doctor` can warn
221
224
  // about release readiness without re-reading .env. Booleans only — we
222
225
  // never persist the key values themselves to kit_setup.json.
@@ -235,7 +238,7 @@ function applyKitSetupFlag(config, module, answers) {
235
238
  case 'feedback':
236
239
  config.feedbackModule = true;
237
240
  break;
238
- case 'llm_chat':
241
+ case 'ai_chat':
239
242
  case 'widget':
240
243
  case 'ci':
241
244
  // controlled by features.dart / patch — no extra kit_setup flag needed
@@ -287,6 +290,16 @@ const MODULE_META = {
287
290
  featureFlag: null,
288
291
  pubspecDeps: {},
289
292
  },
293
+ stripe: {
294
+ // Stripe (web payments) is server-side only: no client env, no dart-define,
295
+ // no extra pubspec deps. Enabling the module flips withStripe; the secret
296
+ // keys are configured on the backend via `kasy configure stripe`.
297
+ promptKeys: [],
298
+ defineUpdates: () => ({}),
299
+ envLines: () => [],
300
+ featureFlag: 'withStripe',
301
+ pubspecDeps: {},
302
+ },
290
303
  onboarding: {
291
304
  promptKeys: [],
292
305
  defineUpdates: () => ({}),
@@ -308,15 +321,15 @@ const MODULE_META = {
308
321
  featureFlag: 'withFeedback',
309
322
  pubspecDeps: {},
310
323
  },
311
- llm_chat: {
312
- promptKeys: ['llmProvider', 'llmSystemPrompt', 'llmApiKey'],
324
+ ai_chat: {
325
+ promptKeys: ['aiProvider', 'aiSystemPrompt', 'aiApiKey'],
313
326
  defineUpdates: (a, kitSetup) => ({
314
- LLM_CHAT_ENDPOINT: resolveLlmChatEndpoint(a, kitSetup),
327
+ AI_CHAT_ENDPOINT: resolveAiChatEndpoint(a, kitSetup),
315
328
  }),
316
329
  envLines: (a, kitSetup) => [
317
- `LLM_CHAT_ENDPOINT=${resolveLlmChatEndpoint(a, kitSetup)}`,
330
+ `AI_CHAT_ENDPOINT=${resolveAiChatEndpoint(a, kitSetup)}`,
318
331
  ],
319
- featureFlag: 'withLlmChat',
332
+ featureFlag: 'withAiChat',
320
333
  pubspecDeps: {},
321
334
  },
322
335
  widget: {
@@ -378,8 +391,8 @@ const PROMPT_QUESTIONS = {
378
391
  },
379
392
  onCancel: cancel,
380
393
  }),
381
- llmProvider: (t, cancel) => ui.select({
382
- message: t('add.prompt.llmProvider'),
394
+ aiProvider: (t, cancel) => ui.select({
395
+ message: t('add.prompt.aiProvider'),
383
396
  initialValue: 'openai',
384
397
  options: [
385
398
  { value: 'openai', label: 'OpenAI (gpt-4o-mini)' },
@@ -387,38 +400,38 @@ const PROMPT_QUESTIONS = {
387
400
  ],
388
401
  onCancel: cancel,
389
402
  }),
390
- llmSystemPrompt: (t, cancel) => ui.text({
391
- message: t('add.prompt.llmSystemPrompt'),
403
+ aiSystemPrompt: (t, cancel) => ui.text({
404
+ message: t('add.prompt.aiSystemPrompt'),
392
405
  onCancel: cancel,
393
406
  }),
394
- llmApiKey: (t, cancel) => ui.password({
395
- message: t('add.prompt.llmApiKey'),
407
+ aiApiKey: (t, cancel) => ui.password({
408
+ message: t('add.prompt.aiApiKey'),
396
409
  onCancel: cancel,
397
410
  }),
398
- llmEndpoint: (t, cancel) => ui.text({
399
- message: t('add.prompt.llmEndpoint'),
411
+ aiEndpoint: (t, cancel) => ui.text({
412
+ message: t('add.prompt.aiEndpoint'),
400
413
  onCancel: cancel,
401
414
  }),
402
415
  };
403
416
 
404
- // ── LLM Chat post-add: copy edge function, set secrets, write functions/.env ──
417
+ // ── AI Chat post-add: copy edge function, set secrets, write functions/.env ──
405
418
 
406
419
  const SUPABASE_EDGE_FUNCTIONS_SOURCE = path.join(
407
420
  __dirname, '..', 'scaffold', 'backends', 'supabase', 'edge-functions'
408
421
  );
409
422
 
410
- async function postAddLlmChat(projectDir, kitSetup, answers, t) {
423
+ async function postAddAiChat(projectDir, kitSetup, answers, t) {
411
424
  const backend = kitSetup?.backendProvider ?? 'firebase';
412
- const apiKey = answers.llmApiKey?.trim() || '';
413
- const provider = answers.llmProvider || 'openai';
414
- const systemPrompt = answers.llmSystemPrompt?.trim() || '';
425
+ const apiKey = answers.aiApiKey?.trim() || '';
426
+ const provider = answers.aiProvider || 'openai';
427
+ const systemPrompt = answers.aiSystemPrompt?.trim() || '';
415
428
  const projectRef = kitSetup?.supabaseProjectId || '';
416
429
 
417
430
  // 1. Firebase: write functions/.env (non-secret vars read by Cloud Functions runtime)
418
431
  if (backend === 'firebase') {
419
432
  const envPath = path.join(projectDir, 'functions', '.env');
420
- const lines = [`LLM_PROVIDER=${provider}`];
421
- if (systemPrompt) lines.push(`LLM_SYSTEM_PROMPT=${systemPrompt}`);
433
+ const lines = [`AI_PROVIDER=${provider}`];
434
+ if (systemPrompt) lines.push(`AI_SYSTEM_PROMPT=${systemPrompt}`);
422
435
  const existing = (await fs.pathExists(envPath)) ? await fs.readFile(envPath, 'utf8') : '';
423
436
  const toAdd = lines.filter((l) => !existing.includes(l.split('=')[0]));
424
437
  if (toAdd.length > 0) {
@@ -428,11 +441,11 @@ async function postAddLlmChat(projectDir, kitSetup, answers, t) {
428
441
 
429
442
  // 1b. Supabase: copy Edge Function file + set ALL vars as Supabase Secrets
430
443
  // (Edge Functions deployed on Supabase read from Deno.env.get() = Supabase Secrets,
431
- // NOT from local .env files — so LLM_PROVIDER and LLM_SYSTEM_PROMPT must be secrets too)
444
+ // NOT from local .env files — so AI_PROVIDER and AI_SYSTEM_PROMPT must be secrets too)
432
445
  if (backend === 'supabase') {
433
- // Copy llm-chat edge function into the project if not already present
434
- const edgeFnSrc = path.join(SUPABASE_EDGE_FUNCTIONS_SOURCE, 'llm-chat');
435
- const edgeFnDest = path.join(projectDir, 'supabase', 'functions', 'llm-chat');
446
+ // Copy ai-chat edge function into the project if not already present
447
+ const edgeFnSrc = path.join(SUPABASE_EDGE_FUNCTIONS_SOURCE, 'ai-chat');
448
+ const edgeFnDest = path.join(projectDir, 'supabase', 'functions', 'ai-chat');
436
449
  if (await fs.pathExists(edgeFnSrc) && !(await fs.pathExists(edgeFnDest))) {
437
450
  await fs.copy(edgeFnSrc, edgeFnDest);
438
451
  }
@@ -440,7 +453,7 @@ async function postAddLlmChat(projectDir, kitSetup, answers, t) {
440
453
 
441
454
  // 2. Set API key as secret (skip if blank)
442
455
  if (!apiKey) {
443
- ui.log.warn(t('add.llm_chat.skipSecret'));
456
+ ui.log.warn(t('add.ai_chat.skipSecret'));
444
457
  return { deployOk: false, deployAttempted: false };
445
458
  }
446
459
 
@@ -449,29 +462,29 @@ async function postAddLlmChat(projectDir, kitSetup, answers, t) {
449
462
 
450
463
  if (backend === 'firebase') {
451
464
  const spinner = ui.spinner({ color: paintLime });
452
- spinner.start(t('add.llm_chat.settingSecret'));
465
+ spinner.start(t('add.ai_chat.settingSecret'));
453
466
  try {
454
467
  // Write to temp file — avoids trailing newline (echo) and shell injection risks
455
- const tmpFile = path.join(os.tmpdir(), `llm_api_key_${Date.now()}.tmp`);
468
+ const tmpFile = path.join(os.tmpdir(), `ai_api_key_${Date.now()}.tmp`);
456
469
  await fs.outputFile(tmpFile, apiKey, 'utf8');
457
- await execAsync(`firebase functions:secrets:set LLM_API_KEY --data-file="${tmpFile}"`, { cwd: projectDir });
470
+ await execAsync(`firebase functions:secrets:set AI_API_KEY --data-file="${tmpFile}"`, { cwd: projectDir });
458
471
  await fs.remove(tmpFile);
459
- spinner.stop(t('add.llm_chat.secretSet'));
472
+ spinner.stop(t('add.ai_chat.secretSet'));
460
473
  secretsOk = true;
461
474
  } catch {
462
- spinner.stop(`⚠ ${t('add.llm_chat.secretFailed')}`);
463
- ui.log.message(kleur.dim('Run manually: firebase functions:secrets:set LLM_API_KEY'));
475
+ spinner.stop(`⚠ ${t('add.ai_chat.secretFailed')}`);
476
+ ui.log.message(kleur.dim('Run manually: firebase functions:secrets:set AI_API_KEY'));
464
477
  }
465
478
  } else if (backend === 'supabase') {
466
479
  const spinner = ui.spinner({ color: paintLime });
467
- spinner.start(t('add.llm_chat.settingSecret'));
468
- // Set LLM_API_KEY, LLM_PROVIDER and LLM_SYSTEM_PROMPT all as Supabase Secrets.
480
+ spinner.start(t('add.ai_chat.settingSecret'));
481
+ // Set AI_API_KEY, AI_PROVIDER and AI_SYSTEM_PROMPT all as Supabase Secrets.
469
482
  // Deployed Edge Functions read from Deno.env.get() = Supabase Secrets, NOT from .env files.
470
483
  const esc = (v) => String(v).replace(/"/g, '\\"');
471
484
  const toSet = [
472
- { name: 'LLM_API_KEY', value: apiKey },
473
- { name: 'LLM_PROVIDER', value: provider },
474
- ...(systemPrompt ? [{ name: 'LLM_SYSTEM_PROMPT', value: systemPrompt }] : []),
485
+ { name: 'AI_API_KEY', value: apiKey },
486
+ { name: 'AI_PROVIDER', value: provider },
487
+ ...(systemPrompt ? [{ name: 'AI_SYSTEM_PROMPT', value: systemPrompt }] : []),
475
488
  ];
476
489
  let allOk = true;
477
490
  for (const { name, value } of toSet) {
@@ -482,33 +495,33 @@ async function postAddLlmChat(projectDir, kitSetup, answers, t) {
482
495
  if (r && r.ok === false) allOk = false;
483
496
  }
484
497
  if (allOk) {
485
- spinner.stop(t('add.llm_chat.secretSet'));
498
+ spinner.stop(t('add.ai_chat.secretSet'));
486
499
  secretsOk = true;
487
500
  } else {
488
- spinner.stop(`⚠ ${t('add.llm_chat.secretFailed')}`);
489
- ui.log.message(kleur.dim(`Run manually: supabase secrets set LLM_API_KEY=YOUR_KEY LLM_PROVIDER=${provider}${refFlag}`));
501
+ spinner.stop(`⚠ ${t('add.ai_chat.secretFailed')}`);
502
+ ui.log.message(kleur.dim(`Run manually: supabase secrets set AI_API_KEY=YOUR_KEY AI_PROVIDER=${provider}${refFlag}`));
490
503
  }
491
504
  }
492
505
 
493
- // 3. Deploy the LLM function automatically
506
+ // 3. Deploy the AI function automatically
494
507
  if (backend === 'api') return { deployOk: false, deployAttempted: false };
495
508
 
496
509
  const deploySpinner = ui.timedSpinner({ color: paintLime });
497
- deploySpinner.start(t('add.llm_chat.deploying'));
510
+ deploySpinner.start(t('add.ai_chat.deploying'));
498
511
  try {
499
512
  if (backend === 'firebase') {
500
- await execAsync('firebase deploy --only functions:llmChat', { cwd: projectDir, timeout: 180_000 });
513
+ await execAsync('firebase deploy --only functions:aiChat', { cwd: projectDir, timeout: 180_000 });
501
514
  } else if (backend === 'supabase') {
502
- await execAsync(`supabase functions deploy llm-chat --no-verify-jwt${refFlag}`, { cwd: projectDir, timeout: 180_000 });
515
+ await execAsync(`supabase functions deploy ai-chat --no-verify-jwt${refFlag}`, { cwd: projectDir, timeout: 180_000 });
503
516
  }
504
- deploySpinner.stop(t('add.llm_chat.deployed'));
517
+ deploySpinner.stop(t('add.ai_chat.deployed'));
505
518
  return { deployOk: true, deployAttempted: true };
506
519
  } catch {
507
- deploySpinner.stop(`⚠ ${t('add.llm_chat.deployFailed')}`);
520
+ deploySpinner.stop(`⚠ ${t('add.ai_chat.deployFailed')}`);
508
521
  if (backend === 'firebase') {
509
- ui.log.message(kleur.dim('Run manually: firebase deploy --only functions:llmChat'));
522
+ ui.log.message(kleur.dim('Run manually: firebase deploy --only functions:aiChat'));
510
523
  } else {
511
- ui.log.message(kleur.dim(`Run manually: supabase functions deploy llm-chat --no-verify-jwt${refFlag}`));
524
+ ui.log.message(kleur.dim(`Run manually: supabase functions deploy ai-chat --no-verify-jwt${refFlag}`));
512
525
  }
513
526
  return { deployOk: false, deployAttempted: true };
514
527
  }
@@ -635,46 +648,46 @@ async function runAdd(module, options = {}) {
635
648
  // 3. Check if already active
636
649
  const activeModules = await getActiveModules(kitSetup, projectDir);
637
650
  if (activeModules.includes(normalized)) {
638
- // llm_chat can be re-run to reconfigure credentials even when already active
639
- if (normalized !== 'llm_chat') {
651
+ // ai_chat can be re-run to reconfigure credentials even when already active
652
+ if (normalized !== 'ai_chat') {
640
653
  printCompactHeader(t);
641
654
  ui.log.warn(t('add.alreadyActive', { module: normalized }));
642
655
  return;
643
656
  }
644
657
  printCompactHeader(t);
645
- ui.intro(t('add.llm_chat.reconfigure'));
658
+ ui.intro(t('add.ai_chat.reconfigure'));
646
659
  // Skip to credential-only flow: prompt → postAdd → done
647
660
  const answers = {};
648
661
  if (!options.yes) {
649
- for (const key of ['llmProvider', 'llmSystemPrompt', 'llmApiKey']) {
662
+ for (const key of ['aiProvider', 'aiSystemPrompt', 'aiApiKey']) {
650
663
  const ask = PROMPT_QUESTIONS[key];
651
664
  if (!ask) continue;
652
665
  answers[key] = (await ask(t, cancel)) ?? '';
653
666
  }
654
667
  if (kitSetup.backendProvider === 'api') {
655
- answers.llmEndpoint = (await PROMPT_QUESTIONS.llmEndpoint(t, cancel)) || '';
668
+ answers.aiEndpoint = (await PROMPT_QUESTIONS.aiEndpoint(t, cancel)) || '';
656
669
  }
657
670
  }
658
- const defines = MODULE_META.llm_chat.defineUpdates(answers, kitSetup);
671
+ const defines = MODULE_META.ai_chat.defineUpdates(answers, kitSetup);
659
672
  if (Object.keys(defines).length > 0) {
660
673
  await updateLaunchJson(projectDir, defines);
661
674
  await updateMakefile(projectDir, defines);
662
- const envLines = MODULE_META.llm_chat.envLines(answers, kitSetup);
675
+ const envLines = MODULE_META.ai_chat.envLines(answers, kitSetup);
663
676
  if (envLines.length > 0) {
664
677
  await appendEnvExample(projectDir, envLines);
665
678
  await appendEnvFile(projectDir, envLines);
666
679
  }
667
680
  }
668
- const { deployOk, deployAttempted } = await postAddLlmChat(projectDir, kitSetup, answers, t);
681
+ const { deployOk, deployAttempted } = await postAddAiChat(projectDir, kitSetup, answers, t);
669
682
  const backend = kitSetup?.backendProvider ?? 'firebase';
670
683
  const needsManualDeploy = deployAttempted && !deployOk;
671
684
  let nextStepsMsg;
672
685
  if (backend === 'firebase') {
673
- nextStepsMsg = t(needsManualDeploy ? 'add.llm_chat.nextSteps.firebase.deployFailed' : 'add.llm_chat.nextSteps.firebase');
686
+ nextStepsMsg = t(needsManualDeploy ? 'add.ai_chat.nextSteps.firebase.deployFailed' : 'add.ai_chat.nextSteps.firebase');
674
687
  } else if (backend === 'supabase') {
675
- nextStepsMsg = t(needsManualDeploy ? 'add.llm_chat.nextSteps.supabase.deployFailed' : 'add.llm_chat.nextSteps.supabase');
688
+ nextStepsMsg = t(needsManualDeploy ? 'add.ai_chat.nextSteps.supabase.deployFailed' : 'add.ai_chat.nextSteps.supabase');
676
689
  } else {
677
- nextStepsMsg = t('add.llm_chat.nextSteps.api');
690
+ nextStepsMsg = t('add.ai_chat.nextSteps.api');
678
691
  }
679
692
  ui.outro(kleur.cyan(nextStepsMsg));
680
693
  return;
@@ -693,9 +706,9 @@ async function runAdd(module, options = {}) {
693
706
  if (!ask) continue;
694
707
  answers[key] = (await ask(t, cancel)) ?? '';
695
708
  }
696
- // Extra prompt for API backend (custom LLM endpoint)
697
- if (normalized === 'llm_chat' && kitSetup.backendProvider === 'api') {
698
- answers.llmEndpoint = (await PROMPT_QUESTIONS.llmEndpoint(t, cancel)) || '';
709
+ // Extra prompt for API backend (custom AI endpoint)
710
+ if (normalized === 'ai_chat' && kitSetup.backendProvider === 'api') {
711
+ answers.aiEndpoint = (await PROMPT_QUESTIONS.aiEndpoint(t, cancel)) || '';
699
712
  }
700
713
  }
701
714
 
@@ -792,7 +805,7 @@ async function runAdd(module, options = {}) {
792
805
  }
793
806
 
794
807
  // 11. build_runner (only when needed: features with codegen)
795
- const needsBuildRunner = ['revenuecat', 'analytics', 'sentry', 'onboarding', 'llm_chat', 'feedback'].includes(normalized);
808
+ const needsBuildRunner = ['revenuecat', 'analytics', 'sentry', 'onboarding', 'ai_chat', 'feedback'].includes(normalized);
796
809
  if (needsBuildRunner) {
797
810
  const spinner = ui.timedSpinner({ color: paintLime });
798
811
  spinner.start(t('add.buildRunner'));
@@ -804,10 +817,10 @@ async function runAdd(module, options = {}) {
804
817
  }
805
818
  }
806
819
 
807
- // 12. LLM Chat: set secrets, write env vars, and deploy
808
- let llmDeployResult = null;
809
- if (normalized === 'llm_chat') {
810
- llmDeployResult = await postAddLlmChat(projectDir, kitSetup, answers, t);
820
+ // 12. AI Chat: set secrets, write env vars, and deploy
821
+ let aiDeployResult = null;
822
+ if (normalized === 'ai_chat') {
823
+ aiDeployResult = await postAddAiChat(projectDir, kitSetup, answers, t);
811
824
  }
812
825
 
813
826
  // Show note if any
@@ -815,17 +828,17 @@ async function runAdd(module, options = {}) {
815
828
  ui.log.message(kleur.dim(t(meta.note)));
816
829
  }
817
830
 
818
- // LLM Chat: show next steps (adjust based on whether deploy succeeded)
819
- if (normalized === 'llm_chat') {
831
+ // AI Chat: show next steps (adjust based on whether deploy succeeded)
832
+ if (normalized === 'ai_chat') {
820
833
  const backend = kitSetup?.backendProvider ?? 'firebase';
821
- const needsManualDeploy = llmDeployResult?.deployAttempted && !llmDeployResult?.deployOk;
834
+ const needsManualDeploy = aiDeployResult?.deployAttempted && !aiDeployResult?.deployOk;
822
835
  let nextStepsMsg;
823
836
  if (backend === 'firebase') {
824
- nextStepsMsg = t(needsManualDeploy ? 'add.llm_chat.nextSteps.firebase.deployFailed' : 'add.llm_chat.nextSteps.firebase');
837
+ nextStepsMsg = t(needsManualDeploy ? 'add.ai_chat.nextSteps.firebase.deployFailed' : 'add.ai_chat.nextSteps.firebase');
825
838
  } else if (backend === 'supabase') {
826
- nextStepsMsg = t(needsManualDeploy ? 'add.llm_chat.nextSteps.supabase.deployFailed' : 'add.llm_chat.nextSteps.supabase');
839
+ nextStepsMsg = t(needsManualDeploy ? 'add.ai_chat.nextSteps.supabase.deployFailed' : 'add.ai_chat.nextSteps.supabase');
827
840
  } else {
828
- nextStepsMsg = t('add.llm_chat.nextSteps.api');
841
+ nextStepsMsg = t('add.ai_chat.nextSteps.api');
829
842
  }
830
843
  ui.log.info(nextStepsMsg);
831
844
  }
@@ -99,14 +99,6 @@ const FEATURE_BLOCKS = [
99
99
  hint: 'Used on physical Android',
100
100
  validate: (v) => (/^goog_/.test(v) ? undefined : 'Must start with `goog_`'),
101
101
  },
102
- {
103
- key: 'RC_WEB_API_KEY',
104
- destination: 'env',
105
- label: 'RevenueCat — Web Billing (rcb_…)',
106
- hint: 'Only if app has Web/PWA',
107
- onlyIf: (kit) => !!kit.revenuecatWeb,
108
- validate: (v) => (/^rcb_/.test(v) ? undefined : 'Must start with `rcb_`'),
109
- },
110
102
  {
111
103
  key: 'RC_WEBHOOK_KEY',
112
104
  destination: 'firebaseSecret',
@@ -133,6 +125,40 @@ const FEATURE_BLOCKS = [
133
125
  },
134
126
  ],
135
127
  },
128
+ {
129
+ id: 'stripe',
130
+ titleKey: 'configure.section.stripe',
131
+ isActive: (kit) => !!kit.stripeModule,
132
+ fields: [
133
+ {
134
+ key: 'STRIPE_SECRET_KEY',
135
+ // firebase → Functions secret; supabase → edge function secret. Same
136
+ // command (`kasy configure stripe`) for both backends.
137
+ destination: (kit) => (kit.backendProvider === 'supabase' ? 'supabaseSecret' : 'firebaseSecret'),
138
+ label: 'Stripe — Secret Key (sk_…)',
139
+ hint: 'Server-side. Stripe Dashboard → Developers → API keys',
140
+ onlyIf: (kit) => kit.backendProvider !== 'api',
141
+ validate: (v) => (/^sk_/.test(v) ? undefined : 'Must start with `sk_`'),
142
+ },
143
+ {
144
+ key: 'STRIPE_WEBHOOK_SECRET',
145
+ destination: (kit) => (kit.backendProvider === 'supabase' ? 'supabaseSecret' : 'firebaseSecret'),
146
+ label: 'Stripe — Webhook Signing Secret (whsec_…)',
147
+ hint: 'Stripe → Developers → Webhooks → your endpoint → Signing secret',
148
+ onlyIf: (kit) => kit.backendProvider !== 'api',
149
+ validate: (v) => (/^whsec_/.test(v) ? undefined : 'Must start with `whsec_`'),
150
+ },
151
+ {
152
+ key: 'STRIPE_PRODUCT_ID',
153
+ // firebase reads it as a defineString (functions/.env); supabase as a secret.
154
+ destination: (kit) => (kit.backendProvider === 'supabase' ? 'supabaseSecret' : 'functionsEnv'),
155
+ label: 'Stripe — Product ID (prod_…, optional)',
156
+ hint: 'Limits the paywall to one product; blank lists all recurring prices',
157
+ onlyIf: (kit) => kit.backendProvider !== 'api',
158
+ validate: (v) => (!v || /^prod_/.test(v) ? undefined : 'Must start with `prod_`'),
159
+ },
160
+ ],
161
+ },
136
162
  {
137
163
  id: 'facebook',
138
164
  titleKey: 'configure.section.facebook',
@@ -155,26 +181,26 @@ const FEATURE_BLOCKS = [
155
181
  ],
156
182
  },
157
183
  {
158
- id: 'llm_chat',
159
- titleKey: 'configure.section.llmChat',
160
- isActive: (kit, ctx) => !!ctx.hasLlmChat,
184
+ id: 'ai_chat',
185
+ titleKey: 'configure.section.aiChat',
186
+ isActive: (kit, ctx) => !!ctx.hasAiChat,
161
187
  fields: [
162
188
  {
163
- key: 'LLM_PROVIDER',
189
+ key: 'AI_PROVIDER',
164
190
  destination: 'functionsEnv',
165
- label: 'LLM Provider (openai or gemini)',
191
+ label: 'AI Provider (openai or gemini)',
166
192
  validate: (v) => (/^(openai|gemini)$/.test(v) ? undefined : 'Use openai or gemini'),
167
193
  },
168
194
  {
169
- key: 'LLM_SYSTEM_PROMPT',
195
+ key: 'AI_SYSTEM_PROMPT',
170
196
  destination: 'functionsEnv',
171
- label: 'LLM system prompt (instructions)',
197
+ label: 'AI system prompt (instructions)',
172
198
  validate: () => undefined,
173
199
  },
174
200
  {
175
- key: 'LLM_API_KEY',
201
+ key: 'AI_API_KEY',
176
202
  destination: 'firebaseSecret',
177
- label: 'LLM provider API key',
203
+ label: 'AI provider API key',
178
204
  hint: 'OpenAI dashboard → API keys, or Google AI Studio',
179
205
  onlyIf: (kit) => kit.backendProvider === 'firebase',
180
206
  validate: () => undefined,
@@ -344,12 +370,26 @@ async function setFirebaseSecret(projectDir, key, value, t) {
344
370
  }
345
371
  }
346
372
 
373
+ async function setSupabaseSecret(projectDir, key, value) {
374
+ // execFile (no shell) so user-supplied values aren't interpolated. The project
375
+ // must be linked (done at `kasy new` for Supabase). `supabase secrets set`
376
+ // accepts KEY=VALUE as a positional arg.
377
+ const { execFile } = require('node:child_process');
378
+ const execFileAsync = promisify(execFile);
379
+ try {
380
+ await execFileAsync('supabase', ['secrets', 'set', `${key}=${value}`], { cwd: projectDir });
381
+ return { ok: true };
382
+ } catch (err) {
383
+ return { ok: false, error: err.stderr || err.message };
384
+ }
385
+ }
386
+
347
387
  async function detectFeatureContext(projectDir) {
348
- const ctx = { hasLlmChat: false };
388
+ const ctx = { hasAiChat: false };
349
389
  const featuresPath = path.join(projectDir, 'lib', 'core', 'config', 'features.dart');
350
390
  if (await fs.pathExists(featuresPath)) {
351
391
  const content = await fs.readFile(featuresPath, 'utf8');
352
- if (/const bool withLlmChat\s*=\s*true/.test(content)) ctx.hasLlmChat = true;
392
+ if (/const bool withAiChat\s*=\s*true/.test(content)) ctx.hasAiChat = true;
353
393
  }
354
394
  return ctx;
355
395
  }
@@ -391,6 +431,13 @@ async function runConfigure(options = {}) {
391
431
  return;
392
432
  }
393
433
 
434
+ // Stripe on the API backend: the server is the user's own, so the CLI has no
435
+ // secret store to write to. Show clear guidance instead of prompting.
436
+ if (kit.backendProvider === 'api' && activeBlocks.some((b) => b.id === 'stripe')) {
437
+ ui.log.step(kleur.bold(t('configure.section.stripe')));
438
+ ui.log.message(kleur.dim(`– ${t('configure.stripe.apiNote')}`));
439
+ }
440
+
394
441
  // Facebook reads from both build files; cache once if the block is active.
395
442
  const facebookState = activeBlocks.some((b) => b.id === 'facebook')
396
443
  ? await readFacebookState(projectDir)
@@ -402,14 +449,15 @@ async function runConfigure(options = {}) {
402
449
  const items = [];
403
450
  for (const field of block.fields) {
404
451
  if (field.onlyIf && !field.onlyIf(kit, ctx)) continue;
452
+ const dest = typeof field.destination === 'function' ? field.destination(kit, ctx) : field.destination;
405
453
  let currentValue;
406
- if (field.destination === 'env') currentValue = envState.get(field.key);
407
- else if (field.destination === 'functionsEnv') currentValue = fnEnvState.get(field.key);
408
- else if (field.destination === 'firebaseSecret') currentValue = undefined;
409
- else if (field.destination === 'facebook') {
454
+ if (dest === 'env') currentValue = envState.get(field.key);
455
+ else if (dest === 'functionsEnv') currentValue = fnEnvState.get(field.key);
456
+ else if (dest === 'firebaseSecret' || dest === 'supabaseSecret') currentValue = undefined;
457
+ else if (dest === 'facebook') {
410
458
  currentValue = field.key === 'FB_APP_ID' ? facebookState?.appId : facebookState?.token;
411
459
  }
412
- items.push({ field, filled: isFilled(currentValue) });
460
+ items.push({ field, dest, filled: isFilled(currentValue) });
413
461
  }
414
462
  if (items.length > 0) plan.push({ block, items });
415
463
  }
@@ -435,6 +483,7 @@ async function runConfigure(options = {}) {
435
483
  const envUpdates = new Map();
436
484
  const fnEnvUpdates = new Map();
437
485
  const secretUpdates = new Map();
486
+ const supabaseSecretUpdates = new Map();
438
487
  const facebookUpdates = {};
439
488
  let answeredCount = 0;
440
489
  let skippedCount = 0;
@@ -448,14 +497,15 @@ async function runConfigure(options = {}) {
448
497
  }
449
498
 
450
499
  ui.log.step(kleur.bold(t(block.titleKey)));
451
- for (const { field, filled } of items) {
500
+ for (const { field, filled, dest } of items) {
452
501
  if (filled) {
453
502
  ui.log.message(kleur.dim(` ✓ ${field.label} ${kleur.dim('— ' + t('configure.alreadyFilledShort'))}`));
454
503
  continue;
455
504
  }
456
- const dst = field.destination === 'firebaseSecret' ? ' [Firebase Secret]'
457
- : field.destination === 'functionsEnv' ? ' [functions/.env]'
458
- : field.destination === 'facebook' ? ' [Info.plist + strings.xml]'
505
+ const dst = dest === 'firebaseSecret' ? ' [Firebase Secret]'
506
+ : dest === 'supabaseSecret' ? ' [Supabase Secret]'
507
+ : dest === 'functionsEnv' ? ' [functions/.env]'
508
+ : dest === 'facebook' ? ' [Info.plist + strings.xml]'
459
509
  : '';
460
510
  const value = await ui.text({
461
511
  message: `${field.label}${kleur.dim(dst)}`,
@@ -476,10 +526,11 @@ async function runConfigure(options = {}) {
476
526
  skippedCount++;
477
527
  continue;
478
528
  }
479
- if (field.destination === 'env') envUpdates.set(field.key, trimmed);
480
- else if (field.destination === 'functionsEnv') fnEnvUpdates.set(field.key, trimmed);
481
- else if (field.destination === 'firebaseSecret') secretUpdates.set(field.key, trimmed);
482
- else if (field.destination === 'facebook') {
529
+ if (dest === 'env') envUpdates.set(field.key, trimmed);
530
+ else if (dest === 'functionsEnv') fnEnvUpdates.set(field.key, trimmed);
531
+ else if (dest === 'firebaseSecret') secretUpdates.set(field.key, trimmed);
532
+ else if (dest === 'supabaseSecret') supabaseSecretUpdates.set(field.key, trimmed);
533
+ else if (dest === 'facebook') {
483
534
  if (field.key === 'FB_APP_ID') facebookUpdates.appId = trimmed;
484
535
  else if (field.key === 'FB_CLIENT_TOKEN') facebookUpdates.token = trimmed;
485
536
  }
@@ -530,6 +581,23 @@ async function runConfigure(options = {}) {
530
581
  }
531
582
  }
532
583
 
584
+ if (supabaseSecretUpdates.size > 0) {
585
+ const supaSpinner = ui.spinner();
586
+ supaSpinner.start(t('configure.settingSecrets', { count: supabaseSecretUpdates.size }));
587
+ let okCount = 0;
588
+ const failures = [];
589
+ for (const [key, value] of supabaseSecretUpdates) {
590
+ const result = await setSupabaseSecret(projectDir, key, value);
591
+ if (result.ok) okCount++;
592
+ else failures.push({ key, error: result.error });
593
+ }
594
+ supaSpinner.stop(t('configure.settingSecrets', { count: supabaseSecretUpdates.size }));
595
+ if (okCount > 0) ui.log.success(t('configure.savedSecrets', { count: okCount }));
596
+ for (const f of failures) {
597
+ ui.log.warn(`${t('configure.secretFailed', { key: f.key })}\n${kleur.dim((f.error || '').slice(0, 200))}`);
598
+ }
599
+ }
600
+
533
601
  const stillPending = totalPending - answeredCount;
534
602
  if (stillPending > 0) {
535
603
  ui.log.message(kleur.dim(`– ${t('configure.stillPending', { count: stillPending })}`));