@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,331 @@
1
+ # Economy
2
+
3
+ Virtual currencies, the catalog of things they buy, stores (curated catalog subsets, possibly segment-gated), and the per-user inventory. Currencies come in two flavors: **soft** (earned in-app, e.g. `gold` / `coins` / `gems`) and **premium** (bought with real money via App Store / Play / Stripe / RevenueCat). Both flow through the same APIs; the difference is whether real money or a tracked event is the input.
4
+
5
+ Pattern:
6
+
7
+ 1. Agent (MCP): create a currency, create catalog items, set prices, group items into one or more stores.
8
+ 2. Client SDK: read the catalog, read the store, show the offer, call `Amba.stores.purchase(...)` (real money) or `Amba.inventory.purchase(...)` (soft currency). The server debits the currency / records the IAP receipt and grants the item.
9
+
10
+ Real-money purchases need a billing integration configured separately — see `references/infrastructure.md` for `amba_integrations_configure` with RevenueCat. Without RevenueCat, premium currencies still work (you grant them manually) but `Amba.stores.purchase(...)` for IAP-backed items won't validate.
11
+
12
+ ## MCP tools
13
+
14
+ ### Currencies
15
+
16
+ | Tool | Purpose | Example args |
17
+ | --- | --- | --- |
18
+ | `amba_currencies_create` / `amba_create_currency` | Define a currency. Soft or premium, with optional auto-recharge (hearts / energy mechanics). | `{ project_id, code: "gems", name: "Gems", is_premium: false, initial_balance: 0, max_balance: null }` |
19
+ | `amba_currencies_list` / `amba_list_currencies` | List currencies. | `{ project_id }` |
20
+ | `amba_currencies_update` / `amba_update_currency` | Edit a currency (rename, change caps, change auto-recharge). | `{ project_id, currency_id, max_balance: 10000 }` |
21
+ | `amba_currencies_delete` / `amba_delete_currency` | Delete a currency (irreversible — users lose their balance). | `{ project_id, currency_id }` |
22
+ | `amba_currencies_grant` / `amba_grant_currency` | Grant currency to a specific user (rewards, refunds, admin adjustments). | `{ project_id, app_user_id, currency_code: "gems", amount: 100, reason: "welcome_bonus" }` |
23
+ | `amba_currencies_get_transactions` / `amba_get_currency_transactions` | Per-user transaction ledger. | `{ project_id, user_id, currency_code: "gems", limit: 100 }` |
24
+
25
+ #### Currency grant rules (auto-grants)
26
+
27
+ | Tool | Purpose | Example args |
28
+ | --- | --- | --- |
29
+ | `amba_currency_grant_rules_create` | Auto-grant currency on an event (e.g. 10 gems per `workout_completed`). | `{ project_id, currency_code: "gems", event_name: "workout_completed", amount: 10, max_per_day: 5 }` |
30
+ | `amba_currency_grant_rules_list` | List grant rules. | `{ project_id, currency_code }` |
31
+ | `amba_currency_grant_rules_delete` | Delete a grant rule. | `{ project_id, rule_id }` |
32
+
33
+ #### Hearts / energy (auto-recharge)
34
+
35
+ Set `auto_recharge_amount` + `auto_recharge_interval_hours` when creating the currency:
36
+
37
+ ```jsonc
38
+ {
39
+ "project_id": "...",
40
+ "code": "hearts",
41
+ "name": "Hearts",
42
+ "is_premium": false,
43
+ "initial_balance": 5,
44
+ "max_balance": 5,
45
+ "auto_recharge_amount": 1,
46
+ "auto_recharge_interval_hours": 4
47
+ }
48
+ ```
49
+
50
+ This is the Duolingo pattern: spend a heart on failure, regenerate 1 heart every 4 hours, capped at 5.
51
+
52
+ ### Catalog
53
+
54
+ | Tool | Purpose | Example args |
55
+ | --- | --- | --- |
56
+ | `amba_catalog_items_create` / `amba_create_catalog_item` | Create a catalog item. | `{ project_id, key: "premium_theme", name: "Dark Pro Theme", item_type: "durable", description: "Pitch-black UI with magenta accents", icon_url: "https://...", category: "themes" }` |
57
+ | `amba_catalog_list` / `amba_list_catalog` | List the catalog. | `{ project_id }` |
58
+ | `amba_catalog_items_get` / `amba_get_catalog_item` | Read one item. | `{ project_id, item_id }` |
59
+ | `amba_catalog_items_update` / `amba_update_catalog_item` | Edit an item. | `{ project_id, item_id, name: "..." }` |
60
+ | `amba_catalog_items_delete` / `amba_delete_catalog_item` | Delete an item. | `{ project_id, item_id }` |
61
+ | `amba_catalog_items_set_price` / `amba_set_item_price` | Set or update a price (per currency, or in-app product id for real money). | `{ project_id, item_id, currency_code: "gems", amount: 200 }` or `{ project_id, item_id, iap_product_id: "com.example.premium_theme" }` |
62
+ | `amba_catalog_items_delete_price` / `amba_delete_catalog_price` | Delete a price (e.g. you no longer accept gems for this item). | `{ project_id, item_id, price_id }` |
63
+ | `amba_catalog_bundles_add_item` / `amba_add_catalog_bundle_item` | Add an item to a bundle (a bundle is itself a catalog item with `item_type: "bundle"`). | `{ project_id, bundle_item_id, child_item_id, quantity: 1 }` |
64
+ | `amba_catalog_bundles_remove_item` / `amba_remove_catalog_bundle_item` | Remove an item from a bundle. | `{ project_id, bundle_item_id, child_item_id }` |
65
+
66
+ Item types:
67
+ - `durable` — owned forever (themes, character skins, ad removal).
68
+ - `consumable` — used up (extra lives, hint packs, energy refills).
69
+ - `bundle` — contains other items (starter pack with 100 gems + 5 hints + 1 theme).
70
+
71
+ ### Stores
72
+
73
+ | Tool | Purpose | Example args |
74
+ | --- | --- | --- |
75
+ | `amba_stores_create` / `amba_create_store` | Create a store (curated catalog subset). Optionally segment-gated. | `{ project_id, name: "Main Shop", description: "Tap to spend gems" }` |
76
+ | `amba_stores_list` | List stores. | `{ project_id }` |
77
+ | `amba_stores_patch` | Edit a store. | `{ project_id, store_id, name: "..." }` |
78
+ | `amba_stores_delete` | Delete a store. | `{ project_id, store_id }` |
79
+ | `amba_stores_add_listing` / `amba_add_store_listing` | Add an item to a store. | `{ project_id, store_id, item_id, sort_order: 1, featured: true }` |
80
+ | `amba_stores_list_listings` | List items in a store. | `{ project_id, store_id }` |
81
+ | `amba_stores_patch_listing` | Edit a listing (re-order, mark featured). | `{ project_id, store_id, listing_id, featured: true }` |
82
+ | `amba_stores_delete_listing` | Remove an item from a store. | `{ project_id, store_id, listing_id }` |
83
+
84
+ A segment-gated store: pass `segment_id` to `amba_stores_create` — only users in that segment see it via `Amba.stores.list()`.
85
+
86
+ ### Inventory (admin-side grants)
87
+
88
+ | Tool | Purpose | Example args |
89
+ | --- | --- | --- |
90
+ | `amba_inventory_grant_item` / `amba_grant_item` | Grant an item to a user without payment (rewards, admin adjustments). | `{ project_id, app_user_id, item_key: "premium_theme", quantity: 1 }` |
91
+ | `amba_users_get_inventory` / `amba_get_user_inventory` | Read a user's inventory. | `{ project_id, user_id }` |
92
+
93
+ ## SDK init per stack
94
+
95
+ The SDK side is mostly read + purchase. `Amba.configure(...)` runs first.
96
+
97
+ ### Expo
98
+
99
+ ```tsx
100
+ import { Amba } from '@layers/amba-expo';
101
+
102
+ // Read the user's currency balance
103
+ const balances = await Amba.currencies.getBalance();
104
+ // [{ code: "gems", amount: 150 }, { code: "hearts", amount: 4 }]
105
+
106
+ // Read the catalog
107
+ const items = await Amba.catalog.list();
108
+
109
+ // Read a store (a curated catalog subset)
110
+ const stores = await Amba.stores.list();
111
+ const offers = await Amba.stores.getPurchaseOptions(stores[0].key);
112
+
113
+ // Soft-currency purchase (deducts from currency balance)
114
+ await Amba.inventory.purchase({
115
+ item_key: 'premium_theme',
116
+ currency_code: 'gems',
117
+ });
118
+
119
+ // Consume a consumable item
120
+ await Amba.inventory.consume({ item_key: 'hint_pack', quantity: 1 });
121
+
122
+ // Read the user's inventory
123
+ const inv = await Amba.inventory.getItems();
124
+ ```
125
+
126
+ ### React Native (bare)
127
+
128
+ Identical surface — only the package name changes:
129
+
130
+ ```tsx
131
+ import { Amba } from '@layers/amba-react-native';
132
+
133
+ const balances = await Amba.currencies.getBalance();
134
+ const items = await Amba.catalog.list();
135
+ const stores = await Amba.stores.list();
136
+ const offers = await Amba.stores.getPurchaseOptions(stores[0].key);
137
+ await Amba.inventory.purchase({ item_key: 'premium_theme', currency_code: 'gems' });
138
+ ```
139
+
140
+ For real-money IAP, you typically combine Amba with RevenueCat or `react-native-iap`:
141
+
142
+ ```tsx
143
+ import Purchases from 'react-native-purchases';
144
+
145
+ // Trigger the OS purchase sheet via RevenueCat
146
+ const offerings = await Purchases.getOfferings();
147
+ const product = offerings.current?.availablePackages[0];
148
+ const { customerInfo, transaction } = await Purchases.purchasePackage(product!);
149
+
150
+ // Forward the receipt to Amba's stores API — server validates with the store
151
+ await Amba.stores.purchase(
152
+ 'main_shop',
153
+ product!.identifier,
154
+ { receipt: transaction.transactionReceipt, platform: 'ios' },
155
+ );
156
+ ```
157
+
158
+ ### Web
159
+
160
+ ```ts
161
+ import { Amba } from '@layers/amba-web';
162
+
163
+ const balances = await Amba.currencies.getBalance();
164
+ const items = await Amba.catalog.list();
165
+ const stores = await Amba.stores.list();
166
+ await Amba.inventory.purchase({ item_key: 'pro_plan', currency_code: 'credits' });
167
+ ```
168
+
169
+ Real-money web flow — typically Stripe Checkout:
170
+
171
+ ```ts
172
+ // Server-side: create a Stripe Checkout Session, redirect.
173
+ // Client-side: on return, Amba's webhook (`/webhooks/stripe` if configured)
174
+ // fulfils via `amba_grant_item`. No SDK call required.
175
+ ```
176
+
177
+ ### iOS (Swift)
178
+
179
+ ```swift
180
+ import Amba
181
+
182
+ let balances = try await Amba.currencies.getBalance()
183
+ let items = try await Amba.catalog.list()
184
+ let stores = try await Amba.stores.list()
185
+ let offers = try await Amba.stores.getPurchaseOptions(storeKey: stores[0].key)
186
+
187
+ // Soft-currency purchase
188
+ _ = try await Amba.inventory.purchase(PurchaseRequest(
189
+ itemKey: "premium_theme",
190
+ currencyCode: "gems"
191
+ ))
192
+
193
+ // Consumable
194
+ _ = try await Amba.inventory.consume(ConsumeRequest(itemKey: "hint_pack", quantity: 1))
195
+
196
+ // Real-money via StoreKit 2
197
+ import StoreKit
198
+
199
+ let products = try await Product.products(for: ["com.example.premium_theme"])
200
+ let result = try await products[0].purchase()
201
+ if case .success(.verified(let transaction)) = result {
202
+ _ = try await Amba.stores.purchase(
203
+ storeKey: "main_shop",
204
+ purchaseOptionId: products[0].id,
205
+ receipt: ["jws_representation": transaction.jsonRepresentation]
206
+ )
207
+ await transaction.finish()
208
+ }
209
+ ```
210
+
211
+ ### Android (Kotlin)
212
+
213
+ ```kotlin
214
+ val balances = Amba.currencies.getBalance()
215
+ val items = Amba.catalog.list()
216
+ val stores = Amba.stores.list()
217
+
218
+ Amba.inventory.purchase(
219
+ PurchaseRequest(itemKey = "premium_theme", currencyCode = "gems")
220
+ )
221
+ Amba.inventory.consume(ConsumeRequest(itemKey = "hint_pack", quantity = 1))
222
+ ```
223
+
224
+ Real-money via Google Play Billing — capture the `purchaseToken` then:
225
+
226
+ ```kotlin
227
+ Amba.stores.purchase(
228
+ storeKey = "main_shop",
229
+ purchaseOptionId = sku,
230
+ receipt = mapOf(
231
+ "purchase_token" to purchaseToken,
232
+ "package_name" to context.packageName,
233
+ "product_id" to sku
234
+ )
235
+ )
236
+ ```
237
+
238
+ ### Flutter
239
+
240
+ ```dart
241
+ import 'package:amba/amba.dart';
242
+
243
+ final balances = await Amba.currencies.getBalance();
244
+ final items = await Amba.catalog.list();
245
+ final stores = await Amba.stores.list();
246
+
247
+ await Amba.inventory.purchase(
248
+ PurchaseRequest(itemKey: 'premium_theme', currencyCode: 'gems'),
249
+ );
250
+ await Amba.inventory.consume(
251
+ ConsumeRequest(itemKey: 'hint_pack', quantity: 1),
252
+ );
253
+ ```
254
+
255
+ For IAP, the `in_app_purchase` Flutter plugin gives you the receipt; pass it to `Amba.stores.purchase(...)` with the same shape as Swift/Kotlin.
256
+
257
+ ## Common follow-ups
258
+
259
+ Batch.
260
+
261
+ 1. **Virtual currency: what's it called?**
262
+ - `gems` (recommended — neutral, premium feel)
263
+ - `coins`
264
+ - `gold`
265
+ - `credits` (recommended for ai_chatbot)
266
+ - `points`
267
+ - Custom — I'll provide
268
+ - None — no soft currency for now
269
+
270
+ 2. **Add a "hearts" / energy mechanic?** (only ask for fitness / game / education)
271
+ - Yes — 5 hearts max, +1 every 4 hours (Duolingo style)
272
+ - Yes but custom (ask for cap and recharge rate)
273
+ - No
274
+
275
+ 3. **Premium currency too?** (real-money purchases of a tradeable virtual currency)
276
+ - Yes — `gems` (premium) — pairs with App Store / Play / Stripe billing
277
+ - No — only soft currency
278
+
279
+ 4. **Seed a starter catalog?**
280
+ - Yes — 3 cosmetics + 1 consumable + 1 starter bundle (uses the chosen currency)
281
+ - Yes but seed it empty — I'll add items myself
282
+ - No
283
+
284
+ 5. **Stores: one store or segmented stores?**
285
+ - One "Main Shop" — recommended for v1
286
+ - Multiple — main + a "Trial Users" segment-gated store
287
+ - I'll wire stores myself
288
+
289
+ 6. **Auto-grant rules:** earn currency on the gamification event?
290
+ - Yes — grant 10 of `<currency>` per `<event>` (same event as XP rule), cap at 5/day
291
+ - No — currency is granted only through achievement rewards / IAP
292
+
293
+ 7. **Real-money integration:** which provider?
294
+ - RevenueCat (recommended for mobile — handles iOS + Android in one SDK; configure with `amba_integrations_configure`)
295
+ - Native StoreKit / Play Billing only (no aggregator)
296
+ - Stripe (web)
297
+ - None yet — I'll add later
298
+
299
+ ## Re-run behavior
300
+
301
+ 1. `.amba/wired.json`:
302
+
303
+ ```json
304
+ {
305
+ "surfaces": {
306
+ "economy": {
307
+ "currencies": ["gems", "hearts"],
308
+ "catalog_items": ["premium_theme", "hint_pack", "starter_bundle"],
309
+ "stores": ["Main Shop"],
310
+ "grant_rules": [{ "currency": "gems", "event": "workout_completed" }],
311
+ "integrations": ["revenuecat"]
312
+ }
313
+ }
314
+ }
315
+ ```
316
+
317
+ 2. Before creating anything:
318
+ - `amba_currencies_list` — match on `code`. Codes are unique per project. Collision → ask to update instead.
319
+ - `amba_catalog_list` — match on `key`. Same.
320
+ - `amba_stores_list` — match on `name`. Same.
321
+
322
+ 3. **Never delete a currency or item without explicit confirmation** — users have balances and inventory tied to them. Suggest renaming + updating instead. If they insist on delete, surface what gets lost (number of users with non-zero balances / inventory).
323
+
324
+ 4. If adding a new currency to an existing project, ask whether to:
325
+ - Add a grant rule for it on the existing gamification event
326
+ - Set up auto-recharge (hearts pattern)
327
+ - Just create it without rules — manual grant only
328
+
329
+ 5. For real-money integrations, treat as orthogonal: `amba_integrations_list` shows what's wired. Don't duplicate. RevenueCat needs webhook URL + secret on the RevenueCat dashboard side; the configure tool prints the URL, but the user has to paste it into RevenueCat themselves — surface that as a "needs your input" line in Step 4.
330
+
331
+ 6. Update `wired.json` to append.