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.
- package/lib/commands/add.js +93 -80
- package/lib/commands/configure.js +100 -32
- package/lib/commands/doctor.js +28 -2
- package/lib/commands/new.js +80 -37
- package/lib/commands/notifications.js +1 -1
- package/lib/commands/remove.js +43 -15
- package/lib/commands/run.js +2 -2
- package/lib/commands/update.js +2 -2
- package/lib/scaffold/CHANGELOG.json +14 -0
- package/lib/scaffold/backends/api/generator.js +14 -14
- package/lib/scaffold/backends/api/patch/README.md +83 -0
- package/lib/scaffold/backends/api/patch/lib/core/data/api/storage_api.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/core/data/entities/user_entity.dart +5 -0
- package/lib/scaffold/backends/api/patch/lib/{environnements.dart → environments.dart} +3 -11
- package/lib/scaffold/backends/api/patch/lib/features/ai_chat/api/ai_chat_api.dart +108 -0
- package/lib/scaffold/backends/api/patch/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +51 -0
- 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
- package/lib/scaffold/backends/api/patch/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +71 -38
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +2 -2
- package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_request_api.dart +54 -0
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/models/user_info.dart +16 -16
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
- package/lib/scaffold/backends/api/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +100 -0
- package/lib/scaffold/backends/api/patch/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +13 -0
- package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/stripe_backend_api.dart +60 -0
- package/lib/scaffold/backends/api/patch/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
- package/lib/scaffold/backends/api/pubspec.yaml.tpl +4 -5
- package/lib/scaffold/backends/firebase/deploy.js +87 -13
- package/lib/scaffold/backends/firebase/generator.js +5 -5
- package/lib/scaffold/backends/firebase/tokens.js +4 -4
- package/lib/scaffold/backends/supabase/deploy.js +63 -11
- package/lib/scaffold/backends/supabase/edge-functions/admin-list-users/index.ts +149 -0
- package/lib/scaffold/backends/supabase/edge-functions/{llm-chat → ai-chat}/index.ts +19 -19
- package/lib/scaffold/backends/supabase/edge-functions/revenuecat-webhook/index.ts +2 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-create-checkout-session/index.ts +123 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-create-portal-session/index.ts +97 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-list-prices/index.ts +83 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-webhook/index.ts +138 -0
- package/lib/scaffold/backends/supabase/generator.js +17 -17
- package/lib/scaffold/backends/supabase/migrations/20240101000009_ai_messages.sql +50 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000012_stripe_customers.sql +36 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000013_admin_role.sql +62 -0
- package/lib/scaffold/backends/supabase/patch/lib/core/data/entities/user_entity.dart +4 -0
- package/lib/scaffold/backends/supabase/patch/lib/{environnements.dart → environments.dart} +3 -13
- package/lib/scaffold/backends/supabase/patch/lib/features/ai_chat/api/ai_chat_api.dart +95 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +52 -0
- 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
- package/lib/scaffold/backends/supabase/patch/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +63 -35
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +1 -1
- package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/api/feature_request_api.dart +46 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/models/user_info.dart +16 -16
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
- package/lib/scaffold/backends/supabase/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +93 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +13 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/stripe_backend_api.dart +52 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
- package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +4 -5
- package/lib/scaffold/backends/supabase/tokens.js +3 -3
- package/lib/scaffold/catalog.js +9 -11
- package/lib/scaffold/generate.js +45 -31
- package/lib/scaffold/shared/generator-utils.js +188 -81
- package/lib/scaffold/shared/sort-imports.js +191 -0
- package/lib/scaffold/shared/template-strings.js +3 -3
- package/lib/utils/checks.js +22 -6
- package/lib/utils/env-tools.js +7 -0
- package/lib/utils/flutter-install.js +114 -0
- package/lib/utils/i18n/messages-en.js +52 -35
- package/lib/utils/i18n/messages-es.js +52 -35
- package/lib/utils/i18n/messages-pt.js +54 -37
- package/lib/utils/updates.js +15 -15
- package/package.json +1 -1
- package/templates/firebase/.env.example +2 -2
- package/templates/firebase/android/app/src/main/res/drawable/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-v21/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-v21/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +1 -1
- package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +1 -1
- package/templates/firebase/assets/images/logo_wordmark_dark.png +0 -0
- package/templates/firebase/assets/images/logo_wordmark_light.png +0 -0
- package/templates/firebase/docs/revenuecat-setup.es.md +1 -1
- package/templates/firebase/docs/revenuecat-setup.pt.md +1 -1
- package/templates/firebase/firestore.rules +24 -5
- package/templates/firebase/functions/package-lock.json +22 -1
- package/templates/firebase/functions/package.json +2 -1
- package/templates/firebase/functions/src/admin/functions.ts +113 -0
- package/templates/firebase/functions/src/{llm_chat → ai_chat}/index.ts +16 -16
- package/templates/firebase/functions/src/index.ts +8 -2
- package/templates/firebase/functions/src/notifications/device_triggers.ts +2 -2
- package/templates/firebase/functions/src/notifications/triggers.ts +3 -3
- package/templates/firebase/functions/src/subscriptions/models/subscription_status.ts +2 -0
- package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +222 -0
- package/templates/firebase/functions/src/subscriptions/subscriptions_functions.ts +2 -2
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png +0 -0
- package/templates/firebase/lib/components/components.dart +4 -1
- package/templates/firebase/lib/components/kasy_app_bar.dart +22 -7
- package/templates/firebase/lib/components/kasy_avatar.dart +7 -6
- package/templates/firebase/lib/components/kasy_button.dart +23 -99
- package/templates/firebase/lib/components/kasy_dialog.dart +11 -11
- package/templates/firebase/lib/components/kasy_image_viewer.dart +84 -0
- package/templates/firebase/lib/components/{kasy_sidebar_pro.dart → kasy_sidebar.dart} +702 -425
- package/templates/firebase/lib/components/kasy_status_tag.dart +91 -0
- package/templates/firebase/lib/components/kasy_text_area.dart +1 -3
- package/templates/firebase/lib/components/kasy_text_field.dart +5 -6
- package/templates/firebase/lib/components/kasy_text_field_otp.dart +1 -3
- package/templates/firebase/lib/components/kasy_toast.dart +2 -2
- package/templates/firebase/lib/components/kasy_web_header.dart +209 -0
- package/templates/firebase/lib/core/ads/ads_provider.dart +1 -1
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +136 -23
- package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +19 -91
- package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +196 -96
- package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +54 -0
- package/templates/firebase/lib/core/config/app_env.dart +5 -11
- package/templates/firebase/lib/core/config/features.dart +5 -4
- package/templates/firebase/lib/core/data/api/analytics_api.dart +1 -1
- package/templates/firebase/lib/core/data/api/http_client.dart +1 -1
- package/templates/firebase/lib/core/data/entities/user_entity.dart +3 -0
- package/templates/firebase/lib/core/data/models/entitlement.dart +35 -0
- package/templates/firebase/lib/core/data/models/subscription.dart +13 -186
- package/templates/firebase/lib/core/data/models/user.dart +11 -0
- package/templates/firebase/lib/core/data/repositories/user_repository.dart +1 -1
- package/templates/firebase/lib/core/home_widgets/home_widget_background_task.dart +1 -1
- package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +1 -1
- package/templates/firebase/lib/core/icons/kasy_icons.dart +31 -1
- package/templates/firebase/lib/core/rating/api/rating_api.dart +2 -2
- package/templates/firebase/lib/core/states/logout_action.dart +25 -0
- package/templates/firebase/lib/core/states/user_state_notifier.dart +3 -3
- package/templates/firebase/lib/core/theme/colors.dart +488 -188
- package/templates/firebase/lib/core/theme/radius.dart +22 -11
- package/templates/firebase/lib/core/theme/shadows.dart +66 -0
- package/templates/firebase/lib/core/theme/texts.dart +75 -41
- package/templates/firebase/lib/core/theme/universal_theme.dart +9 -4
- package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +5 -4
- package/templates/firebase/lib/core/web_viewport_scale.dart +52 -0
- package/templates/firebase/lib/core/widgets/kasy_brand_badge.dart +118 -0
- package/templates/firebase/lib/core/widgets/kasy_brand_logo.dart +40 -0
- package/templates/firebase/lib/core/widgets/kasy_focus_ring.dart +125 -0
- package/templates/firebase/lib/core/widgets/kasy_hover.dart +53 -14
- package/templates/firebase/lib/core/widgets/kasy_pressable_depth.dart +18 -13
- package/templates/firebase/lib/{environnements.dart → environments.dart} +3 -14
- package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +159 -0
- package/templates/firebase/lib/features/ai_chat/api/ai_chat_api.dart +107 -0
- package/templates/firebase/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +64 -0
- package/templates/firebase/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +6 -6
- package/templates/firebase/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +73 -38
- package/templates/firebase/lib/features/ai_chat/providers/ai_conversations_notifier.dart +103 -0
- package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_avatars.dart → ai_chat/ui/widgets/ai_chat_avatars.dart} +13 -13
- package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_composer.dart → ai_chat/ui/widgets/ai_chat_composer.dart} +10 -10
- package/templates/firebase/lib/features/{llm_chat/llm_chat_page.dart → ai_chat/ui/widgets/ai_chat_conversation_view.dart} +80 -67
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +285 -0
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +163 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api.dart +52 -13
- package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher.dart +12 -0
- package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher_web.dart +35 -0
- package/templates/firebase/lib/features/authentication/ui/recover_password_page.dart +108 -68
- package/templates/firebase/lib/features/authentication/ui/signin_page.dart +38 -51
- package/templates/firebase/lib/features/authentication/ui/signup_page.dart +38 -51
- package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +128 -0
- package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +61 -44
- package/templates/firebase/lib/features/feedbacks/api/feature_request_api.dart +32 -0
- package/templates/firebase/lib/features/feedbacks/models/feedback_state.dart +5 -5
- package/templates/firebase/lib/features/feedbacks/providers/feedback_page_notifier.dart +2 -2
- package/templates/firebase/lib/features/home/design_system_page.dart +808 -170
- package/templates/firebase/lib/features/home/home_components_page.dart +6 -3
- package/templates/firebase/lib/features/home/home_components_preview_page.dart +6 -6
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +325 -186
- package/templates/firebase/lib/features/home/home_feed.dart +289 -0
- package/templates/firebase/lib/features/home/home_image_grid.dart +355 -0
- package/templates/firebase/lib/features/home/home_page.dart +11 -250
- package/templates/firebase/lib/features/{local_reminder → local_reminders}/providers/reminder_notifier.dart +1 -1
- package/templates/firebase/lib/features/{local_reminder → local_reminders}/ui/reminder_page.dart +2 -2
- package/templates/firebase/lib/features/notifications/shared/att_permission.dart +1 -1
- package/templates/firebase/lib/features/notifications/ui/request_notification_permission.dart +25 -61
- package/templates/firebase/lib/features/notifications/ui/widgets/permission_request_view.dart +117 -0
- package/templates/firebase/lib/features/onboarding/models/user_info.dart +16 -16
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_features.dart +7 -9
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +71 -48
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +3 -2
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_questions.dart +5 -5
- package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +4 -4
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_background.dart +4 -2
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_feature.dart +39 -121
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +105 -70
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_module_mockups.dart +639 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_progress.dart +62 -50
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_step_header.dart +75 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_sticky_footer.dart +16 -5
- package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +26 -22
- package/templates/firebase/lib/features/settings/settings_page.dart +601 -90
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +1193 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_paywalls.dart +1 -1
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_routes.dart +2 -3
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_api.dart +86 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +1215 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +236 -0
- package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +3 -3
- package/templates/firebase/lib/features/settings/ui/widgets/kasy_user_avatar.dart +77 -0
- package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +1 -0
- package/templates/firebase/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +17 -0
- package/templates/firebase/lib/features/{subscription → subscriptions}/api/inapp_subscription_api.dart +67 -46
- package/templates/firebase/lib/features/subscriptions/api/revenuecat_product.dart +189 -0
- package/templates/firebase/lib/features/subscriptions/api/stripe_backend_api.dart +55 -0
- package/templates/firebase/lib/features/subscriptions/api/stripe_payment_api.dart +82 -0
- package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +178 -0
- package/templates/firebase/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
- package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api.dart +53 -0
- package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api_provider.dart +21 -0
- package/templates/firebase/lib/features/{subscription → subscriptions}/providers/premium_page_provider.dart +9 -6
- package/templates/firebase/lib/features/{subscription → subscriptions}/repositories/subscription_repository.dart +26 -30
- package/{lib/scaffold/backends/supabase/patch/lib/features/subscription → templates/firebase/lib/features/subscriptions}/shared/maybeshow_premium.dart +0 -2
- package/templates/firebase/lib/features/subscriptions/shared/subscription_management.dart +45 -0
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/active_premium_content.dart +28 -4
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_minimal.dart +7 -7
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_row.dart +8 -8
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_with_switch.dart +7 -7
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_content.dart +7 -7
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_page_factory.dart +5 -5
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/premium_page.dart +5 -5
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/comparison_table.dart +1 -1
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/paywall_empty_state.dart +4 -4
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_bottom_menu.dart +3 -4
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_col.dart +1 -1
- package/templates/firebase/lib/i18n/en.i18n.json +171 -46
- package/templates/firebase/lib/i18n/es.i18n.json +175 -50
- package/templates/firebase/lib/i18n/pt.i18n.json +166 -41
- package/templates/firebase/lib/main.dart +6 -3
- package/templates/firebase/lib/router.dart +15 -23
- package/templates/firebase/pubspec.yaml +5 -6
- package/templates/firebase/test/core/data/repositories/user_repository_test.dart +3 -3
- package/templates/firebase/test/core/states/user_state_notifier_test.dart +4 -4
- package/templates/firebase/test/core/widgets/focus_ring_shape_test.dart +55 -0
- package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_inapp_subscription_api.dart +11 -4
- package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_revenuecat_product.dart +1 -0
- package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_subscription_api.dart +2 -2
- package/templates/firebase/test/features/{subscription → subscriptions}/subscription_page_test.dart +4 -4
- package/templates/firebase/test/test_utils.dart +6 -6
- package/templates/firebase/web/index.html +5 -2
- package/lib/scaffold/backends/api/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -58
- package/lib/scaffold/backends/api/patch/lib/features/subscription/shared/maybeshow_premium.dart +0 -86
- package/lib/scaffold/backends/supabase/migrations/20240101000009_llm_messages.sql +0 -22
- package/lib/scaffold/backends/supabase/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -47
- package/templates/firebase/assets/images/onboarding/authentication-login-template.jpg +0 -0
- package/templates/firebase/assets/images/onboarding/img2.jpg +0 -0
- package/templates/firebase/assets/images/onboarding/img3.jpg +0 -0
- package/templates/firebase/assets/images/onboarding/notifications.png +0 -0
- package/templates/firebase/assets/images/onboarding/purchase.png +0 -0
- package/templates/firebase/lib/core/sidebar/kasy_sidebar.dart +0 -2021
- package/templates/firebase/lib/features/authentication/ui/widgets/auth_brand.dart +0 -35
- package/templates/firebase/lib/features/home/home_features_page.dart +0 -207
- package/templates/firebase/lib/features/llm_chat/api/llm_chat_api.dart +0 -50
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +0 -316
- package/templates/firebase/lib/features/subscription/shared/maybeshow_premium.dart +0 -85
- /package/templates/firebase/lib/features/{local_reminder → local_reminders}/repositories/reminder_preferences.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/providers/models/premium_state.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/feature_line.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background_gradient.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_banner.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_card.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_close_button.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_feature.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_row.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/trial_switcher.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/unsubscribe_feedback_popup.dart +0 -0
package/lib/commands/add.js
CHANGED
|
@@ -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
|
|
33
|
-
function
|
|
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.
|
|
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/
|
|
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/
|
|
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
|
|
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 '
|
|
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
|
-
|
|
312
|
-
promptKeys: ['
|
|
324
|
+
ai_chat: {
|
|
325
|
+
promptKeys: ['aiProvider', 'aiSystemPrompt', 'aiApiKey'],
|
|
313
326
|
defineUpdates: (a, kitSetup) => ({
|
|
314
|
-
|
|
327
|
+
AI_CHAT_ENDPOINT: resolveAiChatEndpoint(a, kitSetup),
|
|
315
328
|
}),
|
|
316
329
|
envLines: (a, kitSetup) => [
|
|
317
|
-
`
|
|
330
|
+
`AI_CHAT_ENDPOINT=${resolveAiChatEndpoint(a, kitSetup)}`,
|
|
318
331
|
],
|
|
319
|
-
featureFlag: '
|
|
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
|
-
|
|
382
|
-
message: t('add.prompt.
|
|
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
|
-
|
|
391
|
-
message: t('add.prompt.
|
|
403
|
+
aiSystemPrompt: (t, cancel) => ui.text({
|
|
404
|
+
message: t('add.prompt.aiSystemPrompt'),
|
|
392
405
|
onCancel: cancel,
|
|
393
406
|
}),
|
|
394
|
-
|
|
395
|
-
message: t('add.prompt.
|
|
407
|
+
aiApiKey: (t, cancel) => ui.password({
|
|
408
|
+
message: t('add.prompt.aiApiKey'),
|
|
396
409
|
onCancel: cancel,
|
|
397
410
|
}),
|
|
398
|
-
|
|
399
|
-
message: t('add.prompt.
|
|
411
|
+
aiEndpoint: (t, cancel) => ui.text({
|
|
412
|
+
message: t('add.prompt.aiEndpoint'),
|
|
400
413
|
onCancel: cancel,
|
|
401
414
|
}),
|
|
402
415
|
};
|
|
403
416
|
|
|
404
|
-
// ──
|
|
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
|
|
423
|
+
async function postAddAiChat(projectDir, kitSetup, answers, t) {
|
|
411
424
|
const backend = kitSetup?.backendProvider ?? 'firebase';
|
|
412
|
-
const apiKey = answers.
|
|
413
|
-
const provider = answers.
|
|
414
|
-
const systemPrompt = answers.
|
|
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 = [`
|
|
421
|
-
if (systemPrompt) lines.push(`
|
|
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
|
|
444
|
+
// NOT from local .env files — so AI_PROVIDER and AI_SYSTEM_PROMPT must be secrets too)
|
|
432
445
|
if (backend === 'supabase') {
|
|
433
|
-
// Copy
|
|
434
|
-
const edgeFnSrc = path.join(SUPABASE_EDGE_FUNCTIONS_SOURCE, '
|
|
435
|
-
const edgeFnDest = path.join(projectDir, 'supabase', 'functions', '
|
|
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.
|
|
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.
|
|
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(), `
|
|
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
|
|
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.
|
|
472
|
+
spinner.stop(t('add.ai_chat.secretSet'));
|
|
460
473
|
secretsOk = true;
|
|
461
474
|
} catch {
|
|
462
|
-
spinner.stop(`⚠ ${t('add.
|
|
463
|
-
ui.log.message(kleur.dim('Run manually: firebase functions:secrets:set
|
|
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.
|
|
468
|
-
// Set
|
|
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: '
|
|
473
|
-
{ name: '
|
|
474
|
-
...(systemPrompt ? [{ name: '
|
|
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.
|
|
498
|
+
spinner.stop(t('add.ai_chat.secretSet'));
|
|
486
499
|
secretsOk = true;
|
|
487
500
|
} else {
|
|
488
|
-
spinner.stop(`⚠ ${t('add.
|
|
489
|
-
ui.log.message(kleur.dim(`Run manually: supabase secrets set
|
|
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
|
|
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.
|
|
510
|
+
deploySpinner.start(t('add.ai_chat.deploying'));
|
|
498
511
|
try {
|
|
499
512
|
if (backend === 'firebase') {
|
|
500
|
-
await execAsync('firebase deploy --only functions:
|
|
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
|
|
515
|
+
await execAsync(`supabase functions deploy ai-chat --no-verify-jwt${refFlag}`, { cwd: projectDir, timeout: 180_000 });
|
|
503
516
|
}
|
|
504
|
-
deploySpinner.stop(t('add.
|
|
517
|
+
deploySpinner.stop(t('add.ai_chat.deployed'));
|
|
505
518
|
return { deployOk: true, deployAttempted: true };
|
|
506
519
|
} catch {
|
|
507
|
-
deploySpinner.stop(`⚠ ${t('add.
|
|
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:
|
|
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
|
|
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
|
-
//
|
|
639
|
-
if (normalized !== '
|
|
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.
|
|
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 ['
|
|
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.
|
|
668
|
+
answers.aiEndpoint = (await PROMPT_QUESTIONS.aiEndpoint(t, cancel)) || '';
|
|
656
669
|
}
|
|
657
670
|
}
|
|
658
|
-
const defines = MODULE_META.
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
688
|
+
nextStepsMsg = t(needsManualDeploy ? 'add.ai_chat.nextSteps.supabase.deployFailed' : 'add.ai_chat.nextSteps.supabase');
|
|
676
689
|
} else {
|
|
677
|
-
nextStepsMsg = t('add.
|
|
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
|
|
697
|
-
if (normalized === '
|
|
698
|
-
answers.
|
|
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', '
|
|
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.
|
|
808
|
-
let
|
|
809
|
-
if (normalized === '
|
|
810
|
-
|
|
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
|
-
//
|
|
819
|
-
if (normalized === '
|
|
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 =
|
|
834
|
+
const needsManualDeploy = aiDeployResult?.deployAttempted && !aiDeployResult?.deployOk;
|
|
822
835
|
let nextStepsMsg;
|
|
823
836
|
if (backend === 'firebase') {
|
|
824
|
-
nextStepsMsg = t(needsManualDeploy ? 'add.
|
|
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.
|
|
839
|
+
nextStepsMsg = t(needsManualDeploy ? 'add.ai_chat.nextSteps.supabase.deployFailed' : 'add.ai_chat.nextSteps.supabase');
|
|
827
840
|
} else {
|
|
828
|
-
nextStepsMsg = t('add.
|
|
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: '
|
|
159
|
-
titleKey: 'configure.section.
|
|
160
|
-
isActive: (kit, ctx) => !!ctx.
|
|
184
|
+
id: 'ai_chat',
|
|
185
|
+
titleKey: 'configure.section.aiChat',
|
|
186
|
+
isActive: (kit, ctx) => !!ctx.hasAiChat,
|
|
161
187
|
fields: [
|
|
162
188
|
{
|
|
163
|
-
key: '
|
|
189
|
+
key: 'AI_PROVIDER',
|
|
164
190
|
destination: 'functionsEnv',
|
|
165
|
-
label: '
|
|
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: '
|
|
195
|
+
key: 'AI_SYSTEM_PROMPT',
|
|
170
196
|
destination: 'functionsEnv',
|
|
171
|
-
label: '
|
|
197
|
+
label: 'AI system prompt (instructions)',
|
|
172
198
|
validate: () => undefined,
|
|
173
199
|
},
|
|
174
200
|
{
|
|
175
|
-
key: '
|
|
201
|
+
key: 'AI_API_KEY',
|
|
176
202
|
destination: 'firebaseSecret',
|
|
177
|
-
label: '
|
|
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 = {
|
|
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
|
|
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 (
|
|
407
|
-
else if (
|
|
408
|
-
else if (
|
|
409
|
-
else if (
|
|
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 =
|
|
457
|
-
:
|
|
458
|
-
:
|
|
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 (
|
|
480
|
-
else if (
|
|
481
|
-
else if (
|
|
482
|
-
else if (
|
|
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 })}`));
|