@layers/amba-mcp 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.
- package/README.md +17 -19
- package/dist/api-client.d.ts +42 -4
- package/dist/categories.d.ts +48 -0
- package/dist/expo-build-prompt.js +641 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4443 -718
- package/dist/lib/aliases.d.ts +36 -0
- package/dist/lib/tool-result.d.ts +51 -0
- package/dist/lib/with-pat.d.ts +125 -0
- package/dist/resources/amba-setup-economy.d.ts +14 -0
- package/dist/resources/amba-setup-engagement.d.ts +16 -0
- package/dist/resources/amba-setup-gamification.d.ts +15 -0
- package/dist/resources/amba-setup-identity.d.ts +20 -0
- package/dist/resources/amba-setup-infrastructure.d.ts +18 -0
- package/dist/resources/amba-setup-social.d.ts +15 -0
- package/dist/resources/amba-setup.d.ts +56 -0
- package/dist/resources/expo-build-prompt.d.ts +35 -0
- package/dist/resources/index.d.ts +108 -0
- package/dist/resources/prompts.d.ts +13 -0
- package/dist/resources/prompts.js +2 -0
- package/dist/tools/__test-fixtures__/rename-map.d.ts +19 -0
- package/dist/tools/_pat.d.ts +64 -0
- package/dist/tools/ai-prompts-admin.d.ts +35 -0
- package/dist/tools/auth.d.ts +32 -9
- package/dist/tools/billing.d.ts +29 -0
- package/dist/tools/collections.d.ts +37 -0
- package/dist/tools/functions.d.ts +34 -0
- package/dist/tools/invites.d.ts +21 -0
- package/dist/tools/secrets.d.ts +31 -0
- package/dist/tools/setup.d.ts +1 -1
- package/dist/tools/sites.d.ts +37 -0
- package/package.json +10 -4
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool-name aliasing + deprecation warnings.
|
|
3
|
+
*
|
|
4
|
+
* Background: tool names were normalized to `amba_<resource>_<verb>` shape
|
|
5
|
+
* (DX-12). Older verb-leading names (e.g. the previous `amba_list_users`
|
|
6
|
+
* and `amba_create_streak`) are kept as ALIASES that delegate to the
|
|
7
|
+
* canonical handler so existing agent configs don't break — but each
|
|
8
|
+
* alias hit emits a one-shot deprecation warning so the agent author
|
|
9
|
+
* notices and updates.
|
|
10
|
+
*
|
|
11
|
+
* The Set ensures the warning fires AT MOST ONCE per alias per process
|
|
12
|
+
* lifetime — without that guard a hot agent loop could spam the MCP
|
|
13
|
+
* server logs with the same notice.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Emit a deprecation warning for `alias` (pointing at `canonical`) the
|
|
17
|
+
* first time this alias is invoked in the current process. Subsequent
|
|
18
|
+
* invocations are silent — agents may legitimately call the alias many
|
|
19
|
+
* times in a single session before its config gets updated.
|
|
20
|
+
*
|
|
21
|
+
* Stderr only — `console.warn` so the warning never contaminates the
|
|
22
|
+
* MCP stdio payload.
|
|
23
|
+
*/
|
|
24
|
+
export declare function warnDeprecatedAlias(alias: string, canonical: string): void;
|
|
25
|
+
/**
|
|
26
|
+
* Test-only: clear the once-per-session set so a fresh test can assert
|
|
27
|
+
* the warning fires again. Not exported from the package barrel — only
|
|
28
|
+
* test files import it directly.
|
|
29
|
+
*/
|
|
30
|
+
export declare function resetWarnedAliasesForTesting(): void;
|
|
31
|
+
/**
|
|
32
|
+
* Test-only inspector — returns a snapshot of which aliases have
|
|
33
|
+
* already warned. Lets tests pin "exactly one warn per session" without
|
|
34
|
+
* stubbing console.
|
|
35
|
+
*/
|
|
36
|
+
export declare function getWarnedAliasesForTesting(): ReadonlySet<string>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared MCP tool result helpers.
|
|
3
|
+
*
|
|
4
|
+
* Every tool emits the same `{ content: [{ type: 'text', text: <json> }] }`
|
|
5
|
+
* envelope. Two helpers centralize that:
|
|
6
|
+
*
|
|
7
|
+
* - [`jsonResult`] — wraps an arbitrary payload.
|
|
8
|
+
* - [`passthroughResult`] — flattens an upstream HTTP response
|
|
9
|
+
* (status + parsed body) into the same envelope. Status is written
|
|
10
|
+
* LAST so a colliding top-level `status` field in the API response
|
|
11
|
+
* cannot shadow the HTTP status — agents look at `parsed.status` to
|
|
12
|
+
* distinguish 2xx from 4xx/5xx.
|
|
13
|
+
*
|
|
14
|
+
* Lives in `src/lib/` (vs. `src/tools/_helpers.ts`) to set the same
|
|
15
|
+
* cross-cutting-helper precedent as `src/lib/with-pat.ts` (task #36).
|
|
16
|
+
* `tools/*` files stay strictly tool registrations.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Wire-shape every MCP tool handler returns.
|
|
20
|
+
*
|
|
21
|
+
* The MCP SDK's `tool()` callback signature is structurally typed and
|
|
22
|
+
* carries an open index signature for `_meta` etc. Declaring our return
|
|
23
|
+
* type as a plain `{ content: [...] }` interface won't satisfy that
|
|
24
|
+
* structural check — so the helpers' return type is left as the actual
|
|
25
|
+
* inferred shape (no explicit interface) and consumers rely on the
|
|
26
|
+
* inference + the SDK's structural compatibility. If we ever want a
|
|
27
|
+
* named alias, write it as a type-alias over the inferred shape rather
|
|
28
|
+
* than a closed interface.
|
|
29
|
+
*/
|
|
30
|
+
/** Wrap an arbitrary payload as a JSON-text tool result. */
|
|
31
|
+
export declare function jsonResult(payload: unknown): {
|
|
32
|
+
content: {
|
|
33
|
+
type: "text";
|
|
34
|
+
text: string;
|
|
35
|
+
}[];
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Flatten an upstream HTTP response into the agent-facing tool payload.
|
|
39
|
+
* Body fields are spread FIRST so a future top-level `status` key in
|
|
40
|
+
* the API response cannot shadow the HTTP `status` — agents read
|
|
41
|
+
* `parsed.status` to distinguish 2xx from 4xx/5xx.
|
|
42
|
+
*/
|
|
43
|
+
export declare function passthroughResult(result: {
|
|
44
|
+
status: number;
|
|
45
|
+
body: unknown;
|
|
46
|
+
}): {
|
|
47
|
+
content: {
|
|
48
|
+
type: "text";
|
|
49
|
+
text: string;
|
|
50
|
+
}[];
|
|
51
|
+
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pat`-as-tool-argument pattern helper.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this exists
|
|
5
|
+
*
|
|
6
|
+
* `amba_developer_signup` returns a fresh Personal Access Token (PAT). For
|
|
7
|
+
* subsequent calls to take effect that PAT needs to land in the inbound
|
|
8
|
+
* `Authorization: Bearer …` header — but every MCP client today
|
|
9
|
+
* (Claude Code, Cursor, Windsurf, Claude.ai web) configures that header
|
|
10
|
+
* from a STATIC config file. The agent has no way to mutate it
|
|
11
|
+
* mid-session.
|
|
12
|
+
*
|
|
13
|
+
* The fix: every non-public tool also accepts an optional `pat` argument.
|
|
14
|
+
* If present, that PAT overrides the inbound Bearer for THIS call only.
|
|
15
|
+
* If absent, we fall back to whatever Bearer came in on the HTTP request
|
|
16
|
+
* (the static config value). If neither is set, the tool short-circuits
|
|
17
|
+
* with a structured `MISSING_PAT` error instead of producing a confusing
|
|
18
|
+
* downstream 401.
|
|
19
|
+
*
|
|
20
|
+
* Once the agent has a working PAT, it should pass it as `pat` on every
|
|
21
|
+
* tool call in the current session — that's the same-session magic that
|
|
22
|
+
* eliminates the restart blocker after signup/login. The agent should
|
|
23
|
+
* also persist the PAT to the customer's MCP client config (under
|
|
24
|
+
* `mcpServers.amba.headers.Authorization`). Next agent session will pick
|
|
25
|
+
* the static Bearer up automatically, at which point the `pat` arg
|
|
26
|
+
* becomes optional. The customer does nothing — no restart, no manual
|
|
27
|
+
* step.
|
|
28
|
+
*
|
|
29
|
+
* ## Why a helper instead of editing every tool by hand
|
|
30
|
+
*
|
|
31
|
+
* Mechanical retrofit across 170+ tool registrations. The helper makes the
|
|
32
|
+
* pat-arg shape uniform, surfaces a single `MISSING_PAT` error from one
|
|
33
|
+
* place, and produces a per-call `ApiClient` so individual tool handlers
|
|
34
|
+
* stay unchanged in shape (`async (args, { client }) => client.get(...)`).
|
|
35
|
+
*
|
|
36
|
+
* ## Public-tool variant
|
|
37
|
+
*
|
|
38
|
+
* `amba_developer_signup`, `amba_developer_login`, and `amba_developer_refresh`
|
|
39
|
+
* MINT a PAT — they cannot require one as input, so they keep the plain
|
|
40
|
+
* `server.tool(...)` registration. Use `registerPublicTool` if you want
|
|
41
|
+
* the same call shape without the pat machinery.
|
|
42
|
+
*/
|
|
43
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
44
|
+
import { z, type ZodRawShape } from 'zod';
|
|
45
|
+
import type { ApiClient } from '../api-client.js';
|
|
46
|
+
/** Wire-shape returned by an MCP tool handler. */
|
|
47
|
+
export interface ToolResult {
|
|
48
|
+
content: Array<{
|
|
49
|
+
type: 'text';
|
|
50
|
+
text: string;
|
|
51
|
+
}>;
|
|
52
|
+
}
|
|
53
|
+
/** Context passed to handlers registered via `registerTool`. */
|
|
54
|
+
export interface WithPatHandlerContext {
|
|
55
|
+
/**
|
|
56
|
+
* The Bearer token used for the downstream API call on this invocation.
|
|
57
|
+
* Always a non-empty string — `registerTool` short-circuits with
|
|
58
|
+
* `MISSING_PAT` BEFORE the handler runs if neither `args.pat` nor the
|
|
59
|
+
* inbound HTTP Bearer is set, so handlers can treat this as authoritative.
|
|
60
|
+
*/
|
|
61
|
+
pat: string;
|
|
62
|
+
/**
|
|
63
|
+
* An `ApiClient` bound to `pat`. Use this instead of the closure-captured
|
|
64
|
+
* `apiClient` so the resolved Bearer flows through to the downstream
|
|
65
|
+
* `/admin/*` call.
|
|
66
|
+
*/
|
|
67
|
+
client: ApiClient;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The `pat` schema entry injected into every tool that goes through
|
|
71
|
+
* `registerTool`. Exported so tests can assert on its presence.
|
|
72
|
+
*/
|
|
73
|
+
export declare const PAT_ARG_SCHEMA: z.ZodOptional<z.ZodString>;
|
|
74
|
+
/**
|
|
75
|
+
* Register a tool that:
|
|
76
|
+
*
|
|
77
|
+
* 1. Accepts an optional `pat` argument alongside its declared schema.
|
|
78
|
+
* 2. Resolves the effective Bearer in this priority order:
|
|
79
|
+
* a. `args.pat` (per-call override — the agent-friendly path).
|
|
80
|
+
* b. The inbound HTTP Bearer captured at client construction
|
|
81
|
+
* (the static MCP-config path).
|
|
82
|
+
* c. None — the helper returns `MISSING_PAT` and the handler is
|
|
83
|
+
* NOT invoked.
|
|
84
|
+
* 3. Hands the handler a per-call `ApiClient` already bound to the
|
|
85
|
+
* resolved Bearer (`ctx.client`).
|
|
86
|
+
*
|
|
87
|
+
* Tool authors should call `ctx.client.get(...)` / `.post(...)` rather
|
|
88
|
+
* than the closure-captured `apiClient`, so the pat-arg override actually
|
|
89
|
+
* takes effect.
|
|
90
|
+
*/
|
|
91
|
+
export declare function registerTool<S extends ZodRawShape>(server: McpServer, apiClient: ApiClient, name: string, description: string, schema: S, handler: (args: {
|
|
92
|
+
[K in keyof S]: z.infer<S[K]>;
|
|
93
|
+
} & {
|
|
94
|
+
pat?: string | undefined;
|
|
95
|
+
}, ctx: WithPatHandlerContext) => Promise<ToolResult>,
|
|
96
|
+
/**
|
|
97
|
+
* Legacy tool names to ALSO register as aliases. Each alias accepts the
|
|
98
|
+
* same schema and delegates to the same handler, but emits a one-shot
|
|
99
|
+
* deprecation warning to the MCP server logs so the agent author
|
|
100
|
+
* notices and migrates to the canonical name. See
|
|
101
|
+
* `packages/mcp/src/lib/aliases.ts` for the warning machinery.
|
|
102
|
+
*
|
|
103
|
+
* Added during the DX-12 naming normalization. Aliases are an existing
|
|
104
|
+
* agent-config compatibility shim and will be removed once the broader
|
|
105
|
+
* agent ecosystem has migrated; until then, do not add new aliases for
|
|
106
|
+
* brand-new tools — only use this for the rename mappings recorded in
|
|
107
|
+
* `tools/__test-fixtures__/rename-map.ts`.
|
|
108
|
+
*/
|
|
109
|
+
aliases?: readonly string[]): void;
|
|
110
|
+
/**
|
|
111
|
+
* Register a tool that does NOT need an inbound Bearer or pat argument.
|
|
112
|
+
* Reserved for the 3 public auth tools (`amba_developer_signup`,
|
|
113
|
+
* `amba_developer_login`, `amba_developer_refresh`) — anything else
|
|
114
|
+
* should go through `registerTool`.
|
|
115
|
+
*/
|
|
116
|
+
export declare function registerPublicTool<S extends ZodRawShape>(server: McpServer, name: string, description: string, schema: S, handler: (args: {
|
|
117
|
+
[K in keyof S]: z.infer<S[K]>;
|
|
118
|
+
}) => Promise<ToolResult>,
|
|
119
|
+
/**
|
|
120
|
+
* Legacy tool names to also register as aliases. Same semantics as the
|
|
121
|
+
* `aliases` parameter on `registerTool` — emits a one-shot deprecation
|
|
122
|
+
* warning per alias per process. No active auth-tool rename uses this
|
|
123
|
+
* yet, but the parameter exists so future renames stay consistent.
|
|
124
|
+
*/
|
|
125
|
+
aliases?: readonly string[]): void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `amba://setup/economy` — per-surface playbook for in-app economy.
|
|
3
|
+
*
|
|
4
|
+
* Companion to `AMBA_SETUP_GUIDE_MD`. Step 3 of the main playbook
|
|
5
|
+
* tells the agent to fetch this resource when wiring economy.
|
|
6
|
+
*
|
|
7
|
+
* Surface scope: currencies, catalog, stores, inventory.
|
|
8
|
+
*
|
|
9
|
+
* Twin: `packages/cli/skill-bundle/references/economy.md`.
|
|
10
|
+
* Drift gate in `amba-setup.test.ts`.
|
|
11
|
+
*/
|
|
12
|
+
export declare const AMBA_SETUP_ECONOMY_MD = "# Economy\n\nVirtual 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.\n\nPattern:\n\n1. Agent (MCP): create a currency, create catalog items, set prices, group items into one or more stores.\n2. Client SDK: read the catalog, read the store, show the offer, call `Amba.stores.purchase(...)` (real money) or `Amba.inventory.purchase(...)` (soft currency).\n\nReal-money purchases need a billing integration configured separately \u2014 see `amba://setup/infrastructure` for `amba_integrations_configure` with RevenueCat.\n\n## MCP tools\n\n### Currencies\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_currencies_create` | Define a currency. Soft or premium, with optional auto-recharge (hearts / energy). | `{ project_id, code: \"gems\", name: \"Gems\", is_premium: false, initial_balance: 0, max_balance: null }` |\n| `amba_currencies_list` | List currencies. | `{ project_id }` |\n| `amba_currencies_update` | Edit (rename, change caps, change auto-recharge). | `{ project_id, currency_id, max_balance: 10000 }` |\n| `amba_currencies_delete` | Delete a currency (irreversible \u2014 users lose their balance). | `{ project_id, currency_id }` |\n| `amba_currencies_grant` | Grant currency to a specific user. | `{ project_id, app_user_id, currency_code: \"gems\", amount: 100, reason: \"welcome_bonus\" }` |\n| `amba_currencies_get_transactions` | Per-user transaction ledger. | `{ project_id, user_id, currency_code: \"gems\", limit: 100 }` |\n\n#### Currency grant rules (auto-grants)\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_currency_grant_rules_create` | Auto-grant currency on an event. | `{ project_id, currency_code: \"gems\", event_name: \"workout_completed\", amount: 10, max_per_day: 5 }` |\n| `amba_currency_grant_rules_list` | List grant rules. | `{ project_id, currency_code }` |\n| `amba_currency_grant_rules_delete` | Delete a grant rule. | `{ project_id, rule_id }` |\n\n#### Hearts / energy (auto-recharge)\n\nSet `auto_recharge_amount` + `auto_recharge_interval_hours` when creating the currency:\n\n```jsonc\n{\n \"project_id\": \"...\",\n \"code\": \"hearts\",\n \"name\": \"Hearts\",\n \"is_premium\": false,\n \"initial_balance\": 5,\n \"max_balance\": 5,\n \"auto_recharge_amount\": 1,\n \"auto_recharge_interval_hours\": 4\n}\n```\n\nThis is the Duolingo pattern: spend a heart on failure, regenerate 1 every 4 hours, capped at 5.\n\n### Catalog\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_catalog_items_create` | Create a catalog item. | `{ project_id, key: \"premium_theme\", name: \"Dark Pro Theme\", item_type: \"durable\", description: \"...\", icon_url: \"...\", category: \"themes\" }` |\n| `amba_catalog_list` | List the catalog. | `{ project_id }` |\n| `amba_catalog_items_get` | Read one item. | `{ project_id, item_id }` |\n| `amba_catalog_items_update` | Edit an item. | `{ project_id, item_id, name: \"...\" }` |\n| `amba_catalog_items_delete` | Delete an item. | `{ project_id, item_id }` |\n| `amba_catalog_items_set_price` | Set or update a price. | `{ project_id, item_id, currency_code: \"gems\", amount: 200 }` or `{ project_id, item_id, iap_product_id: \"com.example.premium_theme\" }` |\n| `amba_catalog_items_delete_price` | Delete a price. | `{ project_id, item_id, price_id }` |\n| `amba_catalog_bundles_add_item` | Add an item to a bundle. | `{ project_id, bundle_item_id, child_item_id, quantity: 1 }` |\n| `amba_catalog_bundles_remove_item` | Remove an item from a bundle. | `{ project_id, bundle_item_id, child_item_id }` |\n\nItem types:\n- `durable` \u2014 owned forever (themes, character skins, ad removal).\n- `consumable` \u2014 used up (extra lives, hint packs, energy refills).\n- `bundle` \u2014 contains other items (starter pack with 100 gems + 5 hints + 1 theme).\n\n### Stores\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_stores_create` | Create a store (curated catalog subset). Optionally segment-gated. | `{ project_id, name: \"Main Shop\", description: \"Tap to spend gems\" }` |\n| `amba_stores_list` | List stores. | `{ project_id }` |\n| `amba_stores_patch` | Edit a store. | `{ project_id, store_id, name: \"...\" }` |\n| `amba_stores_delete` | Delete a store. | `{ project_id, store_id }` |\n| `amba_stores_add_listing` | Add an item to a store. | `{ project_id, store_id, item_id, sort_order: 1, featured: true }` |\n| `amba_stores_list_listings` | List items in a store. | `{ project_id, store_id }` |\n| `amba_stores_patch_listing` | Edit a listing (re-order, mark featured). | `{ project_id, store_id, listing_id, featured: true }` |\n| `amba_stores_delete_listing` | Remove an item from a store. | `{ project_id, store_id, listing_id }` |\n\nA segment-gated store: pass `segment_id` to `amba_stores_create` \u2014 only users in that segment see it via `Amba.stores.list()`.\n\n### Inventory (admin-side grants)\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_inventory_grant_item` | Grant an item to a user without payment. | `{ project_id, app_user_id, item_key: \"premium_theme\", quantity: 1 }` |\n| `amba_users_get_inventory` | Read a user's inventory. | `{ project_id, user_id }` |\n\n## SDK init per stack\n\nThe SDK side is mostly read + purchase. `Amba.configure(...)` runs first.\n\n### Expo / React Native\n\n```tsx\nimport { Amba } from '@layers/amba-expo';\n\nconst balances = await Amba.currencies.getBalance();\nconst items = await Amba.catalog.list();\nconst stores = await Amba.stores.list();\nconst offers = await Amba.stores.getPurchaseOptions(stores[0].key);\n\n// Soft-currency purchase\nawait Amba.inventory.purchase({ item_key: 'premium_theme', currency_code: 'gems' });\n\n// Consume a consumable\nawait Amba.inventory.consume({ item_key: 'hint_pack', quantity: 1 });\n\nconst inv = await Amba.inventory.getItems();\n```\n\nFor real-money IAP on RN, combine Amba with RevenueCat or `react-native-iap`. Capture the receipt then:\n\n```tsx\nawait Amba.stores.purchase('main_shop', product.identifier, {\n receipt: transaction.transactionReceipt,\n platform: 'ios',\n});\n```\n\n### Web\n\n```ts\nimport { Amba } from '@layers/amba-web';\n\nconst balances = await Amba.currencies.getBalance();\nconst items = await Amba.catalog.list();\nawait Amba.inventory.purchase({ item_key: 'pro_plan', currency_code: 'credits' });\n```\n\nReal-money web flow \u2014 typically Stripe Checkout. Configure a Stripe webhook via `amba_integrations_configure`; Amba fulfils via `amba_inventory_grant_item` automatically. No SDK call required.\n\n### iOS (Swift)\n\n```swift\nimport Amba\n\nlet balances = try await Amba.currencies.getBalance()\nlet items = try await Amba.catalog.list()\nlet stores = try await Amba.stores.list()\n\n_ = try await Amba.inventory.purchase(PurchaseRequest(\n itemKey: \"premium_theme\",\n currencyCode: \"gems\"\n))\n\n// Real-money via StoreKit 2\nimport StoreKit\nlet products = try await Product.products(for: [\"com.example.premium_theme\"])\nlet result = try await products[0].purchase()\nif case .success(.verified(let transaction)) = result {\n _ = try await Amba.stores.purchase(\n storeKey: \"main_shop\",\n purchaseOptionId: products[0].id,\n receipt: [\"jws_representation\": transaction.jsonRepresentation]\n )\n await transaction.finish()\n}\n```\n\n### Android (Kotlin)\n\n```kotlin\nval balances = Amba.currencies.getBalance()\nval items = Amba.catalog.list()\nAmba.inventory.purchase(PurchaseRequest(itemKey = \"premium_theme\", currencyCode = \"gems\"))\n```\n\nReal-money via Google Play Billing \u2014 capture `purchaseToken` then `Amba.stores.purchase` with `receipt: mapOf(\"purchase_token\" to purchaseToken, \"package_name\" to packageName, \"product_id\" to sku)`.\n\n### Flutter\n\n```dart\nimport 'package:amba/amba.dart';\n\nfinal balances = await Amba.currencies.getBalance();\nfinal items = await Amba.catalog.list();\nawait Amba.inventory.purchase(\n PurchaseRequest(itemKey: 'premium_theme', currencyCode: 'gems'),\n);\n```\n\nFor IAP, the `in_app_purchase` plugin gives you the receipt; pass it to `Amba.stores.purchase`.\n\n## Common follow-ups\n\nBatch.\n\n1. **Virtual currency: what's it called?**\n - `gems` (recommended \u2014 neutral, premium feel)\n - `coins` / `gold`\n - `credits` (recommended for ai_chatbot)\n - `points`\n - Custom \u2014 I'll provide\n - None \u2014 no soft currency for now\n\n2. **Add a \"hearts\" / energy mechanic?** (only ask for fitness / game / education)\n - Yes \u2014 5 hearts max, +1 every 4 hours (Duolingo style)\n - Yes but custom (ask for cap and recharge rate)\n - No\n\n3. **Premium currency too?** (real-money purchases of a tradeable virtual currency)\n - Yes \u2014 `gems` (premium) \u2014 pairs with App Store / Play / Stripe billing\n - No \u2014 only soft currency\n\n4. **Seed a starter catalog?**\n - Yes \u2014 3 cosmetics + 1 consumable + 1 starter bundle (uses the chosen currency)\n - Yes but seed it empty \u2014 I'll add items myself\n - No\n\n5. **Stores: one store or segmented stores?**\n - One \"Main Shop\" \u2014 recommended for v1\n - Multiple \u2014 main + a \"Trial Users\" segment-gated store\n - I'll wire stores myself\n\n6. **Auto-grant rules:** earn currency on the gamification event?\n - Yes \u2014 grant 10 of `<currency>` per `<event>` (same event as XP rule), cap at 5/day\n - No \u2014 currency is granted only through achievement rewards / IAP\n\n7. **Real-money integration:** which provider?\n - RevenueCat (recommended for mobile)\n - Native StoreKit / Play Billing only\n - Stripe (web)\n - None yet\n\n## Re-run behavior\n\n1. Before creating anything:\n - `amba_currencies_list` \u2014 match on `code`. Codes are unique per project. Collision \u2192 ask to update instead.\n - `amba_catalog_list` \u2014 match on `key`. Same.\n - `amba_stores_list` \u2014 match on `name`. Same.\n\n2. **Never delete a currency or item without explicit confirmation** \u2014 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).\n\n3. 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 \u2014 surface that as a \"needs your input\" line.\n";
|
|
13
|
+
export declare const AMBA_SETUP_ECONOMY_URI = "amba://setup/economy";
|
|
14
|
+
export declare const AMBA_SETUP_ECONOMY_MIME = "text/markdown";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `amba://setup/engagement` — per-surface playbook for re-engagement.
|
|
3
|
+
*
|
|
4
|
+
* Companion to `AMBA_SETUP_GUIDE_MD`. Step 3 of the main playbook
|
|
5
|
+
* tells the agent to fetch this resource when wiring engagement.
|
|
6
|
+
*
|
|
7
|
+
* Surface scope: push notifications + campaigns, segments,
|
|
8
|
+
* content libraries, onboarding flows, deeplinks, referrals,
|
|
9
|
+
* tracked links.
|
|
10
|
+
*
|
|
11
|
+
* Twin: `packages/cli/skill-bundle/references/engagement.md`.
|
|
12
|
+
* Drift gate in `amba-setup.test.ts`.
|
|
13
|
+
*/
|
|
14
|
+
export declare const AMBA_SETUP_ENGAGEMENT_MD = "# Engagement\n\nEverything 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 \u2014 the part you do \u2014 lives behind MCP tools.\n\n## MCP tools\n\n### Push\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_push_campaigns_create` | Create a draft push campaign. | `{ project_id, title: \"Don't break your streak!\", body: \"Log your workout to keep the fire alive.\", name: \"streak_reminder\", segment_id: \"seg_active\" }` |\n| `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.\" }` |\n| `amba_push_campaigns_send` | Send (or schedule) a draft campaign. | `{ project_id, campaign_id }` |\n| `amba_push_list_campaigns` | List campaigns. | `{ project_id }` |\n| `amba_push_get_campaign` | Read one campaign + its delivery stats. | `{ project_id, campaign_id }` |\n| `amba_push_update_campaign` | Edit a draft. | `{ project_id, campaign_id, title, body, scheduled_at }` |\n| `amba_push_delete_campaign` | Remove a draft / cancel a scheduled campaign. | `{ project_id, campaign_id }` |\n\n### Segments\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_segments_create` | Create a rule-based user segment. | `{ project_id, name: \"Power Users\", rules: { all: [{ field: \"events.workout_completed.count_7d\", op: \">=\", value: 5 }] } }` |\n| `amba_segments_list` | List all segments (system + custom). | `{ project_id }` |\n| `amba_segments_get` | Get one segment by id. | `{ project_id, segment_id }` |\n| `amba_segments_evaluate` | Materialize the segment \u2014 returns the set of matching user_ids. | `{ project_id, segment_id }` |\n| `amba_segments_patch` | Edit rules / name / description. | `{ project_id, segment_id, rules: {...} }` |\n| `amba_segments_delete` | Drop a segment. | `{ project_id, segment_id }` |\n\n### Content libraries\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_content_libraries_create` | Create a library (daily tips, lessons, quotes). | `{ project_id, name: \"Daily Tips\", description: \"Workout motivation served daily\" }` |\n| `amba_content_items_add` | Bulk-add items to a library. | `{ project_id, library_id, items: [{ body: \"Tip 1\" }, { body: \"Tip 2\" }, ...] }` |\n| `amba_content_bulk_import` | CSV/JSON bulk import. | `{ project_id, library_id, format: \"json\", items: [...] }` |\n| `amba_content_list_libraries` | List libraries in this project. | `{ project_id }` |\n| `amba_content_list_items` | List items in one library. | `{ project_id, library_id, limit: 100 }` |\n| `amba_content_update_item` | Edit one item. | `{ project_id, library_id, item_id, body: \"\u2026\" }` |\n| `amba_content_delete_item` | Drop an item. | `{ project_id, library_id, item_id }` |\n| `amba_content_schedules_create` | Schedule a library for daily / weekly / random delivery. | `{ project_id, library_id, name: \"Daily rotation\", schedule_type: \"daily_rotation\" }` |\n| `amba_content_list_schedules` | List schedules. | `{ project_id, library_id }` |\n| `amba_content_update_schedule` | Edit a schedule's type or config. | `{ project_id, library_id, schedule_id, schedule_type: \"weekly\" }` |\n| `amba_content_delete_schedule` | Drop a schedule. | `{ project_id, library_id, schedule_id }` |\n\nThe worked example:\n\n```\n1. amba_content_libraries_create({ project_id, name: \"Daily Tips\" })\n2. amba_content_items_add({ project_id, library_id, items: [{ body: \"Tip 1\" }, ...] })\n3. amba_content_schedules_create({ project_id, library_id, name: \"Daily rotation\", schedule_type: \"daily_rotation\" })\n4. In-app: const today = await Amba.content.today(\"Daily Tips\");\n```\n\n### Onboarding flows\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_onboarding_create` | 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\"] }, { key: \"notif_permission\", type: \"permission_prompt\" }] }` |\n| `amba_onboarding_list` | List flows. | `{ project_id }` |\n| `amba_onboarding_get` | Read one flow. | `{ project_id, flow_id }` |\n| `amba_onboarding_update` | Edit steps. | `{ project_id, flow_id, steps: [...] }` |\n| `amba_onboarding_get_stats` | Funnel stats \u2014 step-by-step completion + drop-off rates. | `{ project_id, flow_id }` |\n| `amba_onboarding_delete` | Drop a flow. | `{ project_id, flow_id }` |\n\n### Deep links\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `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\" }` |\n| `amba_deeplinks_get_config` | Read the config. | `{ project_id }` |\n| `amba_deeplinks_list` | List existing deep-link records. | `{ project_id }` |\n| `amba_deeplinks_delete` | Drop a deep-link record. | `{ project_id, deeplink_id }` |\n\n### Referrals\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_referrals_create` | 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 } }` |\n| `amba_referrals_list` | List programs. | `{ project_id }` |\n| `amba_referrals_patch` | Edit rewards / program name. | `{ project_id, program_id, referrer_reward: {...} }` |\n| `amba_referrals_get_stats` | Per-program acquisition stats. | `{ project_id, program_id }` |\n| `amba_referrals_delete` | Drop a program. | `{ project_id, program_id }` |\n\n### Tracked links\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_tracked_links_create` | 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\" }` |\n| `amba_tracked_links_get_stats` | Click / conversion stats for a link. | `{ project_id, link_id }` |\n\n## SDK init per stack\n\n`Amba.configure(...)` must run before any of the calls below \u2014 wire it once at app start (see `amba://setup/identity`). The snippets below show only the engagement-specific bits.\n\n### Expo\n\n```bash\nnpx expo install @layers/amba-expo expo-notifications expo-device\n```\n\n```tsx\nimport * as Notifications from 'expo-notifications';\nimport * as Device from 'expo-device';\nimport { Platform } from 'react-native';\nimport { Amba } from '@layers/amba-expo';\n\nasync function registerForPush() {\n if (!Device.isDevice) return;\n const { status: existing } = await Notifications.getPermissionsAsync();\n let final = existing;\n if (existing !== 'granted') {\n const { status } = await Notifications.requestPermissionsAsync();\n final = status;\n }\n if (final !== 'granted') return;\n const { data: token } = await Notifications.getDevicePushTokenAsync();\n await Amba.push.register(token, Platform.OS === 'ios' ? 'apns' : 'fcm');\n}\n\n// Content (daily tip)\nconst today = await Amba.content.today('Daily Tips');\n\n// Onboarding\nconst status = await Amba.onboarding.getStatus();\nawait Amba.onboarding.nextStep({ goal: 'lose_weight' });\nawait Amba.onboarding.complete();\n\n// Referrals\nconst { code } = await Amba.referrals.getReferralCode();\nconst claim = await Amba.referrals.claimReferral(code);\n```\n\n### React Native (bare)\n\n```bash\nnpm install @layers/amba-react-native @react-native-firebase/messaging\n```\n\n```tsx\nimport messaging from '@react-native-firebase/messaging';\nimport { Platform } from 'react-native';\nimport { Amba } from '@layers/amba-react-native';\n\nconst authStatus = await messaging().requestPermission();\nconst token = Platform.OS === 'ios'\n ? await messaging().getAPNSToken()\n : await messaging().getToken();\nif (token) await Amba.push.register(token, Platform.OS === 'ios' ? 'apns' : 'fcm');\n\nawait Amba.push.subscribe('marketing');\n```\n\n### Web (browser / Next.js)\n\n```ts\nimport { Amba } from '@layers/amba-web';\n\nconst reg = await navigator.serviceWorker.register('/sw.js');\nconst sub = await reg.pushManager.subscribe({\n userVisibleOnly: true,\n applicationServerKey: import.meta.env.VITE_VAPID_PUBLIC_KEY,\n});\nawait Amba.push.register(JSON.stringify(sub), 'web');\n\nconst tip = await Amba.content.today('Daily Tips');\nconst status = await Amba.onboarding.getStatus();\n```\n\n### iOS (Swift)\n\n```swift\nimport UIKit\nimport Amba\n\nclass AppDelegate: NSObject, UIApplicationDelegate {\n func application(_ application: UIApplication,\n didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]?) -> Bool {\n UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, _ in\n if granted {\n DispatchQueue.main.async { application.registerForRemoteNotifications() }\n }\n }\n return true\n }\n\n func application(_ application: UIApplication,\n didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {\n let token = deviceToken.map { String(format: \"%02x\", $0) }.joined()\n Task { try await Amba.push.register(token: token, platform: .apns) }\n }\n}\n```\n\n> Enable the **Push Notifications** capability in **Xcode \u2192 Signing & Capabilities**, and upload an APNs key in `app.amba.dev` under your project's integrations tab.\n\n### Android (Kotlin)\n\n```kotlin\nimport com.google.firebase.messaging.FirebaseMessaging\nimport com.layers.amba.Amba\nimport com.layers.amba.push.PushPlatform\n\nFirebaseMessaging.getInstance().token.addOnSuccessListener { token ->\n GlobalScope.launch { Amba.push.register(token = token, platform = PushPlatform.FCM) }\n}\n\noverride fun onNewToken(token: String) {\n GlobalScope.launch { Amba.push.register(token = token, platform = PushPlatform.FCM) }\n}\n```\n\n### Flutter\n\n```dart\nimport 'package:amba/amba.dart';\nimport 'package:firebase_messaging/firebase_messaging.dart';\n\nfinal messaging = FirebaseMessaging.instance;\nawait messaging.requestPermission();\nfinal token = defaultTargetPlatform == TargetPlatform.iOS\n ? await messaging.getAPNSToken()\n : await messaging.getToken();\nif (token != null) {\n await Amba.push.register(\n token: token,\n platform: defaultTargetPlatform == TargetPlatform.iOS ? PushPlatform.apns : PushPlatform.fcm,\n );\n}\n\nfinal tip = await Amba.content.today('Daily Tips');\nfinal status = await Amba.onboarding.getStatus();\n```\n\n## Common follow-ups\n\nBatch.\n\n1. **Push: who do you target by default?**\n - All users (no segment)\n - A custom segment \u2014 I'll define one\n - Don't enable push yet (just register tokens)\n\n2. **Content libraries: do you want to seed a \"Daily Tips\" library?**\n - Yes \u2014 create a \"Daily Tips\" library with 7 starter items and a daily-rotation schedule\n - Yes but seed it empty \u2014 I'll add items myself\n - No\n\n3. **Onboarding: want a default 3-step flow?** (Welcome \u2192 primary goal \u2192 permission prompt)\n - Yes\n - No \u2014 I'll build my own flow\n - Yes but ask me what the goal-question options should be\n\n4. **Referrals: enable a referral program?**\n - Yes \u2014 both sides get 100 of `<currency>` (depends on economy surface; ask if currency isn't wired)\n - Yes \u2014 I'll set the reward myself\n - No\n\n5. **Deep links: which scheme + domain?**\n - Custom scheme only (e.g. `myapp://`) \u2014 recommended for quick start\n - Custom scheme + universal links (need to upload the AASA file and Digital Asset Links)\n - Skip \u2014 I have my own deep linking\n\n## Re-run behavior\n\n1. Before creating any resource, call the corresponding `_list` tool first:\n - `amba_push_list_campaigns` \u2014 don't recreate a campaign with a key that already exists; offer `amba_push_update_campaign` instead.\n - `amba_segments_list` \u2014 segments are keyed by `name`; collide \u2192 ask \"extend or skip\".\n - `amba_content_list_libraries` \u2014 same.\n - `amba_onboarding_list` \u2014 same.\n - `amba_referrals_list` \u2014 same.\n\n2. For push, **never auto-send a campaign on re-run**. Always create as `draft` and let the user trigger `amba_push_campaigns_send` manually.\n\n3. 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.\n";
|
|
15
|
+
export declare const AMBA_SETUP_ENGAGEMENT_URI = "amba://setup/engagement";
|
|
16
|
+
export declare const AMBA_SETUP_ENGAGEMENT_MIME = "text/markdown";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `amba://setup/gamification` — per-surface playbook for game mechanics.
|
|
3
|
+
*
|
|
4
|
+
* Companion to `AMBA_SETUP_GUIDE_MD`. Step 3 of the main playbook
|
|
5
|
+
* tells the agent to fetch this resource when wiring gamification.
|
|
6
|
+
*
|
|
7
|
+
* Surface scope: XP rules, achievements, streaks, leaderboards,
|
|
8
|
+
* challenges.
|
|
9
|
+
*
|
|
10
|
+
* Twin: `packages/cli/skill-bundle/references/gamification.md`.
|
|
11
|
+
* Drift gate in `amba-setup.test.ts`.
|
|
12
|
+
*/
|
|
13
|
+
export declare const AMBA_SETUP_GAMIFICATION_MD = "# Gamification\n\nFive primitives that turn a flat app into something users come back to: XP rules (auto-award points on a tracked event), achievements (badges that unlock on criteria), streaks (consecutive-period qualification), leaderboards (rank by metric), and challenges (time-bounded goals with rewards). All five are define-once / use-many: you create the definitions via MCP, the SDK qualifies / claims / reads against them at runtime.\n\nThe pattern is always:\n\n1. Agent (via MCP): define the rule / achievement / streak / etc.\n2. Client SDK at runtime: track the event (`Amba.events.track(...)`) or qualify (`Amba.streaks.qualify(...)` / `Amba.challenges.claim(...)`).\n3. Server: auto-evaluate, mutate user state, return updated progress.\n\n## MCP tools\n\n### XP rules\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_xp_rules_create` | Auto-award XP on a matching event. | `{ project_id, name: \"Workout Completed\", event_name: \"workout_completed\", xp_amount: 50, max_per_day: 5, cooldown_seconds: 60 }` |\n| `amba_xp_rules_list` | List all XP rules. | `{ project_id }` |\n| `amba_xp_update_rule` | Edit a rule. | `{ project_id, rule_id, xp_amount: 75 }` |\n| `amba_xp_delete_rule` | Delete a rule. | `{ project_id, rule_id }` |\n| `amba_xp_list_users` | List users sorted by XP. | `{ project_id, limit: 50 }` |\n| `amba_users_get_xp` | Read a specific user's XP total + level. | `{ project_id, user_id }` |\n\n### Achievements\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_achievements_create` | Define an achievement that unlocks on criteria. | `{ project_id, key: \"first_workout\", name: \"First Workout\", description: \"Complete your first workout\", xp_reward: 100, criteria: { event: \"workout_completed\", count: 1 } }` |\n| `amba_achievements_list` | List all achievements. | `{ project_id }` |\n| `amba_achievements_get` | Read one. | `{ project_id, achievement_id }` |\n| `amba_achievements_update` | Edit an achievement. | `{ project_id, achievement_id, xp_reward: 150 }` |\n| `amba_achievements_delete` | Delete. | `{ project_id, achievement_id }` |\n\nCommon criteria shapes:\n\n```jsonc\n// Count-based\n{ \"event\": \"workout_completed\", \"count\": 5 }\n\n// Streak-based\n{ \"streak_key\": \"daily_workout\", \"min_length\": 7 }\n\n// XP-based\n{ \"xp_total\": 5000 }\n\n// Catalog-item-based\n{ \"item_owned\": \"premium_theme\" }\n```\n\n### Streaks\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_streaks_create` | Define a streak \u2014 what event qualifies, what period, freeze rules. | `{ project_id, key: \"daily_workout\", name: \"Daily Workout\", qualifying_event: \"workout_completed\", period: \"daily\", grace_period_hours: 6, freeze_enabled: true, max_freezes: 3 }` |\n| `amba_streaks_list` | List streaks. | `{ project_id }` |\n| `amba_streaks_update` | Edit a streak definition. | `{ project_id, streak_id, max_freezes: 5 }` |\n| `amba_streaks_delete` | Delete. | `{ project_id, streak_id }` |\n\nThe `key` is **immutable** after creation (the SDK identifies streaks by key, not UUID \u2014 changing it breaks live `Amba.streaks.qualify(...)` calls). Keys: lowercase letters, digits, `_`, `-`, 1-64 chars.\n\n### Leaderboards\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_leaderboards_create` | Define a leaderboard. | `{ project_id, name: \"Weekly XP\", metric: \"xp\", period: \"weekly\", max_entries: 100 }` |\n| `amba_leaderboards_list` | List. | `{ project_id }` |\n| `amba_leaderboards_get` | Read the current top entries. | `{ project_id, leaderboard_id, limit: 50 }` |\n| `amba_leaderboards_get_definition` | Read the definition without the entries (cheap). | `{ project_id, leaderboard_id }` |\n| `amba_leaderboards_update` | Edit. | `{ project_id, leaderboard_id, max_entries: 250 }` |\n| `amba_leaderboards_delete` | Delete. | `{ project_id, leaderboard_id }` |\n\nMetrics: `xp`, `streak`, `custom`. For `custom`, pass `custom_event` (e.g. `\"workout_completed\"`). Periods: `all_time`, `daily`, `weekly`, `monthly`.\n\n### Challenges\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_challenges_create` | Define a time-bounded challenge with a goal + reward. | `{ project_id, name: \"Spring Sprint\", goal_event: \"workout_completed\", goal_count: 5, starts_at: \"2026-06-01T00:00:00Z\", ends_at: \"2026-06-08T00:00:00Z\", reward: { xp: 500, currency: { code: \"gems\", amount: 50 } } }` |\n| `amba_challenges_list` | List. | `{ project_id }` |\n| `amba_challenges_get` | Read. | `{ project_id, challenge_id }` |\n| `amba_challenges_update` | Edit. | `{ project_id, challenge_id, ends_at: \"...\" }` |\n| `amba_challenges_delete` | Delete. | `{ project_id, challenge_id }` |\n| `amba_challenges_list_participants` | List opt-in participants + progress. | `{ project_id, challenge_id, limit: 100 }` |\n\n## SDK init per stack\n\n`Amba.configure(...)` runs first \u2014 see `amba://setup/identity`. The snippets below show only the gamification calls.\n\n### Expo / React Native\n\n```tsx\nimport { Amba } from '@layers/amba-expo'; // or '@layers/amba-react-native'\n\n// 1. Track the event that drives XP / achievements / streaks / leaderboards.\nawait Amba.events.track('workout_completed', { duration_minutes: 30 });\n\n// 2. Qualify the streak.\nconst streak = await Amba.streaks.qualify('daily_workout');\n\n// 3. Show newly-unlocked achievements.\nconst progress = await Amba.achievements.getProgress();\n\n// 4. Show current XP balance.\nconst xp = await Amba.xp.getBalance();\n\n// 5. Read the leaderboard.\nconst entries = await Amba.leaderboards.getEntries('Weekly XP', 50);\nconst myRank = await Amba.leaderboards.getMyRank('Weekly XP');\n\n// 6. Active challenges.\nconst active = await Amba.challenges.getActive();\nfor (const c of active) {\n const p = await Amba.challenges.getProgress(c.id);\n if (p.completed && !p.claimed) await Amba.challenges.claim(c.id);\n}\n```\n\n### Web\n\n```ts\nimport { Amba } from '@layers/amba-web';\n\nawait Amba.events.track('lesson_completed', { course_id: 'algebra-1' });\nconst streak = await Amba.streaks.qualify('daily_lesson');\nconst xp = await Amba.xp.getBalance();\nconst top = await Amba.leaderboards.getEntries('Weekly XP', 100);\n```\n\n### iOS (Swift)\n\n```swift\nimport Amba\n\ntry await Amba.events.track(\"workout_completed\", properties: [\"duration_minutes\": 30])\nlet streak = try await Amba.streaks.qualify(streakKey: \"daily_workout\")\nlet progress = try await Amba.achievements.getProgress()\nlet xp = try await Amba.xp.getBalance()\nlet entries = try await Amba.leaderboards.getEntries(key: \"Weekly XP\", limit: 50)\nlet active = try await Amba.challenges.getActive()\n```\n\n### Android (Kotlin)\n\n```kotlin\nAmba.events.track(\"workout_completed\", mapOf(\"duration_minutes\" to 30))\nval streak = Amba.streaks.qualify(\"daily_workout\")\nval xp = Amba.xp.getBalance()\nval entries = Amba.leaderboards.getEntries(\"Weekly XP\", limit = 50)\nval active = Amba.challenges.getActive()\n```\n\n### Flutter\n\n```dart\nimport 'package:amba/amba.dart';\n\nawait Amba.events.track('workout_completed', {'duration_minutes': 30});\nfinal streak = await Amba.streaks.qualify('daily_workout');\nfinal xp = await Amba.xp.getBalance();\nfinal entries = await Amba.leaderboards.getEntries('Weekly XP', limit: 50);\nfinal active = await Amba.challenges.getActive();\n```\n\n## Common follow-ups\n\nBatch into one or two questions.\n\n1. **What's the qualifying event for XP / achievements / streaks?** Most preset answers below \u2014 accept the suggestion or override:\n - fitness: `workout_completed`\n - education: `lesson_completed`\n - game: `level_completed` (or `match_played`)\n - productivity: `task_completed`\n - content_creator: `post_published`\n - ai_chatbot: `prompt_sent`\n\n2. **Default XP per qualifying event?** (defaults to `50`)\n\n3. **Streak period?**\n - Daily (recommended)\n - Weekly\n - None (skip streaks)\n\n4. **Streak freezes?**\n - 3 freezes/month (recommended \u2014 covers the occasional missed day)\n - Hard mode (no freezes)\n - None \u2014 don't enable freezes\n\n5. **Leaderboard scope:** (multi-select)\n - [x] Weekly (recommended \u2014 rotates, never gets stale)\n - [ ] All-time\n - [ ] Daily (high-engagement apps only)\n - [ ] Monthly\n\n6. **Which achievements to seed?** Defaults per preset (offer to create, or skip). For fitness: `first_workout`, `week_warrior`, `century_club`. For education: `first_lesson`, `dedicated_learner`, `course_complete`. For game: `first_win`, `streak_master`, `level_50`. For productivity: `first_task`, `inbox_zero`, `monthly_warrior`. For other presets, ask the user to confirm 3 achievements they want or skip.\n\n7. **Challenges:** seed an example weekly challenge?\n - Yes \u2014 \"5 workouts this week\" (or equivalent for the preset) ending next Sunday\n - No\n\n## Re-run behavior\n\n1. Before creating anything, list-then-diff:\n - `amba_xp_rules_list` \u2014 match on `name`. If exists, ask to update (`amba_xp_update_rule`) or skip.\n - `amba_achievements_list` \u2014 match on `key`. Achievement `key` is unique per project; collision \u2192 skip or update.\n - `amba_streaks_list` \u2014 match on `key`. **Never recreate** a streak with an existing key \u2014 the SDK calls would silently target a stale definition. Update instead.\n - `amba_leaderboards_list` \u2014 match on `name`. Collision \u2192 ask.\n - `amba_challenges_list` \u2014 match on `name + starts_at`.\n\n2. In the entry file: detect existing `Amba.events.track('<event_name>')` calls. If they already exist, don't add another. If they're missing for an event tied to a new XP rule, add a sample comment showing where to call `Amba.events.track`.\n\n3. If the user asks to \"remove gamification\": offer a soft path \u2014 pause XP rules and unschedule challenges rather than deleting definitions (deletion is irreversible and loses historical user XP / unlocks).\n";
|
|
14
|
+
export declare const AMBA_SETUP_GAMIFICATION_URI = "amba://setup/gamification";
|
|
15
|
+
export declare const AMBA_SETUP_GAMIFICATION_MIME = "text/markdown";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `amba://setup/identity` — per-surface playbook for end-user auth.
|
|
3
|
+
*
|
|
4
|
+
* Companion to `AMBA_SETUP_GUIDE_MD`. Step 3 of the main playbook
|
|
5
|
+
* tells the agent to fetch this resource when wiring identity.
|
|
6
|
+
*
|
|
7
|
+
* Surface scope: anonymous sessions, email + password, email OTP,
|
|
8
|
+
* SMS OTP, magic links, Sign in with Apple, Sign in with Google,
|
|
9
|
+
* account linking.
|
|
10
|
+
*
|
|
11
|
+
* Twin (local copy on the developer's machine):
|
|
12
|
+
* `packages/cli/skill-bundle/references/identity.md`. The local copy
|
|
13
|
+
* may list legacy MCP tool aliases (`amba_create_api_key`) for
|
|
14
|
+
* backwards-compat; this server-rendered copy advertises only the
|
|
15
|
+
* canonical names (`amba_api_keys_create`) so agents pattern-match
|
|
16
|
+
* against the modern shape. Drift gate in `amba-setup.test.ts`.
|
|
17
|
+
*/
|
|
18
|
+
export declare const AMBA_SETUP_IDENTITY_MD = "# Identity\n\nEnd-user authentication for an Amba project: anonymous sessions, email/password, email OTP, SMS OTP, magic links, Sign in with Apple, Sign in with Google, and account linking. All flows return an `AuthResult` containing a `user` + a session token; the SDK persists tokens to the platform's native secure storage and replays them on the next launch. Subsequent SDK calls (collections, push, XP, etc.) are authenticated as the signed-in user automatically.\n\nThere is no separate \"identity provisioning\" step \u2014 `auth` is the default surface for every project. Your job here is to (a) wire `Amba.configure(...)` plus the right sign-in calls into the user's entry file, and (b) where the user wants social sign-in, set the audience identifiers on the project so the server can verify identity tokens.\n\n## MCP tools\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_developer_me` | Verify the developer PAT and read the developer's profile. Pre-flight check before any provisioning. | `{}` |\n| `amba_projects_get` | Read a project's config (bundle id, OAuth client id, platform). | `{ project_id }` |\n| `amba_projects_update` | Set `bundle_id` (Apple audience) and `google_oauth_client_id` (Google audience). Required before Sign in with Apple / Google works. | `{ project_id, bundle_id: \"com.example.fitness\", google_oauth_client_id: \"1234.apps.googleusercontent.com\" }` |\n| `amba_users_list` | Browse end-users (app_users) of the project \u2014 useful as a smoke check after the first sign-in. | `{ project_id, limit: 20 }` |\n| `amba_users_get` | Fetch a single app_user by id. | `{ project_id, user_id }` |\n| `amba_users_bulk_update` | Set custom properties on many users at once. | `{ project_id, user_ids: [...], properties: { tier: \"trial\" } }` |\n| `amba_api_keys_create` | Mint additional client/server keys (e.g. a separate `production` key). | `{ project_id, key_type: \"client\", environment: \"production\" }` |\n| `amba_api_keys_delete` | Revoke a leaked key. | `{ project_id, api_key_id }` |\n| `amba_roles_assign` | Grant an RBAC role to an app_user (admin / moderator / etc.). | `{ project_id, user_id, role_id }` |\n\nThere's no `amba_auth_*` namespace \u2014 auth is owned by the SDK on the client side, and there are no provisioning calls for it beyond setting the project's audience identifiers. If the user wants Apple / Google sign-in, the **mandatory** preflight is:\n\n```\namba_projects_update({\n project_id: \"<from Step 0>\",\n bundle_id: \"<their iOS bundle id>\",\n google_oauth_client_id: \"<their Google OAuth client id>\"\n})\n```\n\nWithout this, the server rejects identity tokens with `AUDIENCE_NOT_CONFIGURED` and the user thinks Amba is broken. If they don't know their bundle id, ask; if they don't have a Google OAuth client yet, tell them to create one at `console.cloud.google.com` and link it later via `amba_projects_update`.\n\n## SDK init per stack\n\n### Expo\n\n```bash\nnpx expo install @layers/amba-expo @react-native-async-storage/async-storage\n```\n\nIn `app/_layout.tsx` (or whatever your root layout is):\n\n```tsx\nimport { useEffect } from 'react';\nimport { Amba } from '@layers/amba-expo';\n\nexport default function RootLayout() {\n useEffect(() => {\n (async () => {\n await Amba.configure({\n apiKey: process.env.EXPO_PUBLIC_AMBA_CLIENT_KEY!,\n });\n await Amba.auth.signInAnonymously();\n })();\n }, []);\n return /* \u2026 */ null;\n}\n```\n\nFor Sign in with Apple, add `expo-apple-authentication`; capture the identity token and call `Amba.auth.signInWithApple(identityToken)`. For Sign in with Google, use `expo-auth-session/providers/google` and call `Amba.auth.signInWithGoogle(id_token)`.\n\n### React Native (bare)\n\n```bash\nnpm install @layers/amba-react-native @react-native-async-storage/async-storage\n```\n\n```tsx\nimport { Amba } from '@layers/amba-react-native';\n\nawait Amba.configure({ apiKey: process.env.AMBA_CLIENT_KEY! });\nawait Amba.auth.signInAnonymously();\n\n// Email OTP\nawait Amba.auth.requestEmailOtp(email);\nawait Amba.auth.verifyEmailOtp(email, code);\n\n// SMS OTP (E.164, leading \"+\")\nawait Amba.auth.requestSmsOtp('+14155551234');\nawait Amba.auth.verifySmsOtp('+14155551234', code);\n```\n\n### Web (browser / Next.js / Vite / Remix)\n\n```bash\nnpm install @layers/amba-web\n# Optional React hooks:\nnpm install @layers/amba-react\n```\n\n```ts\nimport { Amba } from '@layers/amba-web';\n\nawait Amba.configure({ apiKey: import.meta.env.VITE_AMBA_CLIENT_KEY });\nawait Amba.auth.signInAnonymously();\n\n// Magic link\nawait Amba.auth.requestMagicLink('user@example.com');\nconst token = new URLSearchParams(window.location.search).get('token');\nif (token) await Amba.auth.verifyMagicLink(token);\n```\n\nNext.js \u2014 call `Amba.configure(...)` once at the top of `app/layout.tsx` (or `pages/_app.tsx`). Anonymous sign-in should happen on the client; do not call SDK functions in server components.\n\n### iOS (Swift, SPM)\n\nIn `Package.swift` (or Xcode \u2192 File \u2192 Add Package Dependencies):\n\n```swift\n.package(url: \"https://github.com/layers/amba-sdk-ios\", from: \"1.0.0\")\n```\n\n```swift\nimport SwiftUI\nimport Amba\n\n@main\nstruct MyApp: App {\n init() {\n Task {\n try await Amba.configure(apiKey: ProcessInfo.processInfo.environment[\"AMBA_CLIENT_KEY\"]!)\n try await Amba.auth.signInAnonymously()\n }\n }\n var body: some Scene { WindowGroup { ContentView() } }\n}\n```\n\nSign in with Apple \u2014 use Apple's `AuthenticationServices` framework; pass the `identityToken` to `Amba.auth.signInWithApple`. Sign in with Google \u2014 use Google's `GoogleSignIn-iOS` SDK; pass the `idToken` to `Amba.auth.signInWithGoogle`.\n\n> Add the \"Sign in with Apple\" capability in **Xcode \u2192 target \u2192 Signing & Capabilities \u2192 + Capability**. Without it, the Apple auth call fails before it reaches Amba.\n\n### Android (Kotlin)\n\nIn `app/build.gradle.kts`:\n\n```kotlin\ndependencies {\n implementation(\"com.layers.amba:amba-sdk-android:0.1.0\")\n}\n```\n\nIn your `Application` subclass:\n\n```kotlin\nimport android.app.Application\nimport com.layers.amba.Amba\nimport kotlinx.coroutines.GlobalScope\nimport kotlinx.coroutines.launch\n\nclass MyApp : Application() {\n override fun onCreate() {\n super.onCreate()\n GlobalScope.launch {\n Amba.configure(apiKey = BuildConfig.AMBA_CLIENT_KEY)\n Amba.auth.signInAnonymously()\n }\n }\n}\n```\n\nSign in with Google \u2014 use Google's Credential Manager flow, capture `idToken`, then `Amba.auth.signInWithGoogle(idToken = idToken)`.\n\n### Flutter\n\n```yaml\ndependencies:\n amba: ^1.0.0\n```\n\n```dart\nimport 'package:amba/amba.dart';\n\nFuture<void> main() async {\n WidgetsFlutterBinding.ensureInitialized();\n await Amba.configure(apiKey: const String.fromEnvironment('AMBA_CLIENT_KEY'));\n await Amba.auth.signInAnonymously();\n runApp(const MyApp());\n}\n```\n\nPass the key in: `flutter run --dart-define=AMBA_CLIENT_KEY=$AMBA_CLIENT_KEY`. Apple: `sign_in_with_apple` plugin \u2192 `Amba.auth.signInWithApple`. Google: `google_sign_in` plugin \u2192 `Amba.auth.signInWithGoogle`.\n\n## Common follow-ups\n\nAsk one bundled multi-choice \u2014 don't drip-feed.\n\n1. **Which sign-in methods do you want?** (multi-select)\n - [x] Anonymous (recommended \u2014 call at app start, lets users use the app immediately)\n - [ ] Email + password\n - [ ] Email OTP (6-digit code emailed)\n - [ ] Magic link (single click email)\n - [ ] Phone OTP / SMS (E.164, requires SMS provider configured)\n - [ ] Sign in with Apple (iOS / web; required for iOS apps that have any third-party auth per App Store guideline 4.8)\n - [ ] Sign in with Google (Android / iOS / web)\n\n2. **If Apple is selected:** what's your iOS bundle id?\n\n3. **If Google is selected:** what's your Google OAuth client id? Format: `123456789-abc.apps.googleusercontent.com`. If they don't have one, point them at `console.cloud.google.com` and proceed without it \u2014 they can paste it later via `amba_projects_update`.\n\n4. **If anonymous is selected:** when do you want users to upgrade?\n - On a \"Save your progress\" prompt (offer Apple/Google linking)\n - Behind a paywall / premium gate\n - Never auto-prompt (user upgrades from settings)\n - Defaults to \"never auto-prompt\".\n\n## Re-run behavior\n\nOn a second invocation that targets identity:\n\n1. Call `amba_projects_get({ project_id })` to read current `bundle_id` and `google_oauth_client_id`. Compare to what the user gave you:\n - If both already set \u2192 no `amba_projects_update` needed.\n - If user is adding a new social provider that needs an audience \u2192 call `amba_projects_update` with just the new field. Don't blow away the existing one.\n\n2. For new sign-in methods, append the per-method code block to the existing entry file *without* re-emitting `Amba.configure(...)` (it's already there). Detection: search for `Amba.configure` in the entry file; if present, skip the configure block.\n\n3. If the user asks to \"switch from anonymous to email-only\" or similar destructive change, **don't auto-do it**. Explain that existing anonymous user data would be unreachable without a link flow, then offer:\n - Add the new method alongside anonymous (recommended)\n - Add a forced upgrade prompt in onboarding\n - Migrate manually via `Amba.auth.linkEmailOtp(email, code)` \u2014 keeps existing user data\n";
|
|
19
|
+
export declare const AMBA_SETUP_IDENTITY_URI = "amba://setup/identity";
|
|
20
|
+
export declare const AMBA_SETUP_IDENTITY_MIME = "text/markdown";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `amba://setup/infrastructure` — per-surface playbook for custom plumbing.
|
|
3
|
+
*
|
|
4
|
+
* Companion to `AMBA_SETUP_GUIDE_MD`. Step 3 of the main playbook
|
|
5
|
+
* tells the agent to fetch this resource when wiring infrastructure.
|
|
6
|
+
*
|
|
7
|
+
* Surface scope: collections (typed DB tables), functions
|
|
8
|
+
* (serverless), analytics, AI prompts, secrets, configs / feature
|
|
9
|
+
* flags, third-party integrations, media (file storage + CDN),
|
|
10
|
+
* sites (static asset hosting).
|
|
11
|
+
*
|
|
12
|
+
* Twin: `packages/cli/skill-bundle/references/infrastructure.md`.
|
|
13
|
+
* Drift gate in `amba-setup.test.ts`. No vendor leakage (Postgres /
|
|
14
|
+
* Temporal / R2 names scrubbed before exposing on the wire).
|
|
15
|
+
*/
|
|
16
|
+
export declare const AMBA_SETUP_INFRASTRUCTURE_MD = "# Infrastructure\n\nThe plumbing that sits behind every other surface: custom database tables (Collections \u2014 schema-first, per-tenant), serverless functions (run server-side code without standing up a backend), analytics (events + sessions), AI prompts (managed LLM templates, callable from the SDK with per-tenant keys), secrets, runtime configs, feature flags, third-party integrations (RevenueCat / Superwall / Resend / Stripe / etc.), media (file storage + CDN), and sites (static asset hosting at `*.app.amba.host`).\n\nIf gamification, economy, and social are the playable surface, **infrastructure is what you build a custom product on top of**. Anything that doesn't fit the canned surfaces lands here.\n\n## MCP tools\n\n### Collections (typed tables)\n\nA collection is a schema-first table inside the project's isolated tenant database. You describe the columns, the server creates the table and any indexes. Rows are scoped to the signed-in `app_user` automatically (server-enforced auto row-level isolation) for SDK clients \u2014 admin tools bypass this.\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_collections_create` | Create a typed collection. | `{ project_id, name: \"todos\", columns: [{ name: \"title\", type: \"text\", nullable: false }, { name: \"done\", type: \"boolean\", nullable: false, default: false }, { name: \"due_at\", type: \"timestamptz\", nullable: true }] }` |\n| `amba_collections_list` | List collections in this project. | `{ project_id }` |\n| `amba_collections_get` | Read one collection's schema. | `{ project_id, collection_name: \"todos\" }` |\n| `amba_collections_alter` | Add / drop columns, add / drop indexes. | `{ project_id, collection_name: \"todos\", add_columns: [{ name: \"priority\", type: \"int\", nullable: true }] }` |\n| `amba_collections_delete` | Drop the table (destructive). | `{ project_id, collection_name }` |\n| `amba_admin_insert_row` | Insert a row as the developer (bypasses user-scope). | `{ project_id, collection: \"todos\", row: { title: \"Sample\", done: false } }` |\n| `amba_admin_list_rows` | Read rows as the developer. | `{ project_id, collection: \"todos\", limit: 100 }` |\n| `amba_client_insert_row` | Insert as an end-user. Requires `api_key` + `session_token`. | `{ project_id, api_key, session_token, collection: \"todos\", row: {...} }` |\n| `amba_client_list_rows` | Read as an end-user (auto user-scoped). | `{ project_id, api_key, session_token, collection: \"todos\" }` |\n| `amba_client_get_row` | Get one row by id (end-user). | `{ project_id, api_key, session_token, collection, row_id }` |\n| `amba_client_update_row` | Update one row (end-user). | `{ project_id, api_key, session_token, collection, row_id, patch: {...} }` |\n| `amba_client_delete_row` | Delete one row (end-user). | `{ project_id, api_key, session_token, collection, row_id }` |\n| `amba_client_count_rows` | Count rows matching a filter. | `{ project_id, api_key, session_token, collection, filter: {...} }` |\n| `amba_client_find_rows` | Filter / sort / paginate rows. | `{ project_id, api_key, session_token, collection, filter: {...}, order_by: [...], limit: 50 }` |\n| `amba_client_find_nearest_rows` | Vector-similarity search (rows with a `vector(<dim>)` column). | `{ project_id, api_key, session_token, collection, vector_column: \"embedding\", query_vector: [...], k: 10 }` |\n\nColumn types: `text`, `int`, `bigint`, `float`, `boolean`, `timestamptz`, `date`, `json`, `jsonb`, `uuid`, `vector(<dim>)` (e.g. `vector(1536)` for OpenAI embeddings).\n\n### Functions (serverless code)\n\nRun user code in a sandbox triggered by HTTP, cron, or webhook. The function gets the tenant connection automatically via injected env.\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_functions_deploy` | Deploy a function from source. | `{ project_id, name: \"send_welcome_email\", runtime: \"node22\", source: \"export default async (req) => { ... }\", trigger: { type: \"http\" } }` |\n| `amba_functions_list` | List functions. | `{ project_id }` |\n| `amba_functions_get` | Read function metadata. | `{ project_id, function_id }` |\n| `amba_functions_get_logs` | Recent invocation logs. | `{ project_id, function_id, limit: 100 }` |\n| `amba_functions_delete` | Delete a function. | `{ project_id, function_id }` |\n| `amba_functions_schedule` | Attach a cron schedule. | `{ project_id, function_id, cron: \"0 9 * * *\", timezone: \"America/Los_Angeles\" }` |\n| `amba_functions_pause_schedule` | Pause a scheduled trigger without deleting it. | `{ project_id, function_id }` |\n| `amba_functions_resume_schedule` | Resume. | `{ project_id, function_id }` |\n| `amba_functions_trigger_schedule` | Fire a scheduled function ad-hoc (testing). | `{ project_id, function_id }` |\n\n### AI prompts\n\nManaged LLM templates: stored prompt with model + system message + variables, callable by name from the SDK. The actual LLM call is rewritten per-tenant \u2014 the customer's API keys (Anthropic / OpenAI) live in the tenant secrets, never on the device.\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_ai_prompts_create` | Create a prompt template. | `{ project_id, key: \"summarize\", model: \"claude-opus-4-5\", system: \"Summarize the user's text in 2 sentences.\", variables: [\"text\"] }` |\n| `amba_ai_prompts_list` | List prompts. | `{ project_id }` |\n| `amba_ai_prompts_get` | Read one prompt. | `{ project_id, key }` |\n| `amba_ai_prompts_update` | Edit a prompt. | `{ project_id, key, system: \"...\" }` |\n| `amba_ai_prompts_invoke` | Invoke a prompt server-side (admin testing). | `{ project_id, key, variables: { text: \"...\" } }` |\n| `amba_ai_prompts_delete` | Delete. | `{ project_id, key }` |\n\n### Analytics + events + sessions\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_analytics_get` | Top-level metrics dashboard (MAU, DAU, retention). | `{ project_id, period: \"7d\" }` |\n| `amba_events_list` | Browse raw events. | `{ project_id, limit: 100, since: \"2026-05-19T00:00:00Z\" }` |\n| `amba_events_count` | Count events matching a filter. | `{ project_id, event: \"workout_completed\", since: \"...\" }` |\n| `amba_sessions_list` | List user sessions. | `{ project_id, limit: 50 }` |\n| `amba_sessions_analytics` | Session-level metrics. | `{ project_id, period: \"7d\" }` |\n| `amba_users_list_events` | Per-user event history. | `{ project_id, user_id }` |\n| `amba_users_export` | Export the full user list. | `{ project_id, format: \"csv\" }` |\n\n### Secrets + configs + integrations\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_secrets_set` | Set a tenant secret (encrypted at rest). | `{ project_id, name: \"OPENAI_API_KEY\", value: \"sk-...\" }` |\n| `amba_secrets_get` | Read a secret (returns `\"<redacted>\"` unless explicitly requested). | `{ project_id, name }` |\n| `amba_secrets_list` | List secret names. | `{ project_id }` |\n| `amba_secrets_delete` | Delete. | `{ project_id, name }` |\n| `amba_configs_create` | Create a runtime config value (read from SDK as `Amba.config.fetch()`). | `{ project_id, key: \"primary_color\", value: \"#ff0066\", segment_id: null }` |\n| `amba_configs_list` | List configs. | `{ project_id }` |\n| `amba_configs_update` | Edit. | `{ project_id, config_id, value: \"...\" }` |\n| `amba_configs_delete` | Delete. | `{ project_id, config_id }` |\n| `amba_integrations_list` | List third-party integrations. | `{ project_id }` |\n| `amba_integrations_configure` | Configure a provider. | `{ project_id, provider: \"revenuecat\", config: { webhook_secret: \"...\", default_offering: \"...\" } }` |\n| `amba_integrations_set` | Set/replace integration config wholesale. | `{ project_id, provider, config }` |\n| `amba_integrations_patch` | Patch one field. | `{ project_id, provider, patch: { webhook_secret: \"...\" } }` |\n| `amba_integrations_test` | Send a test event to a configured provider. | `{ project_id, provider }` |\n\n### Media (file storage + CDN)\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_media_upload` | Upload a file (returns a tenant-scoped URL). | `{ project_id, name: \"logo.png\", content_type: \"image/png\", data: \"<base64>\" }` |\n| `amba_media_list` | List files. | `{ project_id, folder: \"/\", limit: 100 }` |\n| `amba_media_delete` | Delete a file. | `{ project_id, file_id }` |\n| `amba_media_create_folder` | Create a logical folder. | `{ project_id, path: \"/uploads/avatars\" }` |\n| `amba_media_list_folders` | List folders. | `{ project_id }` |\n| `amba_media_delete_folder` | Delete a folder (must be empty). | `{ project_id, path }` |\n\n### Sites (static asset hosting)\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_sites_deploy` | Deploy a static site bundle (zip / tar). | `{ project_id, name: \"marketing\", bundle: \"<base64>\", index: \"index.html\" }` |\n| `amba_sites_list` | List sites. | `{ project_id }` |\n| `amba_sites_get` | Read a site. | `{ project_id, site_id }` |\n| `amba_sites_add_domain` | Attach a custom domain. | `{ project_id, site_id, domain: \"marketing.example.com\" }` |\n| `amba_sites_list_domains` | List domains on a site. | `{ project_id, site_id }` |\n| `amba_sites_remove_domain` | Detach a domain. | `{ project_id, site_id, domain }` |\n| `amba_sites_delete` | Delete a site. | `{ project_id, site_id }` |\n\n## SDK init per stack\n\n`Amba.configure(...)` runs first. The infrastructure surfaces \u2014 collections, AI, config, flags, events \u2014 are SDK-side reads; the snippets below show what the client calls look like.\n\n### Expo / React Native\n\n```tsx\nimport { Amba } from '@layers/amba-expo';\n\n// Collections \u2014 typed table, user-scoped reads + writes\ntype Todo = { id: string; title: string; done: boolean; created_at: string };\n\nconst { data: todos } = await Amba.collections.find<Todo>('todos', {\n filter: Amba.collections.where.eq('done', false),\n order: [{ column: 'created_at', direction: 'desc' }],\n limit: 50,\n});\n\nconst newTodo = await Amba.collections.insert('todos', { title: 'Ship the app', done: false });\nawait Amba.collections.update('todos', newTodo.id, { done: true });\nawait Amba.collections.delete('todos', newTodo.id);\n\n// AI \u2014 call a managed prompt\nconst response = await Amba.ai.anthropic.messages.create({\n prompt_key: 'summarize',\n variables: { text: 'A long article about backend services \u2026' },\n});\n\n// Track an analytics event\nawait Amba.events.track('button_clicked', { button: 'cta' });\n\n// Read runtime config\nconst config = await Amba.config.fetch();\n\n// Read a feature flag\nconst showBeta = await Amba.flags.get('beta_feature');\n\n// Diagnostics \u2014 wire-verify\nconst ping = await Amba.diagnostics.ping();\nif (!ping.ok) console.error('Amba misconfigured:', ping);\n```\n\n### Web\n\n```ts\nimport { Amba } from '@layers/amba-web';\n\nconst { data: todos } = await Amba.collections.find('todos', {\n filter: Amba.collections.where.eq('done', false),\n limit: 50,\n});\nawait Amba.collections.insert('todos', { title: 'Ship', done: false });\nawait Amba.events.track('page_view', { path: location.pathname });\n```\n\nWith `@layers/amba-react`:\n\n```tsx\nimport { useCollection, useFlag } from '@layers/amba-react';\n\nfunction TodoList() {\n const { data: todos, loading, refetch } = useCollection<{ id: string; title: string }>('todos');\n const showArchive = useFlag('archive_todos');\n if (loading) return <Spinner />;\n return (\n <ul>\n {todos?.map(t => <li key={t.id}>{t.title}</li>)}\n {showArchive && <ArchiveButton onArchive={refetch} />}\n </ul>\n );\n}\n```\n\n### iOS (Swift)\n\n```swift\nimport Amba\n\nstruct Todo: Codable {\n let id: String\n let title: String\n let done: Bool\n}\n\nlet response = try await Amba.collections.find(\"todos\", as: Todo.self)\n_ = try await Amba.collections.insert(\"todos\", row: [\"title\": \"Ship\", \"done\": false])\n\nlet config = try await Amba.config.fetch()\nlet showBeta = try await Amba.flags.get(name: \"beta_feature\")\ntry await Amba.events.track(\"app_opened\", properties: [\"source\": \"deep_link\"])\n\nlet reply = try await Amba.ai.anthropic.messages.create(\n promptKey: \"summarize\",\n variables: [\"text\": \"A long article...\"]\n)\n```\n\n### Android (Kotlin)\n\n```kotlin\ndata class Todo(val id: String, val title: String, val done: Boolean)\n\nval todos = Amba.collections.find<Todo>(\"todos\")\nAmba.collections.insert(\"todos\", mapOf(\"title\" to \"Ship\", \"done\" to false))\n\nval config = Amba.config.fetch()\nval showBeta = Amba.flags.get(\"beta_feature\")\nAmba.events.track(\"app_opened\", mapOf(\"source\" to \"deep_link\"))\n```\n\n### Flutter\n\n```dart\nimport 'package:amba/amba.dart';\n\nfinal response = await Amba.collections.find('todos', limit: 50);\nawait Amba.collections.insert('todos', {'title': 'Ship', 'done': false});\nfinal config = await Amba.config.fetch();\nfinal showBeta = await Amba.flags.get('beta_feature');\nawait Amba.events.track('app_opened', {'source': 'deep_link'});\n```\n\n## Common follow-ups\n\nBatch.\n\n1. **Custom data tables (collections):** any domain-specific tables to create?\n - Yes \u2014 I'll list them. (For each: name + columns + types.)\n - No, just use the canned Amba surfaces (auth, push, gamification, etc.)\n - Auto-create from the existing code's models \u2014 read `lib/models/`, `src/types/`, `Models/`, infer column lists, confirm with me.\n\n2. **Custom backend logic (functions):** any server-side code to deploy?\n - Yes \u2014 describe what it should do. (Then offer to scaffold a function template and deploy.)\n - No\n\n3. **AI features:** want managed LLM prompts?\n - Yes \u2014 what's the use case? (summarize, translate, classify, generate, custom)\n - No\n\n4. **Analytics:** which tracker do you want?\n - Only Amba's built-in events (recommended \u2014 already wired)\n - Amba + Mixpanel / PostHog / Segment forwarding (configure via `amba_integrations_configure`)\n - None (rarely useful \u2014 events drive XP / achievements / streaks; disabling cripples gamification)\n\n5. **Third-party integrations to set up:**\n - [ ] RevenueCat (IAP / subscriptions on iOS + Android)\n - [ ] Superwall (paywall A/B)\n - [ ] Resend (transactional email)\n - [ ] Stripe (web payments / subscriptions)\n - [ ] Mixpanel / PostHog / Segment (analytics forwarding)\n - [ ] OpenAI / Anthropic (LLM keys \u2014 required for `Amba.ai.*` calls)\n\n6. **Feature flags:** seed any starter flags?\n - Yes \u2014 wire `beta_feature` (off by default) so I can ship the wiring before the feature exists\n - No\n\n7. **Static site:** want a marketing page hosted under your tenant subdomain?\n - Yes \u2014 scaffold and deploy a 1-page index\n - No\n\n## Re-run behavior\n\n1. Before creating:\n - `amba_collections_list` \u2014 match on `name`. Collisions: never silently recreate (data loss). Offer `amba_collections_alter` to add new columns instead.\n - `amba_functions_list` \u2014 match on `name`. Collisions: ask to redeploy (with the new source) or skip.\n - `amba_ai_prompts_list` \u2014 match on `key`. Same.\n - `amba_integrations_list` \u2014 match on `provider`. Same.\n - `amba_configs_list` \u2014 match on `key`. Same.\n\n2. **Never call `amba_collections_delete` on re-run unless the user explicitly asks** \u2014 this drops the underlying table and every row in it across every user of the tenant.\n\n3. For functions: re-deploying replaces source in place (versioned server-side). It's safe to call `amba_functions_deploy` with the same name + new source.\n\n4. For integrations: if a provider is already configured, prefer `amba_integrations_patch` (partial update) over `amba_integrations_set` (full replace).\n\n5. Secrets: don't list secret values in chat output, even on read. Just confirm \"OPENAI_API_KEY is set\" / \"not set\".\n";
|
|
17
|
+
export declare const AMBA_SETUP_INFRASTRUCTURE_URI = "amba://setup/infrastructure";
|
|
18
|
+
export declare const AMBA_SETUP_INFRASTRUCTURE_MIME = "text/markdown";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `amba://setup/social` — per-surface playbook for social graph & UGC.
|
|
3
|
+
*
|
|
4
|
+
* Companion to `AMBA_SETUP_GUIDE_MD`. Step 3 of the main playbook
|
|
5
|
+
* tells the agent to fetch this resource when wiring social.
|
|
6
|
+
*
|
|
7
|
+
* Surface scope: friendships, groups, feeds, messaging, reviews,
|
|
8
|
+
* moderation.
|
|
9
|
+
*
|
|
10
|
+
* Twin: `packages/cli/skill-bundle/references/social.md`.
|
|
11
|
+
* Drift gate in `amba-setup.test.ts`.
|
|
12
|
+
*/
|
|
13
|
+
export declare const AMBA_SETUP_SOCIAL_MD = "# Social\n\nThe social graph and everything that runs on top of it: friendships (with block lists), groups (guilds / clubs), activity feeds with rule-driven filters, 1:1 + group messaging, user-generated reviews, and the moderation queue + trust system that keeps it all from rotting.\n\nMost of this is \"create-the-rule, let-the-SDK-call-it\" \u2014 feeds are rule-driven, moderation has its own queue, friend graph is bidirectional. Only the structural pieces (feed rules, moderation rules, group capacity) are MCP-provisioned; the runtime calls (`Amba.friends.sendRequest`, `Amba.messaging.sendMessage`, `Amba.feeds.getActivity`) are SDK-side.\n\n## MCP tools\n\n### Friendships\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_friendships_list` | List friendships in a project (admin / moderation view). | `{ project_id, status: \"accepted\", limit: 100 }` |\n| `amba_friendships_get_stats` | Aggregate metrics \u2014 accepted, pending, blocked. | `{ project_id }` |\n| `amba_friendships_delete` | Admin-delete a friendship row. | `{ project_id, friendship_id }` |\n\nFriend requests + accept/decline + block are SDK-side. There's no MCP tool to create a friendship \u2014 by design, only end-users can.\n\n### Groups\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_groups_create` | Create a group (guild / club / squad). | `{ project_id, name: \"Morning Runners\", owner_id: \"u_\u2026\", description: \"5am crew\", is_public: true, max_members: 100 }` |\n| `amba_groups_list` | List groups. | `{ project_id, limit: 50 }` |\n| `amba_groups_update` | Edit a group (rename, change visibility, change cap). | `{ project_id, group_id, max_members: 500 }` |\n| `amba_groups_delete` | Delete a group. | `{ project_id, group_id }` |\n| `amba_groups_list_members` | List members. | `{ project_id, group_id }` |\n| `amba_groups_update_member` | Change a member's role (promote to admin / mute). | `{ project_id, group_id, member_id, role: \"admin\" }` |\n| `amba_groups_remove_member` | Kick a member. | `{ project_id, group_id, member_id }` |\n\n### Feeds\n\nActivity feeds are rule-driven: each rule says \"events of type X published by users matching Y should appear in feed Z\". Items land in feeds automatically when matching events fire.\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_feeds_rules_create` | Define a feed rule. | `{ project_id, feed: \"global\", event: \"post_published\", filter: { all: [{ field: \"user.is_creator\", op: \"==\", value: true }] } }` |\n| `amba_feeds_list_rules` | List rules. | `{ project_id, feed }` |\n| `amba_feeds_patch_rule` | Edit a rule. | `{ project_id, rule_id, filter: {...} }` |\n| `amba_feeds_delete_rule` | Delete a rule. | `{ project_id, rule_id }` |\n| `amba_feeds_list_items` | List items in a feed (admin / debugging). | `{ project_id, feed: \"global\", limit: 50 }` |\n| `amba_feeds_delete_item` | Remove a single feed item (moderation). | `{ project_id, feed, item_id }` |\n\nConventional feed names: `global` (everyone), `following` (just users you follow), `group:<group_id>` (a single group's feed).\n\n### Messaging\n\nMostly SDK-side \u2014 admin tools are for moderation + stats.\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_messaging_list_conversations` | List conversations (admin / moderation view). | `{ project_id, limit: 50 }` |\n| `amba_messaging_list_messages` | List messages in a conversation. | `{ project_id, conversation_id, limit: 100 }` |\n| `amba_messaging_delete_message` | Hard-delete a message (moderation). | `{ project_id, conversation_id, message_id }` |\n| `amba_messaging_get_stats` | Aggregate messaging metrics. | `{ project_id }` |\n\n### Moderation\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_moderation_configure` | Set the project's default moderation policy. | `{ project_id, auto_review_threshold: 0.8, default_action: \"queue\", auto_block_threshold: 0.95 }` |\n| `amba_moderation_list_rules` | List rules. | `{ project_id }` |\n| `amba_moderation_update_rule` | Edit a rule. | `{ project_id, rule_id, action: \"block\" }` |\n| `amba_moderation_delete_rule` | Delete a rule. | `{ project_id, rule_id }` |\n| `amba_moderation_queue_list` | List pending reports. | `{ project_id, status: \"pending\", limit: 50 }` |\n| `amba_moderation_queue_get` | Fetch one report. | `{ project_id, report_id }` |\n| `amba_moderation_queue_approve` | Approve (resolve with no action). | `{ project_id, report_id, reason: \"false positive\" }` |\n| `amba_moderation_queue_reject` | Reject (take action \u2014 hide/delete content, ban user). | `{ project_id, report_id, action: \"delete_message\", reason: \"spam\" }` |\n| `amba_moderation_queue_escalate` | Escalate to a senior moderator. | `{ project_id, report_id }` |\n| `amba_moderation_list_trust` | List per-user trust scores. | `{ project_id, limit: 100 }` |\n| `amba_moderation_set_trust` | Manually bump a user's trust score. | `{ project_id, user_id, trust_score: 0.9, reason: \"verified power user\" }` |\n\n### Reviews\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_reviews_list` | List reviews. | `{ project_id, target_type: \"catalog_item\", target_id: \"...\", limit: 50 }` |\n| `amba_reviews_list_items` | List the things that have reviews on them. | `{ project_id, target_type: \"catalog_item\" }` |\n| `amba_reviews_patch` | Edit / hide a review (moderation). | `{ project_id, review_id, hidden: true }` |\n| `amba_reviews_delete` | Delete a review. | `{ project_id, review_id }` |\n| `amba_reviews_get_stats` | Aggregate review stats. | `{ project_id, target_type, target_id }` |\n| `amba_reviews_export` | Export to CSV / JSON. | `{ project_id, format: \"csv\" }` |\n\n## SDK init per stack\n\n`Amba.configure(...)` runs first. Snippets below are the social-only calls.\n\n### Expo / React Native\n\n```tsx\nimport { Amba } from '@layers/amba-expo';\n\n// Friend graph\nconst friendship = await Amba.friends.sendRequest(otherUserId);\nconst friends = await Amba.friends.getFriends();\nawait Amba.friends.acceptRequest(friendship.id);\nawait Amba.friends.removeFriend(otherUserId);\nawait Amba.friends.blockUser(otherUserId);\n\n// Groups\nconst group = await Amba.groups.create({ name: 'Morning Runners' });\n\n// Messaging\nconst conv = await Amba.messaging.createConversation({\n participant_ids: [otherUserId],\n type: 'direct',\n});\nconst msg = await Amba.messaging.sendMessage(conv.id, { body: 'hi' });\nconst inbox = await Amba.messaging.conversations();\nawait Amba.messaging.markRead(conv.id);\n\n// Feeds\nconst { items, next_cursor } = await Amba.feeds.getActivity('global');\n\n// Reviews\nconst reviews = await Amba.reviews.list('catalog_item', 'premium_theme');\nawait Amba.reviews.create({\n target_type: 'catalog_item',\n target_id: 'premium_theme',\n rating: 5,\n body: 'Beautiful theme!',\n});\n\n// Moderation \u2014 user-side\nawait Amba.moderation.reportUser({ reported_user_id: otherUserId, reason: 'harassment' });\nawait Amba.moderation.reportContent({ target_type: 'message', target_id: msg.id, reason: 'spam' });\n```\n\n### Web\n\n```ts\nimport { Amba } from '@layers/amba-web';\n\nawait Amba.friends.sendRequest(otherUserId);\nconst conv = await Amba.messaging.createConversation({\n participant_ids: [otherUserId],\n type: 'direct',\n});\nawait Amba.messaging.sendMessage(conv.id, { body: 'hello' });\nconst activity = await Amba.feeds.getActivity('global');\n```\n\n### iOS (Swift)\n\n```swift\nimport Amba\n\nlet friendship = try await Amba.friends.sendRequest(userId: otherUserId)\nlet conv = try await Amba.messaging.createConversation(CreateConversationRequest(\n participantIds: [otherUserId], type: .direct\n))\n_ = try await Amba.messaging.sendMessage(\n conversationId: conv.id,\n request: SendMessageRequest(body: \"hi\")\n)\nlet feed = try await Amba.feeds.getActivity(feed: \"global\")\n```\n\n### Android (Kotlin)\n\n```kotlin\nval friendship = Amba.friends.sendRequest(otherUserId)\nval conv = Amba.messaging.createConversation(\n CreateConversationRequest(participantIds = listOf(otherUserId), type = \"direct\")\n)\nval msg = Amba.messaging.sendMessage(\n conversationId = conv.id,\n request = SendMessageRequest(body = \"hi\")\n)\nval feed = Amba.feeds.getActivity(\"global\")\n```\n\n### Flutter\n\n```dart\nimport 'package:amba/amba.dart';\n\nfinal friendship = await Amba.friends.sendRequest(otherUserId);\nfinal conv = await Amba.messaging.createConversation(\n CreateConversationRequest(\n participantIds: [otherUserId],\n type: ConversationType.direct,\n ),\n);\nfinal msg = await Amba.messaging.sendMessage(conv.id, SendMessageRequest(body: 'hi'));\nfinal feed = await Amba.feeds.getActivity('global');\n```\n\n## Common follow-ups\n\nBatch.\n\n1. **Which social features?** (multi-select)\n - [x] Friend graph (friend requests, accept/decline, block, unfriend)\n - [ ] Groups / guilds (multi-user)\n - [x] 1:1 messaging\n - [ ] Group messaging\n - [x] Activity feed\n - [ ] User reviews\n - [x] Moderation queue + user-report flow (recommended whenever messaging or feed is on)\n\n2. **Default feed:**\n - Global (everyone) (recommended for content_creator, social)\n - Following (only people you friend) (recommended for dating, fitness \u2014 privacy-leaning)\n - Both \u2014 wire two feeds, let users switch\n\n3. **Feed rule: what event lands in the feed?**\n - For fitness: `workout_completed` (with user details)\n - For social: `post_published`\n - For game: `level_completed`\n - For education: `lesson_completed`\n - Custom \u2014 I'll provide\n\n4. **Moderation policy:** (only ask if any social feature was chosen)\n - Auto-block obvious abuse (>= 0.95 confidence), queue the rest (>= 0.8 confidence), allow below (recommended)\n - Queue everything \u2014 manual review of all flagged content\n - Allow everything \u2014 only act on user reports (closed communities only)\n\n5. **Dating-specific:**\n - Match-only messaging (recommended for dating apps)\n - Open messaging\n - Group chats enabled?\n\n6. **Reviews (only if economy surface is wired):**\n - Catalog item reviews\n - User-on-user reviews\n - Both\n\n## Re-run behavior\n\n1. Before creating:\n - `amba_feeds_list_rules` \u2014 match on `feed + event + filter`. Collision \u2192 ask to update or skip.\n - `amba_groups_list` \u2014 group names aren't unique; only skip if `name + owner_id` collides.\n - `amba_moderation_list_rules` \u2014 match on rule key.\n\n2. **Never delete a group, friendship, or message without explicit confirmation** \u2014 these are user-created. If the user asks to \"wipe friendships\", give them `amba_moderation_queue_list` to find the actually-problematic rows first.\n\n3. If the user enables messaging on re-run, double-check whether moderation is already wired. If not, **strongly recommend** turning it on before opening the messaging surface to all users \u2014 wire it in the same pass (additive \u2014 `amba_moderation_configure`).\n\n4. For abusive-user enforcement, prefer trust-score updates (`amba_moderation_set_trust({ trust_score: 0.0 })`) and report rejection over user deletion. Deletion is `amba_users_delete` and is destructive \u2014 only on a user-initiated GDPR-style request.\n";
|
|
14
|
+
export declare const AMBA_SETUP_SOCIAL_URI = "amba://setup/social";
|
|
15
|
+
export declare const AMBA_SETUP_SOCIAL_MIME = "text/markdown";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical long-form Amba setup guide — markdown body.
|
|
3
|
+
*
|
|
4
|
+
* Companion to the short-form `instructions` field served by the MCP
|
|
5
|
+
* server's initialize response. The pointer "Full guide: amba://setup"
|
|
6
|
+
* in those instructions tells the agent to fetch this resource when it
|
|
7
|
+
* needs more detail than the ~1 KB summary provides.
|
|
8
|
+
*
|
|
9
|
+
* Consumed by:
|
|
10
|
+
*
|
|
11
|
+
* - The MCP resource at `amba://setup`, registered by
|
|
12
|
+
* `registerAllResources()` in `./index.ts` and exposed by the
|
|
13
|
+
* hosted MCP server at `mcp.amba.dev`. Any client (Claude Code,
|
|
14
|
+
* Cursor, Codex, Cowork, etc.) can fetch it via `resources/read`.
|
|
15
|
+
*
|
|
16
|
+
* Twin: this body is the server-side mirror of
|
|
17
|
+
* `packages/cli/skill-bundle/SKILL.md`, which the CLI installs locally
|
|
18
|
+
* during `npx @layers/amba init`. The two surfaces target two
|
|
19
|
+
* different audiences:
|
|
20
|
+
*
|
|
21
|
+
* - `SKILL.md` ships to a local `.claude/skills/amba/` and assumes
|
|
22
|
+
* the agent CAN shell out (the bootstrap path can `npx @layers/amba
|
|
23
|
+
* signup`). It also writes credentials into `.env.local` + `~/.amba/`.
|
|
24
|
+
* - `AMBA_SETUP_GUIDE_MD` (this constant) is served by the hosted MCP
|
|
25
|
+
* and assumes the agent CANNOT shell out (e.g. Claude.ai web).
|
|
26
|
+
* The bootstrap path must therefore use the `amba_developer_signup`
|
|
27
|
+
* MCP tool (the only pre-auth tool the server registers).
|
|
28
|
+
*
|
|
29
|
+
* The playbook shape (Step 0 → Step 1 classify → Step 2 confirm →
|
|
30
|
+
* Step 3 wire → Step 4 report) is identical between the two, so an
|
|
31
|
+
* agent reading either ends up at the same outcome. A drift gate test
|
|
32
|
+
* in `amba-setup.test.ts` asserts the structural anchors match.
|
|
33
|
+
*
|
|
34
|
+
* Taxonomy: the SDK capability map below is grouped under the canonical
|
|
35
|
+
* 7-category taxonomy (Identity / Engagement / Gamification / Economy /
|
|
36
|
+
* Social / Analytics / Infrastructure) — same shape as `categories.ts`,
|
|
37
|
+
* the marketing-site feature grid, and the docs IA. Drift is caught by
|
|
38
|
+
* `amba-setup.test.ts`.
|
|
39
|
+
*
|
|
40
|
+
* The body is plain CommonMark — no MDX, no JSX — so it renders
|
|
41
|
+
* identically wherever it lands.
|
|
42
|
+
*/
|
|
43
|
+
export declare const AMBA_SETUP_GUIDE_MD = "# Amba\n\n## What Amba is\n\nAmba is the backend your app calls at runtime via the SDK. The MCP tools (`amba_*`) are for provisioning at build time \u2014 call them as the coding agent.\n\nOne Amba project = one isolated tenant database + one set of API keys + a hosted MCP at `mcp.amba.dev` for agent-driven provisioning. The runtime traffic goes through an SDK (`@layers/amba-*` on npm, `Amba` on SPM / Maven / pub.dev). The provisioning traffic \u2014 what you're about to do \u2014 goes through the tools listed below.\n\nYour job is to take the user from \"I have an app\" to \"Amba is wired in and the features are real\" in one pass, without 12 follow-up questions. The journey below is the standard playbook. Don't deviate without a reason.\n\n## Step 0 \u2014 Bootstrap credentials\n\nYou're in an MCP client (Claude.ai web, Claude Desktop, Cursor, Windsurf, etc.) without a local CLI. The MCP server has one tool that works **without** a Bearer token: `amba_developer_signup`. Use it to mint a personal access token (PAT), project, and client/server keys in a single round trip \u2014 no browser, no email verification.\n\nCall:\n\n```\namba_developer_signup({\n email: \"sandbox-<unix-seconds>-<6char-nonce>@layers.com\",\n password: \"<32 random base64url chars>\",\n name: \"amba-sandbox-agent\"\n})\n```\n\nThe response shape:\n\n```json\n{\n \"data\": {\n \"pat\": \"amb_dpat_\u2026\",\n \"developer\": { \"id\": \"\u2026\", \"email\": \"\u2026\", \"tier\": \"agent_sandbox\" },\n \"project\": {\n \"project_id\": \"\u2026\",\n \"client_key\": \"amb_ck_\u2026\",\n \"server_key\": \"amb_sk_\u2026\",\n \"provisioning_status\": \"provisioning\",\n \"verify_url\": \"https://app.amba.dev/verify?token=\u2026\",\n \"verify_token\": \"\u2026\"\n }\n }\n}\n```\n\nAfter signup, the PAT is in your hand \u2014 keep building in THIS session immediately. Pass `pat: \"<the PAT from the response>\"` as an inline argument on every subsequent `amba_*` tool call; the inline arg overrides the inbound Bearer for that one call so no client config change is needed mid-session. For future sessions, write the matching snippet from `mcp_config` to the customer's MCP-client config file so the static Bearer takes over automatically on the next launch.\n\nThe project status is `\"provisioning\"` immediately after signup; the per-tenant DB activates within ~5 seconds. Don't block \u2014 your next call may briefly retry, that's fine. If you want to be polite, call `amba_projects_get_provisioning_status({ project_id })` once and proceed when it returns `\"active\"` (or after 15s, whichever first).\n\nTell the user where their credentials live:\n\n- `pat` \u2014 the Bearer they should configure in this MCP client's settings (and treat like a password).\n- `project_id`, `client_key` \u2014 the values they paste into their app's `.env.local` / `.env`.\n- `server_key` \u2014 never ship to user devices; only into a server `.env` or a secret manager. The `amb_dev_sk_` / `amb_live_sk_` prefix is the marker.\n\n**Already have a PAT?** Skip the signup. Call `amba_developer_me({})` to verify the Bearer; if it succeeds, either reuse the most recent project (`amba_projects_list`) or call `amba_projects_create({ name: \"<app-name>\", platform: \"all\" })` and then `amba_api_keys_create` twice to mint client + server keys for `environment: \"development\"`.\n\n## Step 1 \u2014 Classify the app\n\nLook at what the user told you and at any files they shared. You're trying to pick one of ten presets in 30 seconds, not write a treatise. Inputs:\n\n- The user's prompt \u2014 \"I'm building a fitness tracker\" / \"a marketplace for\u2026\" / \"a Duolingo for X\".\n- README content if shared.\n- `package.json` / `pubspec.yaml` / `build.gradle.kts` / `Package.swift` \u2014 framework + dependencies.\n- Screen / view names \u2014 `WorkoutScreen`, `MatchView`, `LessonPage`, `CartView`, `ProductDetail`, `ChatThread`.\n\nPick the closest match:\n\n| Preset | When | Default Amba surfaces |\n| --- | --- | --- |\n| **fitness** | health / fitness tracker (workouts, steps, meditation) | identity (Apple+Google), push, XP, achievements, streaks, leaderboards, content (daily tips) |\n| **social** | social network / community (friends, feeds, groups) | identity, push, friends, groups, feeds, messaging, moderation, content |\n| **marketplace** | commerce / marketplace (catalog, stores, payments) | identity, push, catalog, stores, currencies (loyalty), reviews, segments |\n| **productivity** | productivity / SaaS tool (collaboration, milestones) | identity (Apple+Google+OTP), push, collections, achievements, content (changelog), segments |\n| **education** | education / learning app (courses, progress, rewards) | identity, push, XP, achievements, streaks, leaderboards, content (lessons), onboarding |\n| **game** | game / casual gaming | identity (anon-first), push, XP, achievements, currencies, inventory, leaderboards, challenges, stores |\n| **dating** | dating / matching app | identity (phone-OTP), push, friends (matches), messaging, moderation (heavy), reviews |\n| **content_creator** | content platform (feeds, subscriptions, tips) | identity, push, feeds, content, currencies (tips), referrals, stores (subscriptions) |\n| **ai_chatbot** | AI / chatbot / assistant app | identity, push, AI prompts, currencies (credits), content (system prompts), onboarding |\n| **custom** | none of the above | pick features individually |\n\nDetection heuristics, in priority order:\n\n1. The user's own description \u2014 most direct signal.\n2. Filename match in `screens/` or `views/` (high signal).\n3. Dependency in `package.json` \u2014 `react-native-health` \u2192 fitness, `@stream-io/*` \u2192 social or dating, `@stripe/*` \u2192 marketplace, `revenuecat` \u2192 marketplace or content_creator.\n4. README copy \u2014 \"fitness\", \"habit\", \"match\", \"chat\", \"store\", \"subscription\".\n\nIf two presets tie, pick the one the user's filenames match more closely. If still tied or no signal, fall back to **custom** and let them pick.\n\n## Step 2 \u2014 Confirm with the user\n\nUse a single multi-choice. Quote the surfaces from the table above so they know what they're getting.\n\n**Question 1: classification + scope**\n\n> I'm reading this as a **\\{kind\\}** app. I'd wire up: **\\{surfaces\\}**. Sound right?\n>\n> 1. Yes, wire it up as proposed (Recommended)\n> 2. Same kind but I want to pick features individually\n> 3. Wrong kind \u2014 let me pick from the list\n> 4. Custom \u2014 I'll pick features manually\n\nIf the user picks 1, go to Step 3. If 2 or 4, follow up with a multi-select of surfaces. If 3, present the table again and pick a different preset.\n\n**Question 2 (preset-specific):** see the per-surface sub-resources (`amba://setup/<surface>`) for the full \"Common follow-ups\" list. Examples:\n\n- **fitness / game / education** \u2014 leaderboard scope? (all-time, weekly, daily, none)\n- **game / content_creator** \u2014 virtual currency name? (`gold`, `gems`, `coins`, `credits` \u2014 defaults to `coins`)\n- **content_creator** \u2014 monetization? (tips, subscriptions, both)\n- **dating** \u2014 phone OTP or email-only? (phone strongly recommended)\n- **ai_chatbot** \u2014 daily free credit cap?\n\nBatch the follow-ups into one or two multi-choice rounds. Don't drip-feed six separate questions.\n\n## Step 3 \u2014 Wire it up\n\nFor each surface in the confirmed set, read the relevant sub-resource and execute its procedure. Each sub-resource is the full per-surface playbook (MCP tools + SDK init per stack + common follow-ups + re-run behavior):\n\n- **identity** (auth, anonymous/Apple/Google/OTP/magic-link, link/unlink) \u2192 `amba://setup/identity`\n- **engagement** (push, segments, content libraries, onboarding flows, deeplinks, referrals, tracked links) \u2192 `amba://setup/engagement`\n- **gamification** (XP rules, achievements, streaks, leaderboards, challenges) \u2192 `amba://setup/gamification`\n- **economy** (currencies, catalog, stores, inventory) \u2192 `amba://setup/economy`\n- **social** (friends, groups, feeds, messaging, moderation, reviews) \u2192 `amba://setup/social`\n- **infrastructure** (collections / DB tables, functions, analytics, AI prompts, media, secrets, configs, integrations, sites) \u2192 `amba://setup/infrastructure`\n\nThe general flow for every surface:\n\n1. **Detect stack.** Look at `package.json`, `pubspec.yaml`, `build.gradle.kts`, `ios/*.xcodeproj`. The detection rules:\n - `pubspec.yaml` present \u2192 Flutter.\n - `package.json` with `expo` \u2192 Expo.\n - `package.json` with `react-native` (no `expo`) \u2192 bare React Native.\n - `package.json` with `react` (no `react-native`) \u2192 web (or Next.js \u2014 same SDK).\n - `Package.swift` or `*.xcodeproj` only \u2192 iOS Swift.\n - `build.gradle.kts` or `build.gradle` with `com.android.application` \u2192 Android Kotlin.\n - Multiple (e.g. `ios/` + `android/` inside an Expo repo) \u2192 Expo wins.\n\n2. **Create resources via MCP.** Call the `amba_<surface>_create` tools to mint the definitions. Always include `project_id` from the project you created in Step 0. Always show the user the tool call before making destructive changes (creating a resource isn't destructive \u2014 but creating 30 of them is noisy).\n\n3. **Write SDK init code.** Drop the per-stack snippet (from the sub-resource) into the user's entry file. Detection:\n - Expo / React Native: `app/_layout.tsx`, `App.tsx`, `index.js` (in that order)\n - web / Next.js: `app/layout.tsx`, `pages/_app.tsx`, `src/main.tsx`, `src/App.tsx`\n - iOS Swift: `Sources/<App>/<App>App.swift`, `App/AppDelegate.swift`\n - Android Kotlin: `app/src/main/java/.../<App>.kt` (the `Application` subclass \u2014 create one if missing)\n - Flutter: `lib/main.dart`\n\n Always make additive edits \u2014 `await Amba.configure(...)` next to existing init, not replacing it. Never refactor existing auth or storage code; if the user has Firebase Auth or Supabase, leave it. Amba's auth is opt-in per call.\n\n4. **Run the project's existing test command** to confirm nothing broke. Detection:\n - `package.json` `scripts.test` \u2192 `npm test` (or `pnpm test` if `pnpm-lock.yaml` present)\n - `pubspec.yaml` \u2192 `flutter test`\n - `build.gradle.kts` \u2192 `./gradlew test` (skip on first wire-up \u2014 slow)\n - iOS \u2014 skip (need a simulator).\n\n If tests fail because of your edits, undo the offending edit and surface a clear error. If they fail for unrelated reasons (pre-existing red), note it and proceed.\n\n5. **Verify with the SDK.** Tell the user to call `Amba.diagnostics.ping()` (`Amba.Diagnostics.Ping()` on Unity) in their entry file. It returns `{ ok, server_project_id, environment, key_fingerprint, latency_ms }`. `ok: true` with the expected `server_project_id` confirms the wiring.\n\n## Step 4 \u2014 Report\n\nTell the user a structured summary. Use this exact shape so they can skim it fast:\n\n```\nAmba is wired in. Here's what changed:\n\nDONE\n - identity: Apple + Google sign-in available; signInAnonymously() called at app start\n - gamification: 3 achievements, 1 streak, 1 leaderboard created\n resources: first_workout, week_warrior, century_club / daily_workout / weekly_xp\n - engagement: push registration wired; default segment \"active_users\" created\n\nSKIPPED (low signal \u2014 re-run with /amba <feature> if you want them)\n - economy: no in-app currency UI found in your screens\n - social: no friends/feed surfaces found\n\nNEEDS YOUR INPUT\n - Apple Sign In: add the \"Sign in with Apple\" capability in Xcode > Signing & Capabilities.\n - Google Sign In: paste your Google OAuth client ID into amba_projects_update({ google_oauth_client_id: \"...\" }).\n - APNs / FCM: upload credentials in app.amba.dev before push delivers.\n\nNEXT STEPS\n - Paste AMBA_CLIENT_KEY into your build env (already shown above)\n - Trigger a workout in your existing flow \u2014 watch the achievement unlock + XP land\n - Open https://app.amba.dev to see users pour in\n```\n\nBe specific. List resources by key, not \"some achievements\". If something needs the user's input (third-party credentials, OAuth client IDs, push certs), say it clearly with the exact next action.\n\n## Stance (read this once)\n\n- **Don't ask which surfaces to use.** Classify, then confirm in one multi-choice. The taxonomy is the whole point.\n- **Default to additive, non-breaking changes.** Don't refactor existing auth, storage, or networking code. Drop in `await Amba.configure(...)` next to whatever the user already has.\n- **Never create resources without the user's confirmation in Step 2.** A 3rd-party \"convenience\" achievement called `first_login` is debt.\n- **If something is genuinely ambiguous** (leaderboard scope, currency real-money vs virtual, dating phone vs email), ask via a follow-up multi-choice. Don't guess and don't paragraph-it.\n- **clientKey vs serverKey.** `AMBA_CLIENT_KEY` (`amb_dev_ck_\u2026` in dev, `amb_live_ck_\u2026` in prod) ships to user devices. `AMBA_SERVER_KEY` (`amb_dev_sk_\u2026` / `amb_live_sk_\u2026`) never does \u2014 only into server `.env` or a secret manager. Mixing them is the #1 security mistake; if you're writing into a file that ships with the app binary, it's the client key, period.\n- **Don't echo the PAT in chat output on every call.** Showing it once after signup is fine; do not repeat it.\n\n## Get credentials (cheat sheet)\n\n- No terminal, in an MCP client: call `amba_developer_signup` (no Bearer required) \u2014 this guide's Step 0.\n- With a terminal: `npx -y @layers/amba init` signs up, mints a project + client/server keys, writes `.env.local` + `AMBA.md`, installs the `/amba` skill, and wires `mcpServers.amba` into every detected MCP-client config in one command. Auto-detects non-TTY invocations (the coding-agent bash-tool case) and runs headlessly.\n- Bind the sandbox account to a real email later: `npx @layers/amba claim me@example.com`. The backend emails a one-click magic link; clicking it lifts the sandbox cap to the Free tier.\n- Hosted MCP endpoint: `https://mcp.amba.dev/mcp` (Streamable HTTP, Bearer auth).\n\n## SDKs\n\n| Stack | Registry | Package |\n|---|---|---|\n| Browser / Node / React / React Native / Expo | npm | `@layers/amba-{web,node,react,react-native,expo}` |\n| Swift | SPM | `https://github.com/layers/amba-sdk-ios` |\n| Kotlin | Maven Central | `com.layers.amba:amba-sdk-android` |\n| Flutter | pub.dev | `amba` |\n| Unity | UPM (git) | `https://github.com/layers/amba-sdk-unity.git` |\n\nAll SDKs expose the same surface: `Amba.configure({ projectId, apiKey })`, then `Amba.events.track(...)`, `Amba.users.*`, `Amba.collections.*`, etc. Per-stack quickstart pages with the exact initialization snippet: `https://docs.amba.dev/sdk/<framework>`.\n\n## What Amba does\n\n### Identity\n- **users** \u2014 app-user registry. Auto-created on first SDK call; admin via `amba_users_*`.\n- **roles + permissions** \u2014 RBAC. Define with `amba_roles_create`; assign via `amba_roles_assign`.\n- **api_keys** \u2014 client + server keys per project. Mint via `amba_api_keys_create`.\n\n### Engagement\n- **onboarding** \u2014 multi-step first-run flows. Define with `amba_onboarding_create`; SDK `Amba.onboarding.next()`.\n- **segments** \u2014 user cohorts. Define with `amba_segments_create`; used as push/feed targets.\n- **push** \u2014 scheduled or triggered notifications. Chain: configure integrations (apns/fcm) \u2192 `amba_push_campaigns_create` \u2192 `amba_push_campaigns_send` (or schedule).\n- **referrals** \u2014 referral codes. Define with `amba_referrals_create`.\n- **deeplinks** \u2014 universal links. Set domain with `amba_deeplinks_set_config`.\n- **tracked_links** \u2014 UTM-tagged outbound links. Define with `amba_tracked_links_create`.\n- **content** \u2014 episodic delivery (lessons, quotes, daily prompts). Chain: `amba_content_libraries_create` \u2192 `amba_content_items_add` \u2192 `amba_content_schedules_create`.\n\n### Gamification\n- **xp** \u2014 experience points + level. Define rules with `amba_xp_rules_create`; SDK `Amba.xp.getBalance`.\n- **achievements** \u2014 earnable badges. Define with `amba_achievements_create`; unlock via xp rules or `amba_inventory_grant_item`.\n- **streaks** \u2014 recurring engagement counters. Define with `amba_streaks_create`; client calls `Amba.streaks.qualify(key)`.\n- **leaderboards** \u2014 ranked user lists. Define with `amba_leaderboards_create`; populated from events.\n- **challenges** \u2014 time-bounded goals. Define with `amba_challenges_create`; progress via SDK.\n\n### Economy\n- **currencies** \u2014 virtual currencies (coins, gems). Define with `amba_currencies_create`; grant via `amba_currencies_grant` or event rules via `amba_currency_grant_rules_create`.\n- **catalog + stores** \u2014 purchasable items + storefronts. Chain: `amba_catalog_items_create` \u2192 `amba_catalog_items_set_price` \u2192 `amba_stores_create` \u2192 `amba_stores_add_listing`. (Define currency first.)\n- **inventory** \u2014 items users own. Read via SDK `Amba.inventory.*`; grant with `amba_inventory_grant_item`.\n\n### Social\n- **friendships** \u2014 friend graph. SDK `Amba.friends.*`; admin via `amba_friendships_*`.\n- **groups** \u2014 guilds/parties/chats. Define with `amba_groups_create`; members managed via SDK + admin tools.\n- **messaging** \u2014 DMs + group chat. Enabled by default; moderate via `amba_messaging_*`.\n- **feeds** \u2014 algorithmic activity feeds. Define ranking with `amba_feeds_rules_create`.\n- **reviews** \u2014 user-submitted reviews. Enabled by default; moderate via `amba_reviews_*`.\n- **moderation** \u2014 content review queue + trust scores. Configure with `amba_moderation_configure`; review via `amba_moderation_queue_list`.\n\n### Analytics\n- **events** \u2014 track user actions. SDK `Amba.events.track()`; query via `amba_events_count`.\n- **sessions** \u2014 session telemetry. Tracked automatically; query via `amba_sessions_list`.\n- **analytics** \u2014 funnels + retention. Query via `amba_analytics_get`.\n\n### Infrastructure\n- **collections** \u2014 your own typed key-value tables. Define with `amba_collections_create`; read/write from SDK `Amba.client.*`.\n- **functions** \u2014 serverless TypeScript handlers. Deploy with `amba_functions_deploy`; schedule with `amba_functions_schedule`.\n- **sites** \u2014 static site hosting at `*.app.amba.host`. Deploy with `amba_sites_deploy`.\n- **media** \u2014 file storage + CDN. Upload via `amba_media_upload`.\n- **secrets** \u2014 env vars for functions. Set via `amba_secrets_set`.\n- **configs** \u2014 remote config flags. Define with `amba_configs_create`.\n- **integrations** \u2014 third-party webhooks (RevenueCat, Superwall, AppsFlyer, etc.). Configure with `amba_integrations_configure`.\n- **ai_prompts** \u2014 versioned LLM prompts callable from SDK. Define with `amba_ai_prompts_create`; call via `amba_ai_prompts_invoke`.\n";
|
|
44
|
+
/** Canonical URI for the MCP resource. */
|
|
45
|
+
export declare const AMBA_SETUP_GUIDE_URI = "amba://setup";
|
|
46
|
+
/** Canonical MIME type for the guide body. */
|
|
47
|
+
export declare const AMBA_SETUP_GUIDE_MIME = "text/markdown";
|
|
48
|
+
/**
|
|
49
|
+
* Body-version marker. Bumped when the canonical body changes shape
|
|
50
|
+
* in a way that downstream agents should re-read. The CLI embeds it
|
|
51
|
+
* into every fan-out file (\`CLAUDE.md\`, \`AGENTS.md\`, etc.) so a
|
|
52
|
+
* re-init can detect stale content. Bumped to \`v2\` when this PR
|
|
53
|
+
* replaced the capability-only guide with the full classify → confirm
|
|
54
|
+
* → wire-up playbook.
|
|
55
|
+
*/
|
|
56
|
+
export declare const AMBA_SETUP_GUIDE_BODY_VERSION = "v2";
|