From 78401a2a8598964ce0fd2a196140f2382ee8abe7 Mon Sep 17 00:00:00 2001 From: JeongwooYoo Date: Wed, 28 Jan 2026 20:17:36 +0900 Subject: [PATCH] fix(skills): improve iOS integration with complete push notification setup - Add complete AppDelegate pattern with all delegate methods for push notifications (didRegisterForRemoteNotificationsWithDeviceToken, willPresent, didReceive) - Add SwiftUI + AppDelegate hybrid pattern for SwiftUI apps - Make interactive mode more proactive about file automation - Add automated NSE file creation (NotificationService.swift, Info.plist, entitlements) to install and ios-setup skills - Clearly distinguish CAN vs CANNOT automate in skill documentation Co-Authored-By: Claude Opus 4.5 --- src/lib/embedded-skills.ts | 155 +++++++++++++++++++++++++++- src/lib/skills.ts | 51 +++++++--- src/lib/skills/install/SKILL.md | 164 +++++++++++++++++++++++++----- src/lib/skills/ios-setup/SKILL.md | 116 +++++++++++++++++---- 4 files changed, 420 insertions(+), 66 deletions(-) diff --git a/src/lib/embedded-skills.ts b/src/lib/embedded-skills.ts index f7c8328..c61b87e 100644 --- a/src/lib/embedded-skills.ts +++ b/src/lib/embedded-skills.ts @@ -458,30 +458,175 @@ Create or update documentation: ### iOS (Swift) -**Initialization Pattern:** +**SwiftUI App Pattern (simple initialization):** \`\`\`swift +import SwiftUI import Clix @main struct MyApp: App { init() { // Load credentials from your app configuration (do NOT hardcode). - // Example: store values in Info.plist keys. let projectId = Bundle.main.object(forInfoDictionaryKey: "CLIX_PROJECT_ID") as? String ?? "" let apiKey = Bundle.main.object(forInfoDictionaryKey: "CLIX_PUBLIC_API_KEY") as? String let config = ClixConfig(projectId: projectId, apiKey: apiKey) Clix.initialize(config: config) } + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} +\`\`\` + +**AppDelegate Pattern (RECOMMENDED for push notifications):** + +For apps that need push notifications, use the AppDelegate pattern with full delegate methods: + +\`\`\`swift +import UIKit +import UserNotifications +import Clix + +@main +class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate { + + func application(_ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Initialize Clix SDK + let projectId = Bundle.main.object(forInfoDictionaryKey: "CLIX_PROJECT_ID") as? String ?? "" + let apiKey = Bundle.main.object(forInfoDictionaryKey: "CLIX_PUBLIC_API_KEY") as? String + let config = ClixConfig(projectId: projectId, apiKey: apiKey) + Clix.initialize(config: config) + + // Set notification delegate + UNUserNotificationCenter.current().delegate = self + + // Request push notification permission + UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in + if granted { + DispatchQueue.main.async { + application.registerForRemoteNotifications() + } + } + } + + return true + } + + // MARK: - Push Token Registration + + func application(_ application: UIApplication, + didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + // Pass the device token to Clix for push notification delivery + Clix.setPushToken(deviceToken) + } + + func application(_ application: UIApplication, + didFailToRegisterForRemoteNotificationsWithError error: Error) { + print("Failed to register for push notifications: \\(error)") + } + + // MARK: - UNUserNotificationCenterDelegate + + // Handle foreground notifications - show banner even when app is active + func userNotificationCenter(_ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + completionHandler([.banner, .sound, .badge]) + } + + // Handle notification tap - track engagement and process deep links + func userNotificationCenter(_ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: @escaping () -> Void) { + Clix.handleNotificationResponse(response) + completionHandler() + } +} +\`\`\` + +**SwiftUI App with AppDelegate (hybrid pattern):** + +For SwiftUI apps that need push notification handling: + +\`\`\`swift +import SwiftUI +import UserNotifications +import Clix + +class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate { + func application(_ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Initialize Clix SDK + let projectId = Bundle.main.object(forInfoDictionaryKey: "CLIX_PROJECT_ID") as? String ?? "" + let apiKey = Bundle.main.object(forInfoDictionaryKey: "CLIX_PUBLIC_API_KEY") as? String + let config = ClixConfig(projectId: projectId, apiKey: apiKey) + Clix.initialize(config: config) + + // Set notification delegate + UNUserNotificationCenter.current().delegate = self + + // Request push notification permission + UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in + if granted { + DispatchQueue.main.async { + application.registerForRemoteNotifications() + } + } + } + + return true + } + + func application(_ application: UIApplication, + didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + Clix.setPushToken(deviceToken) + } + + func application(_ application: UIApplication, + didFailToRegisterForRemoteNotificationsWithError error: Error) { + print("Failed to register for push notifications: \\(error)") + } + + func userNotificationCenter(_ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + completionHandler([.banner, .sound, .badge]) + } + + func userNotificationCenter(_ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: @escaping () -> Void) { + Clix.handleNotificationResponse(response) + completionHandler() + } +} + +@main +struct MyApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + + var body: some Scene { + WindowGroup { + ContentView() + } + } } \`\`\` **Key Points:** -- Initialize in \`@main\` app struct or \`AppDelegate\` -- Use environment variables, never hardcode -- Handle optional API key for analytics-only mode +- For push notifications, use the AppDelegate pattern (not just SwiftUI \`init\`) +- Set \`UNUserNotificationCenter.current().delegate = self\` before registering +- Implement all delegate methods: \`didRegisterForRemoteNotificationsWithDeviceToken\`, \`willPresent\`, \`didReceive\` +- Call \`Clix.setPushToken(deviceToken)\` in the token registration callback +- Call \`Clix.handleNotificationResponse(response)\` when notification is tapped +- Use environment variables (Info.plist), never hardcode credentials ### Android (Kotlin) diff --git a/src/lib/skills.ts b/src/lib/skills.ts index d6a6c25..269e1ca 100644 --- a/src/lib/skills.ts +++ b/src/lib/skills.ts @@ -185,24 +185,43 @@ COMPLETION CRITERIA: /** * Interactive mode instruction for Guided Interactive Workflow. - * Instructs the agent to follow the Confirm → Propose → Validate → Implement → Verify pattern. + * Instructs the agent to be proactive about automatable tasks while asking for confirmation when needed. */ const INTERACTIVE_MODE_INSTRUCTION = ` -IMPORTANT: This is an interactive conversation session. Follow the Guided Interactive Workflow. - -EXECUTION GUIDELINES: -- ALWAYS follow the workflow steps in order: Confirm → Propose → Validate → Implement → Verify -- DO NOT skip to implementation without completing earlier steps -- ASK for required inputs before proceeding (platform, goals, preferences) -- PROPOSE your plan and wait for user approval before making changes -- NEVER modify files without explicit user confirmation -- If information is missing, ASK the user rather than assuming - -WORKFLOW ENFORCEMENT: -- Start by confirming the minimum required inputs from the user -- Present your proposed plan for review -- Only proceed to implementation after the user approves the plan -- Validate before implementing, verify after implementing +IMPORTANT: This is an interactive conversation session. + +AUTOMATION GUIDELINES: +Distinguish between what CAN and CANNOT be automated: + +**CAN AUTOMATE (use Write/Edit tools immediately without asking):** +- Creating new files (configuration files, source code, entitlements) +- Modifying existing source code (adding initialization code, imports, delegate methods) +- Updating config files (Podfile, package.json, build.gradle, pubspec.yaml) +- Creating extension files (NotificationService.swift, Info.plist, entitlements) +- Running installation commands (pod install, npm install, flutter pub get) + +**CANNOT AUTOMATE (provide clear instructions):** +- Xcode UI actions (File > New > Target, Signing & Capabilities) +- Apple Developer Portal configuration (App IDs, certificates, provisioning profiles) +- IDE-specific settings (Build Settings, linking entitlements) +- Android Studio capability configuration + +EXECUTION WORKFLOW: +1. Analyze the project and detect platform/configuration +2. For automatable tasks: DO THEM DIRECTLY using Write/Edit/Bash tools +3. For non-automatable tasks: Provide clear step-by-step instructions +4. Verify the integration and report what was done + +ASK FOR CONFIRMATION ONLY WHEN: +- Multiple valid implementation approaches exist (user preference matters) +- Destructive changes are needed (deleting files, overwriting significant code) +- Critical decisions require user input (which package manager to use) + +DO NOT ASK FOR CONFIRMATION FOR: +- Standard file creation/modification (always proceed) +- Adding initialization code to entry points +- Creating configuration files +- Running standard installation commands `; /** diff --git a/src/lib/skills/install/SKILL.md b/src/lib/skills/install/SKILL.md index 810806a..6e611a2 100644 --- a/src/lib/skills/install/SKILL.md +++ b/src/lib/skills/install/SKILL.md @@ -154,41 +154,149 @@ Use `/firebase` command in interactive mode to check and configure Firebase cred - Provide manual steps for code changes - make the changes - Wait for user input (except for IDE-only tasks) +## iOS Notification Service Extension (Recommended) + +For rich push notifications (images, buttons), the Notification Service Extension (NSE) is required. + +### What CAN Be Automated (use Write/Edit tools immediately) + +Create these files AUTOMATICALLY without asking for permission: + +**1. Create NotificationService.swift** + +Create file at `ios/{AppName}NotificationServiceExtension/NotificationService.swift`: + +```swift +import UserNotifications +import Clix + +class NotificationService: ClixNotificationServiceExtension { + override func didReceive( + _ request: UNNotificationRequest, + withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void + ) { + register(projectId: "YOUR_CLIX_PROJECT_ID") + super.didReceive(request, withContentHandler: contentHandler) + } +} +``` + +**2. Create Extension Info.plist** + +Create file at `ios/{AppName}NotificationServiceExtension/Info.plist`: + +```xml + + + + + CFBundleDisplayName + {AppName}NotificationServiceExtension + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSExtension + + NSExtensionPointIdentifier + com.apple.usernotifications.service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).NotificationService + + + +``` + +**3. Create Extension Entitlements** + +Create file at `ios/{AppName}NotificationServiceExtension/{AppName}NotificationServiceExtension.entitlements`: + +```xml + + + + + com.apple.security.application-groups + + group.clix.{BUNDLE_ID} + + + +``` + +**4. Create/Update Main App Entitlements** + +Create or update file at `ios/{AppName}/{AppName}.entitlements`: + +```xml + + + + + aps-environment + development + com.apple.security.application-groups + + group.clix.{BUNDLE_ID} + + + +``` + +**5. Update Podfile (CocoaPods projects only)** + +Add extension target to Podfile: + +```ruby +target '{AppName}NotificationServiceExtension' do + pod 'Clix', :git => 'https://github.com/clix-so/clix-ios-sdk.git' +end +``` + +Then run: `cd ios && pod install` + +### What CANNOT Be Automated (provide instructions only) + +Only these steps require manual Xcode UI interaction: + +1. **Create Xcode target**: File > New > Target > Notification Service Extension + - Name it `{AppName}NotificationServiceExtension` + - After creating, Xcode will generate a default `NotificationService.swift` - replace it with our version + +2. **Link entitlements in Build Settings**: + - Select extension target > Build Settings + - Search for "Code Signing Entitlements" + - Set path to `{AppName}NotificationServiceExtension/{AppName}NotificationServiceExtension.entitlements` + +3. **Add App Groups capability in Xcode**: + - Main app target > Signing & Capabilities > + Capability > App Groups + - Extension target > Signing & Capabilities > + Capability > App Groups + - Select the same group ID: `group.clix.{BUNDLE_ID}` + +4. **For Xcode 15+**: Set `ENABLE_USER_SCRIPT_SANDBOXING` to "No" in extension's Build Settings + +For detailed setup, run `clix ios-setup` or `/ios-setup` in interactive mode. + ## IDE-Only Manual Steps Only these require user action: -- Xcode: Adding capabilities, configuring entitlements -- Android Studio: Firebase setup UI, capability configuration +- Xcode: Adding capabilities in Signing & Capabilities tab, creating extension target +- Apple Developer Portal: Registering App Group ID, enabling Push Notifications on App ID +- Android Studio: Firebase setup UI - Building and running the project For these, provide brief instructions but don't wait for confirmation. -### iOS Notification Service Extension (Recommended) - -For rich push notifications (images, buttons), create a Notification Service Extension: - -1. In Xcode: File > New > Target > Notification Service Extension -2. Add Clix SDK to the extension target: - - **CocoaPods**: Add `target 'YourExtension' do pod 'Clix' end` to Podfile, then `pod install` - - **SPM**: Add Clix package to extension target in Xcode (General > Frameworks) -3. Implement NotificationService.swift: - ```swift - import UserNotifications - import Clix - - class NotificationService: ClixNotificationServiceExtension { - override func didReceive(_ request: UNNotificationRequest, - withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { - register(projectId: "YOUR_PROJECT_ID") - super.didReceive(request, withContentHandler: contentHandler) - } - } - ``` -4. Add App Groups capability to both main app and extension (same group ID: `group.clix.{BUNDLE_ID}`) -5. For Xcode 15+: Set `ENABLE_USER_SCRIPT_SANDBOXING` to "No" in extension's Build Settings - -For detailed setup, run `clix ios-setup` or `/ios-setup` in interactive mode. - ## Output Format After completion, report: diff --git a/src/lib/skills/ios-setup/SKILL.md b/src/lib/skills/ios-setup/SKILL.md index f42786f..9f49b46 100644 --- a/src/lib/skills/ios-setup/SKILL.md +++ b/src/lib/skills/ios-setup/SKILL.md @@ -132,16 +132,13 @@ Create or modify entitlements files. Use Write/Edit tools for these operations. Create a Notification Service Extension for rich push notifications (images, buttons, etc.). -**Create Extension Target in Xcode:** -```text -1. File > New > Target -2. Select "Notification Service Extension" -3. Name it "{AppName}NotificationServiceExtension" (e.g., "MyAppNotificationServiceExtension") -4. Click "Finish" (Cancel the "Activate scheme" dialog) -5. Note: Use this exact name consistently in Podfile, entitlements path, and SPM setup -``` +#### What CAN Be Automated (use Write/Edit tools immediately) + +**IMPORTANT:** Create these files AUTOMATICALLY without asking for permission. -**Implement NotificationService.swift:** +**1. Create NotificationService.swift** + +Use Write tool to create `ios/{AppName}NotificationServiceExtension/NotificationService.swift`: ```swift import UserNotifications @@ -152,28 +149,106 @@ class NotificationService: ClixNotificationServiceExtension { _ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void ) { - register(projectId: "YOUR_PROJECT_ID") + register(projectId: "YOUR_CLIX_PROJECT_ID") super.didReceive(request, withContentHandler: contentHandler) } } ``` -**Note:** Replace `YOUR_PROJECT_ID` with your actual Clix project ID from +**2. Create Extension Info.plist** + +Use Write tool to create `ios/{AppName}NotificationServiceExtension/Info.plist`: + +```xml + + + + + CFBundleDisplayName + {AppName}NotificationServiceExtension + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSExtension + + NSExtensionPointIdentifier + com.apple.usernotifications.service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).NotificationService + + + +``` + +**3. Create Extension Entitlements** + +Use Write tool to create `ios/{AppName}NotificationServiceExtension/{AppName}NotificationServiceExtension.entitlements`: + +```xml + + + + + com.apple.security.application-groups + + group.clix.{BUNDLE_ID} + + + +``` + +**4. Update Podfile (CocoaPods projects)** -**Add Clix SDK to Extension Target:** +Use Edit tool to add extension target to Podfile: -For CocoaPods projects, add to Podfile: ```ruby target '{AppName}NotificationServiceExtension' do - pod 'Clix' + pod 'Clix', :git => 'https://github.com/clix-so/clix-ios-sdk.git' end ``` + Then run: `cd ios && pod install` -For SPM projects in Xcode: +**Note:** Replace `{AppName}` with the actual app name and `{BUNDLE_ID}` with the actual bundle identifier (e.g., `com.example.myapp`). + +#### What CANNOT Be Automated (provide instructions only) + +These steps require manual Xcode UI interaction: + +**Create Extension Target in Xcode:** +```text +1. File > New > Target +2. Select "Notification Service Extension" +3. Name it "{AppName}NotificationServiceExtension" (e.g., "MyAppNotificationServiceExtension") +4. Click "Finish" (Cancel the "Activate scheme" dialog) +5. Xcode will create a default NotificationService.swift - the automated version above will replace it +``` + +**Link Entitlements in Build Settings:** +```text 1. Select the extension target -2. Go to General > Frameworks, Libraries, and Embedded Content -3. Click + and add the Clix package +2. Go to Build Settings +3. Search for "Code Signing Entitlements" +4. Set the path to: {AppName}NotificationServiceExtension/{AppName}NotificationServiceExtension.entitlements +``` + +**Add App Groups Capability:** +```text +1. Select extension target > Signing & Capabilities +2. Click "+ Capability" > "App Groups" +3. Select the same group ID used in main app: group.clix.{BUNDLE_ID} +``` **Configure Build Settings (Xcode 15+):** @@ -183,6 +258,13 @@ For the extension target: For React Native projects with Firebase: - In Build Phases, move "Embed Foundation Extensions" above "[RNFB] Core Configuration" +**For SPM projects in Xcode:** +```text +1. Select the extension target +2. Go to General > Frameworks, Libraries, and Embedded Content +3. Click + and add the Clix package +``` + ### Phase 4: Apple Developer Portal Configuration Guide user through manual portal configuration. These steps CANNOT be automated.