@layers/amba 1.0.1 → 4.0.2

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.
@@ -0,0 +1,400 @@
1
+ # Engagement
2
+
3
+ Everything that brings a user back to the app: push notifications + campaigns, segments (rule-based user cohorts), content libraries (daily quotes, lessons, tips with scheduled rotation), onboarding flows, deep links, referrals, and tracked links. The SDK side is mostly read-and-call (`Amba.push.register`, `Amba.content.today`, `Amba.onboarding.nextStep`, `Amba.referrals.claimReferral`); the provisioning side — the part you do — lives behind MCP tools.
4
+
5
+ ## MCP tools
6
+
7
+ ### Push
8
+
9
+ | Tool | Purpose | Example args |
10
+ | --- | --- | --- |
11
+ | `amba_push_campaigns_create` | Create a draft push campaign (title + body + optional segment + optional schedule). | `{ project_id, title: "Don't break your streak!", body: "Log your workout to keep the fire alive.", name: "streak_reminder", segment_id: "seg_active" }` |
12
+ | `amba_push_send_test` | Send a one-off push to a single app_user. Use this as your wire-verify after registering a token. | `{ project_id, user_id, title: "Test", body: "Wired up." }` |
13
+ | `amba_push_campaigns_send` / `amba_send_push_campaign` | Send (or schedule) a draft campaign. | `{ project_id, campaign_id }` |
14
+ | `amba_push_list_campaigns` | List campaigns. | `{ project_id }` |
15
+ | `amba_push_get_campaign` | Read one campaign + its delivery stats. | `{ project_id, campaign_id }` |
16
+ | `amba_push_update_campaign` | Edit a draft. | `{ project_id, campaign_id, title, body, scheduled_at }` |
17
+ | `amba_push_delete_campaign` | Remove a draft / cancel a scheduled campaign. | `{ project_id, campaign_id }` |
18
+
19
+ ### Segments
20
+
21
+ | Tool | Purpose | Example args |
22
+ | --- | --- | --- |
23
+ | `amba_segments_create` / `amba_create_segment` | Create a rule-based user segment. | `{ project_id, name: "Power Users", rules: { all: [{ field: "events.workout_completed.count_7d", op: ">=", value: 5 }] } }` |
24
+ | `amba_segments_list` / `amba_list_segments` | List all segments (system + custom). | `{ project_id }` |
25
+ | `amba_segments_get` | Get one segment by id. | `{ project_id, segment_id }` |
26
+ | `amba_segments_evaluate` | Materialize the segment — returns the set of matching user_ids. | `{ project_id, segment_id }` |
27
+ | `amba_segments_patch` | Edit rules / name / description. | `{ project_id, segment_id, rules: {...} }` |
28
+ | `amba_segments_delete` | Drop a segment. | `{ project_id, segment_id }` |
29
+
30
+ ### Content libraries
31
+
32
+ | Tool | Purpose | Example args |
33
+ | --- | --- | --- |
34
+ | `amba_content_libraries_create` / `amba_create_content_library` | Create a library (daily tips, lessons, quotes). | `{ project_id, name: "Daily Tips", description: "Workout motivation served daily" }` |
35
+ | `amba_content_items_add` / `amba_add_content_items` | Bulk-add items to a library. | `{ project_id, library_id, items: [{ body: "Tip 1" }, { body: "Tip 2" }, ...] }` |
36
+ | `amba_content_bulk_import` | Same as `_items_add` but supports CSV/JSON payloads. | `{ project_id, library_id, format: "json", items: [...] }` |
37
+ | `amba_content_list_libraries` | List libraries in this project. | `{ project_id }` |
38
+ | `amba_content_list_items` | List items in one library. | `{ project_id, library_id, limit: 100 }` |
39
+ | `amba_content_update_item` | Edit one item. | `{ project_id, library_id, item_id, body: "…" }` |
40
+ | `amba_content_delete_item` | Drop an item. | `{ project_id, library_id, item_id }` |
41
+ | `amba_content_schedules_create` / `amba_create_content_schedule` | Schedule a library for daily / weekly / random delivery. | `{ project_id, library_id, name: "Daily rotation", schedule_type: "daily_rotation" }` |
42
+ | `amba_content_list_schedules` | List schedules. | `{ project_id, library_id }` |
43
+ | `amba_content_update_schedule` | Edit a schedule's type or config. | `{ project_id, library_id, schedule_id, schedule_type: "weekly" }` |
44
+ | `amba_content_delete_schedule` | Drop a schedule. | `{ project_id, library_id, schedule_id }` |
45
+
46
+ The worked example (also captured inside the `amba_create_content_schedule` tool description):
47
+
48
+ ```
49
+ 1. amba_content_libraries_create({ project_id, name: "Daily Tips" })
50
+ 2. amba_content_items_add({ project_id, library_id, items: [{ body: "Tip 1" }, ...] })
51
+ 3. amba_content_schedules_create({ project_id, library_id, name: "Daily rotation", schedule_type: "daily_rotation" })
52
+ 4. In-app: const today = await Amba.content.today("Daily Tips");
53
+ ```
54
+
55
+ ### Onboarding flows
56
+
57
+ | Tool | Purpose | Example args |
58
+ | --- | --- | --- |
59
+ | `amba_onboarding_create` / `amba_create_onboarding_flow` | Create an onboarding flow definition (ordered steps). | `{ project_id, name: "New user", steps: [{ key: "welcome", type: "screen" }, { key: "goal", type: "question", options: ["lose_weight", "build_muscle", "stay_active"] }, { key: "notif_permission", type: "permission_prompt" }] }` |
60
+ | `amba_onboarding_list` | List flows. | `{ project_id }` |
61
+ | `amba_onboarding_get` | Read one flow. | `{ project_id, flow_id }` |
62
+ | `amba_onboarding_update` | Edit steps. | `{ project_id, flow_id, steps: [...] }` |
63
+ | `amba_onboarding_get_stats` / `amba_get_onboarding_stats` | Funnel stats — step-by-step completion + drop-off rates. | `{ project_id, flow_id }` |
64
+ | `amba_onboarding_delete` | Drop a flow. | `{ project_id, flow_id }` |
65
+
66
+ ### Deep links
67
+
68
+ | Tool | Purpose | Example args |
69
+ | --- | --- | --- |
70
+ | `amba_deeplinks_set_config` | Set the project's deep-link config (custom scheme, universal-link domains, fallback URL). | `{ project_id, scheme: "myapp", universal_links: ["myapp.com"], fallback_url: "https://myapp.com/get" }` |
71
+ | `amba_deeplinks_get_config` | Read the config. | `{ project_id }` |
72
+ | `amba_deeplinks_list` | List existing deep-link records. | `{ project_id }` |
73
+ | `amba_deeplinks_delete` | Drop a deep-link record. | `{ project_id, deeplink_id }` |
74
+
75
+ ### Referrals
76
+
77
+ | Tool | Purpose | Example args |
78
+ | --- | --- | --- |
79
+ | `amba_referrals_create` / `amba_create_referral_program` | Create a referral program (per-program rewards on both sides). | `{ project_id, name: "Invite a friend", referrer_reward: { currency: "gems", amount: 100 }, referee_reward: { currency: "gems", amount: 50 } }` |
80
+ | `amba_referrals_list` | List programs. | `{ project_id }` |
81
+ | `amba_referrals_patch` | Edit rewards / program name. | `{ project_id, program_id, referrer_reward: {...} }` |
82
+ | `amba_referrals_get_stats` / `amba_get_referral_stats` | Per-program acquisition stats. | `{ project_id, program_id }` |
83
+ | `amba_referrals_delete` | Drop a program. | `{ project_id, program_id }` |
84
+
85
+ ### Tracked links
86
+
87
+ | Tool | Purpose | Example args |
88
+ | --- | --- | --- |
89
+ | `amba_tracked_links_create` / `amba_create_tracked_link` | Create a short tracked link that resolves through Amba and records the click. | `{ project_id, name: "Twitter campaign", destination: "https://myapp.com", utm_source: "twitter" }` |
90
+ | `amba_tracked_links_get_stats` / `amba_get_link_stats` | Click / conversion stats for a link. | `{ project_id, link_id }` |
91
+
92
+ ## SDK init per stack
93
+
94
+ For all stacks: `Amba.configure(...)` must run before any of the calls below — wire it once at app start (see `identity.md`). The snippets below show only the engagement-specific bits.
95
+
96
+ ### Expo
97
+
98
+ ```bash
99
+ npx expo install @layers/amba-expo expo-notifications expo-device
100
+ ```
101
+
102
+ Push registration:
103
+
104
+ ```tsx
105
+ import * as Notifications from 'expo-notifications';
106
+ import * as Device from 'expo-device';
107
+ import { Platform } from 'react-native';
108
+ import { Amba } from '@layers/amba-expo';
109
+
110
+ async function registerForPush() {
111
+ if (!Device.isDevice) return; // simulators can't get a real token
112
+ const { status: existingStatus } = await Notifications.getPermissionsAsync();
113
+ let finalStatus = existingStatus;
114
+ if (existingStatus !== 'granted') {
115
+ const { status } = await Notifications.requestPermissionsAsync();
116
+ finalStatus = status;
117
+ }
118
+ if (finalStatus !== 'granted') return;
119
+
120
+ const { data: token } = await Notifications.getDevicePushTokenAsync();
121
+ await Amba.push.register(token, Platform.OS === 'ios' ? 'apns' : 'fcm');
122
+ }
123
+ ```
124
+
125
+ Content (daily tip):
126
+
127
+ ```tsx
128
+ import { Amba } from '@layers/amba-expo';
129
+
130
+ const todayTip = await Amba.content.today('Daily Tips');
131
+ // todayTip may be null if the schedule hasn't rotated in an item yet
132
+ console.log(todayTip?.body);
133
+ ```
134
+
135
+ Onboarding:
136
+
137
+ ```tsx
138
+ const status = await Amba.onboarding.getStatus();
139
+ // status.current_step is the next step the user should see
140
+ // status.completed flips true when the flow is done
141
+ await Amba.onboarding.nextStep({ goal: 'lose_weight' });
142
+ await Amba.onboarding.skipStep(); // for optional steps
143
+ await Amba.onboarding.complete(); // explicit terminal call (idempotent)
144
+ ```
145
+
146
+ Referrals:
147
+
148
+ ```tsx
149
+ const { code } = await Amba.referrals.getReferralCode();
150
+ // Show `code` to the user — they share it with a friend
151
+ // On the friend's device, after sign-in:
152
+ const claim = await Amba.referrals.claimReferral(code);
153
+ ```
154
+
155
+ ### React Native (bare)
156
+
157
+ ```bash
158
+ npm install @layers/amba-react-native @react-native-async-storage/async-storage
159
+ # Push token: either expo-notifications (works in bare too) or @react-native-firebase/messaging
160
+ npm install @react-native-firebase/app @react-native-firebase/messaging
161
+ ```
162
+
163
+ Push via React Native Firebase:
164
+
165
+ ```tsx
166
+ import messaging from '@react-native-firebase/messaging';
167
+ import { Platform } from 'react-native';
168
+ import { Amba } from '@layers/amba-react-native';
169
+
170
+ async function registerForPush() {
171
+ const authStatus = await messaging().requestPermission();
172
+ const enabled =
173
+ authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
174
+ authStatus === messaging.AuthorizationStatus.PROVISIONAL;
175
+ if (!enabled) return;
176
+
177
+ const token = Platform.OS === 'ios'
178
+ ? await messaging().getAPNSToken()
179
+ : await messaging().getToken();
180
+ if (!token) return;
181
+ await Amba.push.register(token, Platform.OS === 'ios' ? 'apns' : 'fcm');
182
+ }
183
+ ```
184
+
185
+ Subscribing to topics:
186
+
187
+ ```tsx
188
+ await Amba.push.subscribe('marketing');
189
+ await Amba.push.unsubscribe('marketing');
190
+ ```
191
+
192
+ ### Web (browser / Next.js)
193
+
194
+ Web push uses the browser's Push API + a service worker. The SDK handles registration once you have the subscription:
195
+
196
+ ```ts
197
+ import { Amba } from '@layers/amba-web';
198
+
199
+ async function registerForWebPush() {
200
+ const reg = await navigator.serviceWorker.register('/sw.js');
201
+ const sub = await reg.pushManager.subscribe({
202
+ userVisibleOnly: true,
203
+ applicationServerKey: import.meta.env.VITE_VAPID_PUBLIC_KEY,
204
+ });
205
+ await Amba.push.register(JSON.stringify(sub), 'web');
206
+ }
207
+ ```
208
+
209
+ Content:
210
+
211
+ ```ts
212
+ const tip = await Amba.content.today('Daily Tips');
213
+ if (tip) {
214
+ document.querySelector('#daily-tip')!.textContent = tip.body;
215
+ }
216
+ ```
217
+
218
+ Onboarding (typical use in a `useEffect`):
219
+
220
+ ```tsx
221
+ import { useEffect, useState } from 'react';
222
+ import { Amba } from '@layers/amba-web';
223
+
224
+ function OnboardingGate() {
225
+ const [needsOnboarding, setNeedsOnboarding] = useState(false);
226
+ useEffect(() => {
227
+ (async () => {
228
+ const status = await Amba.onboarding.getStatus();
229
+ setNeedsOnboarding(!status.completed);
230
+ })();
231
+ }, []);
232
+ return needsOnboarding ? <OnboardingFlow /> : <MainApp />;
233
+ }
234
+ ```
235
+
236
+ ### iOS (Swift)
237
+
238
+ ```swift
239
+ import UIKit
240
+ import Amba
241
+
242
+ class AppDelegate: NSObject, UIApplicationDelegate {
243
+ func application(_ application: UIApplication,
244
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]?) -> Bool {
245
+ UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, _ in
246
+ if granted {
247
+ DispatchQueue.main.async { application.registerForRemoteNotifications() }
248
+ }
249
+ }
250
+ return true
251
+ }
252
+
253
+ func application(_ application: UIApplication,
254
+ didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
255
+ let token = deviceToken.map { String(format: "%02x", $0) }.joined()
256
+ Task {
257
+ try await Amba.push.register(token: token, platform: .apns)
258
+ }
259
+ }
260
+ }
261
+ ```
262
+
263
+ Content + onboarding:
264
+
265
+ ```swift
266
+ let today = try await Amba.content.today(channel: "Daily Tips")
267
+ let status = try await Amba.onboarding.getStatus()
268
+ try await Amba.onboarding.nextStep(payload: ["goal": "lose_weight"])
269
+ ```
270
+
271
+ > Don't forget: enable the **Push Notifications** capability in **Xcode → Signing & Capabilities**, and upload an APNs key in app.amba.dev under your project's integrations tab.
272
+
273
+ ### Android (Kotlin)
274
+
275
+ In your `Application.onCreate`:
276
+
277
+ ```kotlin
278
+ import com.google.firebase.messaging.FirebaseMessaging
279
+ import com.layers.amba.Amba
280
+ import com.layers.amba.push.PushPlatform
281
+
282
+ FirebaseMessaging.getInstance().token.addOnSuccessListener { token ->
283
+ GlobalScope.launch {
284
+ Amba.push.register(token = token, platform = PushPlatform.FCM)
285
+ }
286
+ }
287
+ ```
288
+
289
+ In your `FirebaseMessagingService` subclass:
290
+
291
+ ```kotlin
292
+ override fun onNewToken(token: String) {
293
+ GlobalScope.launch {
294
+ Amba.push.register(token = token, platform = PushPlatform.FCM)
295
+ }
296
+ }
297
+ ```
298
+
299
+ Content + onboarding:
300
+
301
+ ```kotlin
302
+ val today = Amba.content.today("Daily Tips")
303
+ val status = Amba.onboarding.getStatus()
304
+ Amba.onboarding.nextStep(mapOf("goal" to "lose_weight"))
305
+ ```
306
+
307
+ ### Flutter
308
+
309
+ ```yaml
310
+ dependencies:
311
+ amba: ^1.0.0
312
+ firebase_messaging: ^14.7.0
313
+ ```
314
+
315
+ ```dart
316
+ import 'package:amba/amba.dart';
317
+ import 'package:firebase_messaging/firebase_messaging.dart';
318
+ import 'package:flutter/foundation.dart';
319
+
320
+ Future<void> registerForPush() async {
321
+ final messaging = FirebaseMessaging.instance;
322
+ final settings = await messaging.requestPermission();
323
+ if (settings.authorizationStatus != AuthorizationStatus.authorized) return;
324
+ final token = defaultTargetPlatform == TargetPlatform.iOS
325
+ ? await messaging.getAPNSToken()
326
+ : await messaging.getToken();
327
+ if (token == null) return;
328
+ await Amba.push.register(
329
+ token: token,
330
+ platform: defaultTargetPlatform == TargetPlatform.iOS
331
+ ? PushPlatform.apns
332
+ : PushPlatform.fcm,
333
+ );
334
+ }
335
+
336
+ final tip = await Amba.content.today('Daily Tips');
337
+ final status = await Amba.onboarding.getStatus();
338
+ await Amba.onboarding.nextStep({'goal': 'lose_weight'});
339
+ ```
340
+
341
+ ## Common follow-ups
342
+
343
+ Batch these into one or two multi-choice rounds.
344
+
345
+ 1. **Push: who do you target by default?**
346
+ - All users (no segment)
347
+ - A custom segment — I'll define one
348
+ - Don't enable push yet (just register tokens)
349
+
350
+ 2. **Content libraries: do you want to seed a "Daily Tips" library?**
351
+ - Yes — create a "Daily Tips" library with 7 starter items and a daily-rotation schedule
352
+ - Yes but seed it empty — I'll add items myself
353
+ - No
354
+
355
+ 3. **Onboarding: want a default 3-step flow?** (Welcome → primary goal → permission prompt)
356
+ - Yes
357
+ - No — I'll build my own flow
358
+ - Yes but ask me what the goal-question options should be
359
+
360
+ 4. **Referrals: enable a referral program?**
361
+ - Yes — both sides get 100 of `<currency>` (depends on economy surface; ask if currency isn't wired)
362
+ - Yes — I'll set the reward myself
363
+ - No
364
+
365
+ 5. **Deep links: which scheme + domain?**
366
+ - Custom scheme only (e.g. `myapp://`) — recommended for quick start
367
+ - Custom scheme + universal links (need to upload the AASA file and Digital Asset Links — I'll set up the records, you serve the files)
368
+ - Skip — I have my own deep linking
369
+
370
+ ## Re-run behavior
371
+
372
+ 1. Read `.amba/wired.json` for the existing `engagement` block:
373
+
374
+ ```json
375
+ {
376
+ "surfaces": {
377
+ "engagement": {
378
+ "push": { "campaigns": ["streak_reminder"], "registered_token_via": "expo-notifications" },
379
+ "segments": ["seg_active", "seg_trial"],
380
+ "content": { "libraries": ["Daily Tips"], "schedules": ["Daily rotation"] },
381
+ "onboarding": ["new_user"],
382
+ "referrals": ["invite_a_friend"],
383
+ "deeplinks": { "scheme": "myapp", "universal_links": ["myapp.com"] }
384
+ }
385
+ }
386
+ }
387
+ ```
388
+
389
+ 2. Before creating any resource, call the corresponding `_list` tool first:
390
+ - `amba_push_list_campaigns` — don't recreate a campaign with a key that already exists; offer to edit (`amba_push_update_campaign`) instead.
391
+ - `amba_segments_list` — segments are keyed by `name`; collide → ask "extend or skip".
392
+ - `amba_content_list_libraries` — same.
393
+ - `amba_onboarding_list` — same.
394
+ - `amba_referrals_list` — same.
395
+
396
+ 3. For push, never auto-send a campaign on re-run. Always create as `draft` and let the user trigger `amba_push_campaigns_send` manually (the tool's own description marks send as the action gate).
397
+
398
+ 4. If the user re-runs and the entry file already has `Amba.push.register(...)`, don't duplicate it. Detection: search for `Amba.push.register` in the entry file.
399
+
400
+ 5. Update `wired.json` to append (not replace) new campaigns, segments, libraries, etc.