kasy-cli 1.21.9 → 1.23.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 (269) 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 +22 -6
  67. package/lib/utils/env-tools.js +7 -0
  68. package/lib/utils/flutter-install.js +114 -0
  69. package/lib/utils/i18n/messages-en.js +52 -35
  70. package/lib/utils/i18n/messages-es.js +52 -35
  71. package/lib/utils/i18n/messages-pt.js +54 -37
  72. package/lib/utils/updates.js +15 -15
  73. package/package.json +1 -1
  74. package/templates/firebase/.env.example +2 -2
  75. package/templates/firebase/android/app/src/main/res/drawable/background.png +0 -0
  76. package/templates/firebase/android/app/src/main/res/drawable-night/background.png +0 -0
  77. package/templates/firebase/android/app/src/main/res/drawable-night-v21/background.png +0 -0
  78. package/templates/firebase/android/app/src/main/res/drawable-v21/background.png +0 -0
  79. package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +1 -1
  80. package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +1 -1
  81. package/templates/firebase/assets/images/logo_wordmark_dark.png +0 -0
  82. package/templates/firebase/assets/images/logo_wordmark_light.png +0 -0
  83. package/templates/firebase/docs/revenuecat-setup.es.md +1 -1
  84. package/templates/firebase/docs/revenuecat-setup.pt.md +1 -1
  85. package/templates/firebase/firestore.rules +24 -5
  86. package/templates/firebase/functions/package-lock.json +22 -1
  87. package/templates/firebase/functions/package.json +2 -1
  88. package/templates/firebase/functions/src/admin/functions.ts +113 -0
  89. package/templates/firebase/functions/src/{llm_chat → ai_chat}/index.ts +16 -16
  90. package/templates/firebase/functions/src/index.ts +8 -2
  91. package/templates/firebase/functions/src/notifications/device_triggers.ts +2 -2
  92. package/templates/firebase/functions/src/notifications/triggers.ts +3 -3
  93. package/templates/firebase/functions/src/subscriptions/models/subscription_status.ts +2 -0
  94. package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +222 -0
  95. package/templates/firebase/functions/src/subscriptions/subscriptions_functions.ts +2 -2
  96. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png +0 -0
  97. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png +0 -0
  98. package/templates/firebase/lib/components/components.dart +4 -1
  99. package/templates/firebase/lib/components/kasy_app_bar.dart +22 -7
  100. package/templates/firebase/lib/components/kasy_avatar.dart +7 -6
  101. package/templates/firebase/lib/components/kasy_button.dart +23 -99
  102. package/templates/firebase/lib/components/kasy_dialog.dart +11 -11
  103. package/templates/firebase/lib/components/kasy_image_viewer.dart +84 -0
  104. package/templates/firebase/lib/components/{kasy_sidebar_pro.dart → kasy_sidebar.dart} +702 -425
  105. package/templates/firebase/lib/components/kasy_status_tag.dart +91 -0
  106. package/templates/firebase/lib/components/kasy_text_area.dart +1 -3
  107. package/templates/firebase/lib/components/kasy_text_field.dart +5 -6
  108. package/templates/firebase/lib/components/kasy_text_field_otp.dart +1 -3
  109. package/templates/firebase/lib/components/kasy_toast.dart +2 -2
  110. package/templates/firebase/lib/components/kasy_web_header.dart +209 -0
  111. package/templates/firebase/lib/core/ads/ads_provider.dart +1 -1
  112. package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +136 -23
  113. package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +19 -91
  114. package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +196 -96
  115. package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +54 -0
  116. package/templates/firebase/lib/core/config/app_env.dart +5 -11
  117. package/templates/firebase/lib/core/config/features.dart +5 -4
  118. package/templates/firebase/lib/core/data/api/analytics_api.dart +1 -1
  119. package/templates/firebase/lib/core/data/api/http_client.dart +1 -1
  120. package/templates/firebase/lib/core/data/entities/user_entity.dart +3 -0
  121. package/templates/firebase/lib/core/data/models/entitlement.dart +35 -0
  122. package/templates/firebase/lib/core/data/models/subscription.dart +13 -186
  123. package/templates/firebase/lib/core/data/models/user.dart +11 -0
  124. package/templates/firebase/lib/core/data/repositories/user_repository.dart +1 -1
  125. package/templates/firebase/lib/core/home_widgets/home_widget_background_task.dart +1 -1
  126. package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +1 -1
  127. package/templates/firebase/lib/core/icons/kasy_icons.dart +31 -1
  128. package/templates/firebase/lib/core/rating/api/rating_api.dart +2 -2
  129. package/templates/firebase/lib/core/states/logout_action.dart +25 -0
  130. package/templates/firebase/lib/core/states/user_state_notifier.dart +3 -3
  131. package/templates/firebase/lib/core/theme/colors.dart +488 -188
  132. package/templates/firebase/lib/core/theme/radius.dart +22 -11
  133. package/templates/firebase/lib/core/theme/shadows.dart +66 -0
  134. package/templates/firebase/lib/core/theme/texts.dart +75 -41
  135. package/templates/firebase/lib/core/theme/universal_theme.dart +9 -4
  136. package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +5 -4
  137. package/templates/firebase/lib/core/web_viewport_scale.dart +52 -0
  138. package/templates/firebase/lib/core/widgets/kasy_brand_badge.dart +118 -0
  139. package/templates/firebase/lib/core/widgets/kasy_brand_logo.dart +40 -0
  140. package/templates/firebase/lib/core/widgets/kasy_focus_ring.dart +125 -0
  141. package/templates/firebase/lib/core/widgets/kasy_hover.dart +53 -14
  142. package/templates/firebase/lib/core/widgets/kasy_pressable_depth.dart +18 -13
  143. package/templates/firebase/lib/{environnements.dart → environments.dart} +3 -14
  144. package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +159 -0
  145. package/templates/firebase/lib/features/ai_chat/api/ai_chat_api.dart +107 -0
  146. package/templates/firebase/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +64 -0
  147. package/templates/firebase/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +6 -6
  148. package/templates/firebase/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +73 -38
  149. package/templates/firebase/lib/features/ai_chat/providers/ai_conversations_notifier.dart +103 -0
  150. package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_avatars.dart → ai_chat/ui/widgets/ai_chat_avatars.dart} +13 -13
  151. package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_composer.dart → ai_chat/ui/widgets/ai_chat_composer.dart} +10 -10
  152. package/templates/firebase/lib/features/{llm_chat/llm_chat_page.dart → ai_chat/ui/widgets/ai_chat_conversation_view.dart} +80 -67
  153. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +285 -0
  154. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +163 -0
  155. package/templates/firebase/lib/features/authentication/api/authentication_api.dart +52 -13
  156. package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher.dart +12 -0
  157. package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher_web.dart +35 -0
  158. package/templates/firebase/lib/features/authentication/ui/recover_password_page.dart +108 -68
  159. package/templates/firebase/lib/features/authentication/ui/signin_page.dart +38 -51
  160. package/templates/firebase/lib/features/authentication/ui/signup_page.dart +38 -51
  161. package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +128 -0
  162. package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +61 -44
  163. package/templates/firebase/lib/features/feedbacks/api/feature_request_api.dart +32 -0
  164. package/templates/firebase/lib/features/feedbacks/models/feedback_state.dart +5 -5
  165. package/templates/firebase/lib/features/feedbacks/providers/feedback_page_notifier.dart +2 -2
  166. package/templates/firebase/lib/features/home/design_system_page.dart +808 -170
  167. package/templates/firebase/lib/features/home/home_components_page.dart +6 -3
  168. package/templates/firebase/lib/features/home/home_components_preview_page.dart +6 -6
  169. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +325 -186
  170. package/templates/firebase/lib/features/home/home_feed.dart +289 -0
  171. package/templates/firebase/lib/features/home/home_image_grid.dart +355 -0
  172. package/templates/firebase/lib/features/home/home_page.dart +11 -250
  173. package/templates/firebase/lib/features/{local_reminder → local_reminders}/providers/reminder_notifier.dart +1 -1
  174. package/templates/firebase/lib/features/{local_reminder → local_reminders}/ui/reminder_page.dart +2 -2
  175. package/templates/firebase/lib/features/notifications/shared/att_permission.dart +1 -1
  176. package/templates/firebase/lib/features/notifications/ui/request_notification_permission.dart +25 -61
  177. package/templates/firebase/lib/features/notifications/ui/widgets/permission_request_view.dart +117 -0
  178. package/templates/firebase/lib/features/onboarding/models/user_info.dart +16 -16
  179. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
  180. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_features.dart +7 -9
  181. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +71 -48
  182. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +3 -2
  183. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_questions.dart +5 -5
  184. package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +4 -4
  185. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_background.dart +4 -2
  186. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_feature.dart +39 -121
  187. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +105 -70
  188. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_module_mockups.dart +639 -0
  189. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_progress.dart +62 -50
  190. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
  191. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_step_header.dart +75 -0
  192. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_sticky_footer.dart +16 -5
  193. package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +26 -22
  194. package/templates/firebase/lib/features/settings/settings_page.dart +601 -90
  195. package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +1193 -0
  196. package/templates/firebase/lib/features/settings/ui/components/admin/admin_paywalls.dart +1 -1
  197. package/templates/firebase/lib/features/settings/ui/components/admin/admin_routes.dart +2 -3
  198. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_api.dart +86 -0
  199. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +1215 -0
  200. package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +236 -0
  201. package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +3 -3
  202. package/templates/firebase/lib/features/settings/ui/widgets/kasy_user_avatar.dart +77 -0
  203. package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +1 -0
  204. package/templates/firebase/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +17 -0
  205. package/templates/firebase/lib/features/{subscription → subscriptions}/api/inapp_subscription_api.dart +67 -46
  206. package/templates/firebase/lib/features/subscriptions/api/revenuecat_product.dart +189 -0
  207. package/templates/firebase/lib/features/subscriptions/api/stripe_backend_api.dart +55 -0
  208. package/templates/firebase/lib/features/subscriptions/api/stripe_payment_api.dart +82 -0
  209. package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +178 -0
  210. package/templates/firebase/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
  211. package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api.dart +53 -0
  212. package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api_provider.dart +21 -0
  213. package/templates/firebase/lib/features/{subscription → subscriptions}/providers/premium_page_provider.dart +9 -6
  214. package/templates/firebase/lib/features/{subscription → subscriptions}/repositories/subscription_repository.dart +26 -30
  215. package/{lib/scaffold/backends/supabase/patch/lib/features/subscription → templates/firebase/lib/features/subscriptions}/shared/maybeshow_premium.dart +0 -2
  216. package/templates/firebase/lib/features/subscriptions/shared/subscription_management.dart +45 -0
  217. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/active_premium_content.dart +28 -4
  218. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_minimal.dart +7 -7
  219. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_row.dart +8 -8
  220. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_with_switch.dart +7 -7
  221. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_content.dart +7 -7
  222. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_page_factory.dart +5 -5
  223. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/premium_page.dart +5 -5
  224. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/comparison_table.dart +1 -1
  225. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/paywall_empty_state.dart +4 -4
  226. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_bottom_menu.dart +3 -4
  227. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_col.dart +1 -1
  228. package/templates/firebase/lib/i18n/en.i18n.json +171 -46
  229. package/templates/firebase/lib/i18n/es.i18n.json +175 -50
  230. package/templates/firebase/lib/i18n/pt.i18n.json +166 -41
  231. package/templates/firebase/lib/main.dart +6 -3
  232. package/templates/firebase/lib/router.dart +15 -23
  233. package/templates/firebase/pubspec.yaml +5 -6
  234. package/templates/firebase/test/core/data/repositories/user_repository_test.dart +3 -3
  235. package/templates/firebase/test/core/states/user_state_notifier_test.dart +4 -4
  236. package/templates/firebase/test/core/widgets/focus_ring_shape_test.dart +55 -0
  237. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_inapp_subscription_api.dart +11 -4
  238. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_revenuecat_product.dart +1 -0
  239. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_subscription_api.dart +2 -2
  240. package/templates/firebase/test/features/{subscription → subscriptions}/subscription_page_test.dart +4 -4
  241. package/templates/firebase/test/test_utils.dart +6 -6
  242. package/templates/firebase/web/index.html +5 -2
  243. package/lib/scaffold/backends/api/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -58
  244. package/lib/scaffold/backends/api/patch/lib/features/subscription/shared/maybeshow_premium.dart +0 -86
  245. package/lib/scaffold/backends/supabase/migrations/20240101000009_llm_messages.sql +0 -22
  246. package/lib/scaffold/backends/supabase/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -47
  247. package/templates/firebase/assets/images/onboarding/authentication-login-template.jpg +0 -0
  248. package/templates/firebase/assets/images/onboarding/img2.jpg +0 -0
  249. package/templates/firebase/assets/images/onboarding/img3.jpg +0 -0
  250. package/templates/firebase/assets/images/onboarding/notifications.png +0 -0
  251. package/templates/firebase/assets/images/onboarding/purchase.png +0 -0
  252. package/templates/firebase/lib/core/sidebar/kasy_sidebar.dart +0 -2021
  253. package/templates/firebase/lib/features/authentication/ui/widgets/auth_brand.dart +0 -35
  254. package/templates/firebase/lib/features/home/home_features_page.dart +0 -207
  255. package/templates/firebase/lib/features/llm_chat/api/llm_chat_api.dart +0 -50
  256. package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +0 -316
  257. package/templates/firebase/lib/features/subscription/shared/maybeshow_premium.dart +0 -85
  258. /package/templates/firebase/lib/features/{local_reminder → local_reminders}/repositories/reminder_preferences.dart +0 -0
  259. /package/templates/firebase/lib/features/{subscription → subscriptions}/providers/models/premium_state.dart +0 -0
  260. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/feature_line.dart +0 -0
  261. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background.dart +0 -0
  262. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background_gradient.dart +0 -0
  263. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_banner.dart +0 -0
  264. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_card.dart +0 -0
  265. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_close_button.dart +0 -0
  266. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_feature.dart +0 -0
  267. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_row.dart +0 -0
  268. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/trial_switcher.dart +0 -0
  269. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/unsubscribe_feedback_popup.dart +0 -0
@@ -62,15 +62,16 @@ async function getActiveModules(kitSetup, projectDir) {
62
62
  if (kitSetup.analyticsProvider === 'mixpanel') modules.push('analytics');
63
63
  if (kitSetup.withFacebookPixel) modules.push('facebook');
64
64
  if (kitSetup.subscriptionModule) modules.push('revenuecat');
65
+ if (kitSetup.stripeModule) modules.push('stripe');
65
66
  if (kitSetup.withOnboarding) modules.push('onboarding');
66
67
  if (kitSetup.webCompat) modules.push('web');
67
68
  if (kitSetup.feedbackModule) modules.push('feedback');
68
69
 
69
- // Read features.dart for llm_chat, widget
70
+ // Read features.dart for ai_chat, widget
70
71
  const featuresPath = path.join(projectDir, 'lib', 'core', 'config', 'features.dart');
71
72
  if (await fs.pathExists(featuresPath)) {
72
73
  const content = await fs.readFile(featuresPath, 'utf8');
73
- if (/const bool withLlmChat\s*=\s*true/.test(content)) modules.push('llm_chat');
74
+ if (/const bool withAiChat\s*=\s*true/.test(content)) modules.push('ai_chat');
74
75
  }
75
76
 
76
77
  // Check ci by directory
@@ -114,6 +115,7 @@ async function runProjectChecks(projectDir, t, options = {}) {
114
115
 
115
116
  await runIosReleaseChecks(projectDir, t, options.language, config);
116
117
  await runRevenueCatChecks(projectDir, t, config);
118
+ await runStripeChecks(projectDir, t, config);
117
119
  }
118
120
 
119
121
  async function runIosReleaseChecks(projectDir, t, language, config = {}) {
@@ -249,6 +251,30 @@ async function runRevenueCatChecks(projectDir, t, config) {
249
251
  }
250
252
  }
251
253
 
254
+ async function runStripeChecks(projectDir, t, config) {
255
+ if (!config.stripeModule) return;
256
+
257
+ ui.log.step(kleur.bold(t('doctor.stripe.title')));
258
+ ui.log.message(kleur.dim(`– ${t('doctor.stripe.serverSide')}`));
259
+
260
+ if (config.backendProvider === 'firebase') {
261
+ ui.log.message(kleur.dim(`– ${t('doctor.stripe.secretsFirebase')}`));
262
+ if (config.firebaseProjectId) {
263
+ const region = config.functionsRegion || 'us-central1';
264
+ const url = `https://${region}-${config.firebaseProjectId}.cloudfunctions.net/stripeFunctions-stripeWebhook`;
265
+ ui.log.success(`${t('doctor.stripe.webhookUrl')}:\n${kleur.cyan(url)}`);
266
+ }
267
+ } else if (config.backendProvider === 'supabase') {
268
+ ui.log.message(kleur.dim(`– ${t('doctor.stripe.secretsSupabase')}`));
269
+ if (config.supabaseProjectId) {
270
+ const url = `https://${config.supabaseProjectId}.supabase.co/functions/v1/stripe-webhook`;
271
+ ui.log.success(`${t('doctor.stripe.webhookUrl')}:\n${kleur.cyan(url)}`);
272
+ }
273
+ } else {
274
+ ui.log.message(kleur.dim(`– ${t('doctor.stripe.secretsApi')}`));
275
+ }
276
+ }
277
+
252
278
  async function runDoctor(options = {}) {
253
279
  const language = options.language || detectDefaultLanguage();
254
280
  const t = createTranslator(language);
@@ -41,14 +41,14 @@ const KASY_AUDIENCE = process.env.KASY_INTERNAL === '1' ? 'internal' : 'public';
41
41
  const { generateFirebaseProject } = require('../scaffold/backends/firebase/generator');
42
42
  const { generateSupabaseProject } = require('../scaffold/backends/supabase/generator');
43
43
  const { generateApiProject } = require('../scaffold/backends/api/generator');
44
- const { createProjectAndGetKeys, setupLinkedProject, checkLoggedIn, getOrgsList, getProjectsByOrg, getProjectKeys } = require('../scaffold/backends/supabase/deploy');
44
+ const { createProjectAndGetKeys, setupLinkedProject, checkLoggedIn, getOrgsList, getProjectsByOrg, getProjectKeys, classifyCreateError } = require('../scaffold/backends/supabase/deploy');
45
45
  const { writeSupabaseGoogleAuthOptions, readSupabaseGoogleCredentials, getGoogleClientSecretViaGcloud, flutterfireConfigure, writeGoogleAuthOptions, writeGoogleIosUrlScheme, writeGoogleIosUrlSchemeFromClientId } = require('../scaffold/shared/post-build');
46
46
  const { toPackageName } = require('../scaffold/backends/firebase/tokens');
47
47
  const { setupFromScratch, setupExistingProject, listBillingAccounts, listGcpOrganizations, checkGcloudAuth, getFirebaseAccount, getGcloudInstallInstructions, enableAuthProviders, ensureFirebaseAuthInitialized, registerDebugSha1 } = require('../scaffold/backends/firebase/setup-from-scratch');
48
48
  const { enableAuthViaFirebaseCli } = require('../scaffold/backends/firebase/enable-auth-via-cli');
49
49
  const { createFcmServiceAccountKey } = require('../scaffold/shared/fcm-service-account');
50
50
 
51
- // Região padrão para criação de projetos Supabase via API.
51
+ // Default region for creating Supabase projects via the API.
52
52
  // Supabase regions: https://supabase.com/docs/guides/platform/regions
53
53
  const DEFAULT_SUPABASE_REGION = 'sa-east-1'; // São Paulo
54
54
 
@@ -383,6 +383,16 @@ function printSuccessCard(tr, answers, targetDir) {
383
383
 
384
384
  const lines = [];
385
385
 
386
+ // Show the app's bundle ID (Android applicationId / iOS bundle identifier) so
387
+ // the user knows exactly which identifier their project was created with — it's
388
+ // baked into the Android build, the iOS project and the Firebase app
389
+ // registration, and changing it later is a manual, multi-file operation.
390
+ if (answers.bundleId) {
391
+ lines.push(`📦 ${tr('new.success.bundleId')}: ${kleur.cyan(answers.bundleId)}`);
392
+ lines.push(kleur.dim(` ${tr('new.success.bundleId.hint')}`));
393
+ lines.push('');
394
+ }
395
+
386
396
  if (answers.modules?.length > 0) {
387
397
  const byId = Object.fromEntries(FEATURE_CATALOG.map((f) => [f.id, f]));
388
398
  const visible = answers.modules.filter((id) => byId[id]);
@@ -693,9 +703,12 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
693
703
  if (!isQuick) ui.log.info(paintLime(`── ${tr(key)} ──`));
694
704
  };
695
705
 
696
- // ── Bundle ID — Quick uses derived value silently; Step-by-step lets user override
706
+ // ── Bundle ID — asked in every interactive run (Quick included). It's baked into
707
+ // Android/iOS/Firebase and painful to change after the fact, so it's worth one
708
+ // Enter to confirm even in Quick (the field is pre-filled with the value derived
709
+ // from the app name). Only --yes (non-interactive) keeps the derived value silently.
697
710
  section('new.advanced.section.config');
698
- if (!isQuick) {
711
+ if (!yes) {
699
712
  const bundleId = await ui.text({
700
713
  message: tr('new.firebase.q.bundleId'),
701
714
  placeholder: tr('new.firebase.q.bundleId.hint'),
@@ -1160,13 +1173,24 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
1160
1173
  core.supabaseAnonKey = supabaseCreateResult.supabaseAnonKey;
1161
1174
  ui.log.success(tr('new.supabase.created'));
1162
1175
  } else {
1163
- ui.log.error(`${tr('new.supabase.createFailed')}: ${supabaseCreateResult.error}`);
1164
- ui.log.message(tr('new.supabase.loginHint'));
1176
+ // Tailor the message to WHY it failed, instead of always dumping the raw
1177
+ // API string + a login hint that may be irrelevant.
1178
+ const failKind = classifyCreateError(supabaseCreateResult.error);
1179
+ if (failKind === 'free_limit') {
1180
+ ui.log.error(tr('new.supabase.createFailed.freeLimit'));
1181
+ ui.note(tr('new.supabase.freeLimit.options'), tr('new.supabase.freeLimit.title'));
1182
+ ui.log.message(kleur.cyan('https://supabase.com/dashboard/projects'));
1183
+ } else if (failKind === 'login') {
1184
+ ui.log.error(`${tr('new.supabase.createFailed')}: ${supabaseCreateResult.error}`);
1185
+ ui.log.message(tr('new.supabase.loginHint'));
1186
+ } else {
1187
+ ui.log.error(`${tr('new.supabase.createFailed')}: ${supabaseCreateResult.error}`);
1188
+ }
1165
1189
  Object.assign(core, await promptSupabaseManual(tr, cancel));
1166
1190
  supabaseCreate = false;
1167
1191
  }
1168
1192
  } else {
1169
- // Usar projeto Supabase existente: org → projeto → keys → senha (mesmo fluxo de setup, sem criar)
1193
+ // Use an existing Supabase project: org → project → keys → password (same setup flow, without creating)
1170
1194
  const loginCheck = await checkLoggedIn();
1171
1195
  if (!loginCheck.ok) {
1172
1196
  showLoginRequired();
@@ -1321,8 +1345,8 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
1321
1345
  // Visual groups in display order (header key → feature ids in this group)
1322
1346
  const groups = [
1323
1347
  { header: null, ids: ['sentry', 'analytics', 'facebook'] },
1324
- { header: 'new.modules.header.monetization', ids: ['revenuecat'] },
1325
- { header: 'new.modules.header.features', ids: ['onboarding', 'web', 'widget', 'llm_chat', 'local_notifications'] },
1348
+ { header: 'new.modules.header.monetization', ids: ['revenuecat', 'stripe'] },
1349
+ { header: 'new.modules.header.features', ids: ['onboarding', 'web', 'widget', 'ai_chat', 'local_reminders'] },
1326
1350
  { header: 'new.modules.header.feedback', ids: ['feedback'] },
1327
1351
  { header: 'new.modules.header.ci', ids: ['ci'] },
1328
1352
  ];
@@ -1371,7 +1395,7 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
1371
1395
  // philosophy. User can opt-in to enter everything inline if they prefer.
1372
1396
  let configureCredsNow = false;
1373
1397
  if (!isQuick) {
1374
- const FEATURES_WITH_CREDS = ['revenuecat', 'sentry', 'analytics', 'llm_chat', 'facebook'];
1398
+ const FEATURES_WITH_CREDS = ['revenuecat', 'stripe', 'sentry', 'analytics', 'ai_chat', 'facebook'];
1375
1399
  if (modules.some((m) => FEATURES_WITH_CREDS.includes(m))) {
1376
1400
  section('new.advanced.section.creds');
1377
1401
  configureCredsNow = await ui.confirm({
@@ -1445,21 +1469,35 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
1445
1469
  }
1446
1470
  }
1447
1471
 
1448
- // RC web keyonly when configuring inline. Otherwise mark scaffolded.
1449
- if (modules.includes('revenuecat') && modules.includes('web')) {
1450
- moduleAnswers.revenuecatWeb = true;
1472
+ // Stripe (web payments)server-side keys only. In quick/deferred mode they
1473
+ // are configured later via `kasy configure stripe`. The app needs no Stripe
1474
+ // client key (hosted Checkout). Keys are deployed to the backend, never baked
1475
+ // into the app.
1476
+ if (modules.includes('stripe')) {
1451
1477
  if (askCreds) {
1452
- const rcWebKey = await ui.text({
1453
- message: tr('new.firebase.q.revenuecat.webKey'),
1478
+ moduleAnswers.stripeSecretKey = ((await ui.password({
1479
+ message: tr('new.firebase.q.stripe.secretKey'),
1480
+ onCancel: cancel,
1481
+ })) || '').trim();
1482
+ moduleAnswers.stripeWebhookSecret = ((await ui.text({
1483
+ message: tr('new.firebase.q.stripe.webhookSecret'),
1484
+ placeholder: tr('new.firebase.q.stripe.webhookSecret.hint'),
1454
1485
  validate: (v) => {
1455
- if (!v || !v.trim()) return undefined; // optional — blank is fine
1456
- return /^rcb_/.test(v.trim()) ? undefined : tr('new.firebase.q.revenuecat.webKey.invalid');
1486
+ const s = (v || '').trim();
1487
+ if (!s) return undefined; // optional — blank is fine
1488
+ return /^whsec_/.test(s) ? undefined : tr('new.firebase.q.stripe.webhookSecret.invalid');
1457
1489
  },
1458
1490
  onCancel: cancel,
1459
- });
1460
- moduleAnswers.rcWebKey = (rcWebKey || '').trim();
1491
+ })) || '').trim();
1492
+ moduleAnswers.stripeProductId = ((await ui.text({
1493
+ message: tr('new.firebase.q.stripe.productId'),
1494
+ placeholder: tr('new.firebase.q.stripe.productId.hint'),
1495
+ onCancel: cancel,
1496
+ })) || '').trim();
1461
1497
  } else {
1462
- moduleAnswers.rcWebKey = '';
1498
+ moduleAnswers.stripeSecretKey = '';
1499
+ moduleAnswers.stripeWebhookSecret = '';
1500
+ moduleAnswers.stripeProductId = '';
1463
1501
  }
1464
1502
  }
1465
1503
 
@@ -1485,11 +1523,11 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
1485
1523
  });
1486
1524
  }
1487
1525
 
1488
- // LLM Chat credentials.
1489
- if (modules.includes('llm_chat')) {
1526
+ // AI Chat credentials.
1527
+ if (modules.includes('ai_chat')) {
1490
1528
  if (askCreds) {
1491
- moduleAnswers.llmProvider = await ui.select({
1492
- message: tr('add.prompt.llmProvider'),
1529
+ moduleAnswers.aiProvider = await ui.select({
1530
+ message: tr('add.prompt.aiProvider'),
1493
1531
  initialValue: 'openai',
1494
1532
  options: [
1495
1533
  { value: 'openai', label: 'OpenAI (gpt-4o-mini)' },
@@ -1497,16 +1535,16 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
1497
1535
  ],
1498
1536
  onCancel: cancel,
1499
1537
  });
1500
- moduleAnswers.llmSystemPrompt = await ui.text({
1501
- message: tr('add.prompt.llmSystemPrompt'),
1538
+ moduleAnswers.aiSystemPrompt = await ui.text({
1539
+ message: tr('add.prompt.aiSystemPrompt'),
1502
1540
  onCancel: cancel,
1503
1541
  });
1504
- moduleAnswers.llmApiKey = await ui.password({
1505
- message: tr('add.prompt.llmApiKey'),
1542
+ moduleAnswers.aiApiKey = await ui.password({
1543
+ message: tr('add.prompt.aiApiKey'),
1506
1544
  onCancel: cancel,
1507
1545
  });
1508
1546
  } else {
1509
- moduleAnswers.llmConfigureLater = true;
1547
+ moduleAnswers.aiConfigureLater = true;
1510
1548
  }
1511
1549
  }
1512
1550
 
@@ -1652,12 +1690,12 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
1652
1690
  await fs.writeJson(path.join(kasyDir, 'config.json'), { functionsRegion: firebaseRegion }, { spaces: 2 });
1653
1691
  } catch (_) {}
1654
1692
 
1655
- // Write functions/.env with LLM non-secret vars (provider + system prompt)
1656
- if (modules.includes('llm_chat') && !moduleAnswers.llmConfigureLater) {
1693
+ // Write functions/.env with AI non-secret vars (provider + system prompt)
1694
+ if (modules.includes('ai_chat') && !moduleAnswers.aiConfigureLater) {
1657
1695
  try {
1658
1696
  const envLines = [];
1659
- if (moduleAnswers.llmProvider) envLines.push(`LLM_PROVIDER=${moduleAnswers.llmProvider}`);
1660
- if (moduleAnswers.llmSystemPrompt?.trim()) envLines.push(`LLM_SYSTEM_PROMPT=${moduleAnswers.llmSystemPrompt.trim()}`);
1697
+ if (moduleAnswers.aiProvider) envLines.push(`AI_PROVIDER=${moduleAnswers.aiProvider}`);
1698
+ if (moduleAnswers.aiSystemPrompt?.trim()) envLines.push(`AI_SYSTEM_PROMPT=${moduleAnswers.aiSystemPrompt.trim()}`);
1661
1699
  if (envLines.length > 0) {
1662
1700
  await fs.ensureDir(path.join(targetDir, 'functions'));
1663
1701
  await fs.appendFile(path.join(targetDir, 'functions', '.env'), envLines.join('\n') + '\n', 'utf8');
@@ -1774,10 +1812,15 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
1774
1812
  metaAccessToken: moduleAnswers.metaAccessToken,
1775
1813
  metaDatasetId: moduleAnswers.metaDatasetId,
1776
1814
  } : {}),
1777
- ...(answers.modules?.includes('llm_chat') && !moduleAnswers.llmConfigureLater ? {
1778
- llmApiKey: moduleAnswers.llmApiKey,
1779
- llmProvider: moduleAnswers.llmProvider,
1780
- llmSystemPrompt: moduleAnswers.llmSystemPrompt,
1815
+ ...(answers.modules?.includes('ai_chat') && !moduleAnswers.aiConfigureLater ? {
1816
+ aiApiKey: moduleAnswers.aiApiKey,
1817
+ aiProvider: moduleAnswers.aiProvider,
1818
+ aiSystemPrompt: moduleAnswers.aiSystemPrompt,
1819
+ } : {}),
1820
+ ...(answers.modules?.includes('stripe') ? {
1821
+ stripeSecretKey: moduleAnswers.stripeSecretKey,
1822
+ stripeWebhookSecret: moduleAnswers.stripeWebhookSecret,
1823
+ stripeProductId: moduleAnswers.stripeProductId,
1781
1824
  } : {}),
1782
1825
  };
1783
1826
  const google = googleWebClientId && googleClientSecret
@@ -81,7 +81,7 @@ async function assertLocalNotificationsEnabled(projectDir, t) {
81
81
  throw new Error(t('notifications.error.noFeatures'));
82
82
  }
83
83
  const content = await fs.readFile(featuresPath, 'utf8');
84
- if (!/const bool withLocalNotifications\s*=\s*true/.test(content)) {
84
+ if (!/const bool withLocalReminders\s*=\s*true/.test(content)) {
85
85
  throw new Error(t('notifications.error.notEnabled'));
86
86
  }
87
87
  }
@@ -24,6 +24,7 @@ const {
24
24
  writeNoOpFeatureRequestRepository,
25
25
  writeMainDart,
26
26
  writeNoOpSubscriptionStubs,
27
+ writeStripeOnlySubscription,
27
28
  writeNoOpSentryUsages,
28
29
  } = require('../scaffold/shared/generator-utils');
29
30
  const { toPackageName } = require('../scaffold/backends/firebase/tokens');
@@ -49,8 +50,8 @@ const MODULE_DEPS = {
49
50
  const MODULE_DEFINES = {
50
51
  sentry: ['SENTRY_DSN'],
51
52
  analytics: ['MIXPANEL_TOKEN'],
52
- revenuecat: ['RC_ANDROID_API_KEY', 'RC_IOS_API_KEY', 'RC_WEB_API_KEY'],
53
- llm_chat: ['LLM_CHAT_ENDPOINT'],
53
+ revenuecat: ['RC_ANDROID_API_KEY', 'RC_IOS_API_KEY'],
54
+ ai_chat: ['AI_CHAT_ENDPOINT'],
54
55
  };
55
56
 
56
57
  /**
@@ -65,7 +66,6 @@ const MODULE_ENV_KEYS = {
65
66
  'RC_TEST_KEY',
66
67
  'RC_IOS_PROD_KEY',
67
68
  'RC_ANDROID_PROD_KEY',
68
- 'RC_WEB_API_KEY',
69
69
  // Legacy single-key format — only present in projects generated before
70
70
  // the test/prod split. Listed here so removal cleans them up too.
71
71
  'RC_ANDROID_API_KEY',
@@ -80,7 +80,8 @@ const MODULE_FEATURE_FLAGS = {
80
80
  onboarding: 'withOnboarding',
81
81
  web: 'withWeb',
82
82
  feedback: 'withFeedback',
83
- llm_chat: 'withLlmChat',
83
+ ai_chat: 'withAiChat',
84
+ stripe: 'withStripe',
84
85
  };
85
86
 
86
87
  // ── Helpers ──────────────────────────────────────────────────────────────────
@@ -95,6 +96,7 @@ async function getActiveModules(kitSetup, projectDir) {
95
96
  if (kitSetup.analyticsProvider === 'mixpanel') modules.push('analytics');
96
97
  if (kitSetup.withFacebookPixel) modules.push('facebook');
97
98
  if (kitSetup.subscriptionModule) modules.push('revenuecat');
99
+ if (kitSetup.stripeModule) modules.push('stripe');
98
100
  if (kitSetup.withOnboarding) modules.push('onboarding');
99
101
  if (kitSetup.webCompat) modules.push('web');
100
102
  if (kitSetup.feedbackModule) modules.push('feedback');
@@ -102,7 +104,7 @@ async function getActiveModules(kitSetup, projectDir) {
102
104
  const featuresPath = path.join(projectDir, 'lib', 'core', 'config', 'features.dart');
103
105
  if (await fs.pathExists(featuresPath)) {
104
106
  const content = await fs.readFile(featuresPath, 'utf8');
105
- if (/const bool withLlmChat\s*=\s*true/.test(content)) modules.push('llm_chat');
107
+ if (/const bool withAiChat\s*=\s*true/.test(content)) modules.push('ai_chat');
106
108
  }
107
109
 
108
110
  if (await fs.pathExists(path.join(projectDir, '.github', 'workflows'))) modules.push('ci');
@@ -226,9 +228,11 @@ function revertKitSetupFlag(config, module) {
226
228
  case 'facebook':
227
229
  config.withFacebookPixel = false;
228
230
  break;
231
+ case 'stripe':
232
+ config.stripeModule = false;
233
+ break;
229
234
  case 'revenuecat':
230
235
  config.subscriptionModule = false;
231
- config.revenuecatWeb = false;
232
236
  break;
233
237
  case 'onboarding':
234
238
  config.withOnboarding = false;
@@ -239,7 +243,7 @@ function revertKitSetupFlag(config, module) {
239
243
  case 'feedback':
240
244
  config.feedbackModule = false;
241
245
  break;
242
- case 'llm_chat':
246
+ case 'ai_chat':
243
247
  case 'widget':
244
248
  case 'ci':
245
249
  // Controlled by features.dart / patch — no kit_setup flag to revert
@@ -254,18 +258,33 @@ function revertKitSetupFlag(config, module) {
254
258
  * For modules whose stubs are regenerated (analytics, facebook, sentry),
255
259
  * no deletion is needed — the stub writer overwrites the file in-place.
256
260
  */
257
- async function removeModuleFiles(projectDir, module, backend) {
261
+ async function removeModuleFiles(projectDir, module, backend, modulesAfterRemoval = []) {
258
262
  // Modules that have dedicated directories to remove
259
263
  const dirRemovals = {
260
- llm_chat: [path.join('lib', 'features', 'llm_chat')],
264
+ ai_chat: [path.join('lib', 'features', 'ai_chat')],
261
265
  onboarding: [path.join('lib', 'features', 'onboarding')],
262
266
  widget: [path.join('lib', 'core', 'home_widgets')],
263
- revenuecat: [path.join('lib', 'features', 'subscription')],
267
+ // revenuecat/stripe: the subscription/ dir is SHARED by both payment modules
268
+ // and is handled below (removed only when neither remains).
264
269
  // feedback: handled below (dir deleted, no-op stub recreated afterward)
265
270
  // ci: handled below (multiple top-level paths)
266
271
  // analytics/facebook/sentry: no dir — stub overwrites the file in place
267
272
  };
268
273
 
274
+ // The subscription/ core is shared by RevenueCat (mobile) and Stripe (web).
275
+ // Remove it only when NEITHER payment module remains. If the other one stays,
276
+ // keep the dir — the stub regeneration step (writeStripeOnlySubscription /
277
+ // no-op) reshapes it for the remaining module.
278
+ if (module === 'revenuecat' || module === 'stripe') {
279
+ const stillHasPayments =
280
+ modulesAfterRemoval.includes('revenuecat') || modulesAfterRemoval.includes('stripe');
281
+ if (!stillHasPayments) {
282
+ const subDir = path.join(projectDir, 'lib', 'features', 'subscriptions');
283
+ if (await fs.pathExists(subDir)) await fs.remove(subDir);
284
+ }
285
+ return;
286
+ }
287
+
269
288
  if (module === 'feedback') {
270
289
  // Delete the full feedbacks dir; writeNoOpFeatureRequestRepository recreates
271
290
  // only the repository stub (the rest of the UI/api code is gone).
@@ -295,9 +314,9 @@ async function removeModuleFiles(projectDir, module, backend) {
295
314
  if (await fs.pathExists(fullPath)) await fs.remove(fullPath);
296
315
  }
297
316
 
298
- // LLM Chat on Supabase: also remove the copied edge function
299
- if (module === 'llm_chat' && backend === 'supabase') {
300
- const edgeFnPath = path.join(projectDir, 'supabase', 'functions', 'llm-chat');
317
+ // AI Chat on Supabase: also remove the copied edge function
318
+ if (module === 'ai_chat' && backend === 'supabase') {
319
+ const edgeFnPath = path.join(projectDir, 'supabase', 'functions', 'ai-chat');
301
320
  if (await fs.pathExists(edgeFnPath)) await fs.remove(edgeFnPath);
302
321
  }
303
322
  }
@@ -405,7 +424,7 @@ async function runRemove(module, options = {}) {
405
424
  }
406
425
 
407
426
  // 10. Remove files and directories added by the module's patch
408
- await removeModuleFiles(projectDir, normalized, kitSetup.backendProvider);
427
+ await removeModuleFiles(projectDir, normalized, kitSetup.backendProvider, modulesAfterRemoval);
409
428
 
410
429
  // 11. Regenerate no-op stubs for the removed module
411
430
  const packageName = toPackageName(kitSetup.appName);
@@ -423,6 +442,15 @@ async function runRemove(module, options = {}) {
423
442
  await writeNoOpAdminHomeWidgets(projectDir).catch(() => {});
424
443
  }
425
444
  if (normalized === 'revenuecat') {
445
+ if (modulesAfterRemoval.includes('stripe')) {
446
+ // Both → Stripe-only: keep the core, strip the RevenueCat (purchases_flutter) files.
447
+ await writeStripeOnlySubscription(projectDir, packageName).catch(() => {});
448
+ } else {
449
+ await writeNoOpSubscriptionStubs(projectDir, packageName).catch(() => {});
450
+ }
451
+ }
452
+ if (normalized === 'stripe' && !modulesAfterRemoval.includes('revenuecat')) {
453
+ // Stripe-only → none: the dir was removed; write the no-op subscription stubs.
426
454
  await writeNoOpSubscriptionStubs(projectDir, packageName).catch(() => {});
427
455
  }
428
456
 
@@ -452,7 +480,7 @@ async function runRemove(module, options = {}) {
452
480
 
453
481
  // 14. build_runner (only for modules whose removal changes codegen inputs)
454
482
  const needsBuildRunner = [
455
- 'revenuecat', 'analytics', 'sentry', 'onboarding', 'llm_chat', 'feedback',
483
+ 'revenuecat', 'analytics', 'sentry', 'onboarding', 'ai_chat', 'feedback',
456
484
  ].includes(normalized);
457
485
  if (needsBuildRunner) {
458
486
  const spinner = ui.timedSpinner();
@@ -319,8 +319,8 @@ async function runRun(directory, options = {}) {
319
319
 
320
320
  // Override RC keys based on device. We only touch RC_ANDROID_API_KEY /
321
321
  // RC_IOS_API_KEY when the launch.json already declares them — that's the
322
- // signal the project uses RevenueCat. Web defines (RC_WEB_API_KEY) aren't
323
- // touched here; they're already a single key.
322
+ // signal the project uses RevenueCat. Web payments use Stripe (server-side
323
+ // only), so there are no web dart-defines to touch here.
324
324
  const usesRc = dartDefines.some(
325
325
  (a) => a.startsWith('--dart-define=RC_ANDROID_API_KEY=') ||
326
326
  a.startsWith('--dart-define=RC_IOS_API_KEY='),
@@ -26,7 +26,7 @@ const execAsync = promisify(exec);
26
26
  const CHANGELOG_PATH = path.join(__dirname, '..', 'scaffold', 'CHANGELOG.json');
27
27
 
28
28
  const NEEDS_BUILD_RUNNER = [
29
- 'revenuecat', 'analytics', 'sentry', 'onboarding', 'llm_chat', 'feedback',
29
+ 'revenuecat', 'analytics', 'sentry', 'onboarding', 'ai_chat', 'feedback',
30
30
  ];
31
31
  const COMPONENTS_UPDATE_TARGET = 'components';
32
32
  const CORE_UPDATE_TARGET = 'core';
@@ -129,7 +129,7 @@ async function getActiveModules(kitSetup, projectDir) {
129
129
  const featuresPath = path.join(projectDir, 'lib', 'core', 'config', 'features.dart');
130
130
  if (await fs.pathExists(featuresPath)) {
131
131
  const content = await fs.readFile(featuresPath, 'utf8');
132
- if (/const bool withLlmChat\s*=\s*true/.test(content)) modules.push('llm_chat');
132
+ if (/const bool withAiChat\s*=\s*true/.test(content)) modules.push('ai_chat');
133
133
  }
134
134
  if (await fs.pathExists(path.join(projectDir, '.github', 'workflows'))) modules.push('ci');
135
135
  const pubspecPath = path.join(projectDir, 'pubspec.yaml');
@@ -1,4 +1,18 @@
1
1
  {
2
+ "1.22.0": {
3
+ "modules": {
4
+ "components": {
5
+ "pt": "Navegação repaginada para desktop/web: novo KasyWebHeader, KasySidebar e KasyBottomBar mais limpos e consistentes, com KasyAppBar se adaptando ao desktop.",
6
+ "en": "Revamped desktop/web navigation: new KasyWebHeader, cleaner and more consistent KasySidebar and KasyBottomBar, with KasyAppBar adapting to desktop.",
7
+ "es": "Navegación renovada para escritorio/web: nuevo KasyWebHeader, KasySidebar y KasyBottomBar más limpios y consistentes, con KasyAppBar adaptándose al escritorio."
8
+ },
9
+ "core": {
10
+ "pt": "Cor do texto sobre o accent (accentForeground) agora é automática: ela se ajusta sozinha pela luminância do accent (clara em accents escuros, escura em claros), então trocar a cor da marca continua legível em ambos os temas.",
11
+ "en": "Text color on top of the accent (accentForeground) is now automatic: it adapts to the accent's luminance (light on dark accents, dark on light ones), so changing the brand color stays legible in both themes.",
12
+ "es": "El color del texto sobre el accent (accentForeground) ahora es automático: se ajusta solo según la luminancia del accent (claro en accents oscuros, oscuro en claros), así cambiar el color de marca sigue siendo legible en ambos temas."
13
+ }
14
+ }
15
+ },
2
16
  "1.21.0": {
3
17
  "modules": {
4
18
  "components": {
@@ -1,22 +1,22 @@
1
1
  /**
2
2
  * API (REST) project generator.
3
3
  *
4
- * Wrapper fino sobre generateProject().
5
- * A lógica de geração comum (cópia, pub get, slang, build_runner, flutterfire)
6
- * vive em cli/lib/scaffold/generate.js.
4
+ * Thin wrapper over generateProject().
5
+ * The common generation logic (copy, pub get, slang, build_runner, flutterfire)
6
+ * lives in cli/lib/scaffold/generate.js.
7
7
  *
8
- * Hook específico (applyBackendSetup):
9
- * 1. Aplica api/patch/ sobre o template Firebase copiado
10
- * 2. Substitui pubspec.yaml pelo template API (pubspec.yaml.tpl)
11
- * 3. Remove artefatos Firebase exclusivos (feedbacks mantido — patch substitui as APIs)
12
- * 4. Escreve overrides de environment (apiBaseUrl)
8
+ * Specific hook (applyBackendSetup):
9
+ * 1. Applies api/patch/ over the copied Firebase template
10
+ * 2. Replaces pubspec.yaml with the API template (pubspec.yaml.tpl)
11
+ * 3. Removes Firebase-only artifacts (feedbacks keptthe patch replaces the APIs)
12
+ * 4. Writes environment overrides (apiBaseUrl)
13
13
  */
14
14
 
15
15
  const path = require('node:path');
16
16
  const fs = require('fs-extra');
17
17
  const { applyPatch } = require('../../engine');
18
18
  const { generateProject } = require('../../generate');
19
- const { writeEnvironnementsOverrides } = require('../../shared/generator-utils');
19
+ const { writeEnvironmentsOverrides } = require('../../shared/generator-utils');
20
20
  const { removeBackendSpecificArtifacts } = require('../../shared/backend-config');
21
21
 
22
22
  const API_PATCH_DIR = path.join(__dirname, 'patch');
@@ -34,7 +34,7 @@ async function generateApiProject(targetDir, options) {
34
34
  // 1. Patch API (auth via HTTP, storage null, notifications via REST)
35
35
  const { filesApplied } = await applyPatch(API_PATCH_DIR, dir, tokens, pathReplacements);
36
36
 
37
- // 1b. README do backend (excluído do applyPatch) — copiar manualmente
37
+ // 1b. Backend README (excluded from applyPatch) — copy it manually
38
38
  const language = opts.language ?? 'pt';
39
39
  const readmeLang = ['en', 'pt', 'es'].includes(language) ? language : 'en';
40
40
  const candidates = [
@@ -48,7 +48,7 @@ async function generateApiProject(targetDir, options) {
48
48
  }
49
49
  }
50
50
 
51
- // 2. Substituir pubspec.yaml pelos deps do API backend
51
+ // 2. Replace pubspec.yaml with the API backend deps
52
52
  const pubspecContent = await fs.readFile(API_PUBSPEC, 'utf8');
53
53
  let pubspecReplaced = pubspecContent;
54
54
  for (const [from, to] of Object.entries(tokens)) {
@@ -56,11 +56,11 @@ async function generateApiProject(targetDir, options) {
56
56
  }
57
57
  await fs.outputFile(path.join(dir, 'pubspec.yaml'), pubspecReplaced, 'utf8');
58
58
 
59
- // 3. Remover artefatos exclusivos do Firebase (feedbacks é mantido patch API substitui as APIs)
59
+ // 3. Remove Firebase-only artifacts (feedbacks keptthe API patch replaces the APIs)
60
60
  await removeBackendSpecificArtifacts(dir, 'api', opts.modules ?? []);
61
61
 
62
- // 4. Escrever overrides de environment com a URL da API
63
- await writeEnvironnementsOverrides(dir, 'api', tokens, { apiBaseUrl: opts.apiBaseUrl });
62
+ // 4. Write environment overrides with the API URL
63
+ await writeEnvironmentsOverrides(dir, 'api', tokens, { apiBaseUrl: opts.apiBaseUrl });
64
64
 
65
65
  return {
66
66
  detail: `${filesApplied} patch files`,
@@ -79,6 +79,89 @@ Após editar qualquer `.i18n.json`, sempre rodar `dart run slang`.
79
79
 
80
80
  ---
81
81
 
82
+ ## Admin (console interno)
83
+
84
+ O app tem um **console de admin** (aba Usuários, Solicitações, etc.) liberado só para quem tem `role == "admin"`. O `role` é um campo de **controle de acesso** que o **seu backend controla** — o app nunca pode escrever nele.
85
+
86
+ ### Campo `role` no usuário
87
+
88
+ O seu endpoint de usuário (`GET /users/{id}`) deve devolver o `role` junto com os outros dados:
89
+
90
+ ```json
91
+ { "id": "...", "email": "ana@b.com", "name": "Ana", "onboarded": true, "role": "admin" }
92
+ ```
93
+
94
+ - `role` ausente / `null` → usuário normal.
95
+ - `role: "admin"` → libera o console de admin.
96
+
97
+ **Regra de segurança (obrigatória):** o `role` só pode ser definido no servidor (banco/painel). O backend deve **rejeitar** qualquer tentativa do cliente de gravar `role` (ex.: num `PATCH /users/{id}`), senão qualquer pessoa vira admin. Defina-o manualmente no seu banco para promover alguém.
98
+
99
+ ### Endpoint: listar usuários
100
+
101
+ ```
102
+ GET /admin/users
103
+ Auth: Authorization: Bearer <token> (enviado automaticamente pelo app)
104
+ O servidor DEVE validar role == "admin" e responder 403 caso contrário.
105
+
106
+ 200 OK:
107
+ {
108
+ "users": [
109
+ {
110
+ "id": "...",
111
+ "email": "ana@b.com" | null,
112
+ "name": "Ana" | null,
113
+ "createdAt": 1700000000000, // epoch em milissegundos | null
114
+ "subscriber": true // tem assinatura ativa?
115
+ }
116
+ ],
117
+ "totalUsers": 142, // tamanho real da coleção
118
+ "truncated": false // true quando há mais usuários do que os retornados
119
+ }
120
+ ```
121
+
122
+ Devolva os usuários **mais recentes** (com um limite, ex.: 1000). O app faz busca, ordenação e paginação localmente — por isso uma chamada só já basta e a experiência fica instantânea.
123
+
124
+ ### Endpoint: moderar solicitações (aba Solicitações)
125
+
126
+ ```
127
+ GET /admin/feature-requests → lista TODAS (ativas + ocultas), mais votadas primeiro
128
+ PATCH /admin/feature-requests/{id} body: {"active": true|false}
129
+ PATCH /admin/feature-requests/{id} body: {"title": {...}, "description": {...}}
130
+ ```
131
+
132
+ Mesma regra: validar `role == "admin"` e responder 403 caso contrário. `title`/`description` são mapas por idioma (`{"en": "...", "pt": "...", "es": "..."}`).
133
+
134
+ ### Endpoints: AI Chat (histórico de conversas)
135
+
136
+ O assistente guarda várias conversas por usuário, cada uma com várias mensagens.
137
+ O usuário é identificado pelo token `Authorization: Bearer`.
138
+
139
+ ```
140
+ GET /ai-conversations → lista as conversas do usuário, mais recente primeiro
141
+ POST /ai-conversations → cria uma conversa vazia e devolve o objeto criado
142
+ DELETE /ai-conversations/{id} → apaga a conversa e todas as mensagens dela
143
+ GET /ai-conversations/{id}/messages → mensagens da conversa, mais antiga primeiro
144
+ POST /ai-conversations/{id}/messages body: {"role": "...", "content": "...", "created_at": "..."}
145
+ ```
146
+
147
+ Formato de uma conversa (o "última mensagem" é desnormalizado para a lista ficar barata):
148
+
149
+ ```json
150
+ {
151
+ "id": "...",
152
+ "created_at": "2026-01-01T12:00:00Z",
153
+ "updated_at": "2026-01-01T12:05:00Z",
154
+ "last_message_role": "user" | "assistant" | null,
155
+ "last_message_content": "..." | null
156
+ }
157
+ ```
158
+
159
+ Ao salvar uma mensagem, o servidor deve atualizar `updated_at`, `last_message_role` e
160
+ `last_message_content` da conversa. O streaming da resposta da IA continua no endpoint
161
+ `AI_CHAT_ENDPOINT` (SSE) — ele só recebe `message` + `history`, não persiste nada.
162
+
163
+ ---
164
+
82
165
  ## Segurança
83
166
 
84
167
  O `.gitignore` já exclui: `.env`, `.env.*`, `*.pem`, `*.keystore`.