@layers/amba-mcp 1.0.1 → 4.0.3

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.
Files changed (37) hide show
  1. package/README.md +17 -19
  2. package/dist/api-client.d.ts +42 -4
  3. package/dist/categories.d.ts +48 -0
  4. package/dist/expo-build-prompt.js +641 -0
  5. package/dist/index.d.ts +4 -0
  6. package/dist/index.js +5388 -715
  7. package/dist/lib/aliases.d.ts +36 -0
  8. package/dist/lib/annotations.d.ts +113 -0
  9. package/dist/lib/tool-result.d.ts +51 -0
  10. package/dist/lib/with-pat.d.ts +125 -0
  11. package/dist/resources/amba-setup-economy.d.ts +14 -0
  12. package/dist/resources/amba-setup-engagement.d.ts +16 -0
  13. package/dist/resources/amba-setup-gamification.d.ts +15 -0
  14. package/dist/resources/amba-setup-identity.d.ts +20 -0
  15. package/dist/resources/amba-setup-infrastructure.d.ts +18 -0
  16. package/dist/resources/amba-setup-social.d.ts +15 -0
  17. package/dist/resources/amba-setup.d.ts +56 -0
  18. package/dist/resources/expo-build-prompt.d.ts +35 -0
  19. package/dist/resources/index.d.ts +108 -0
  20. package/dist/resources/prompts.d.ts +13 -0
  21. package/dist/resources/prompts.js +2 -0
  22. package/dist/tools/__test-fixtures__/rename-map.d.ts +19 -0
  23. package/dist/tools/_pat.d.ts +64 -0
  24. package/dist/tools/ai-prompts-admin.d.ts +35 -0
  25. package/dist/tools/auth.d.ts +32 -9
  26. package/dist/tools/billing.d.ts +29 -0
  27. package/dist/tools/collections.d.ts +37 -0
  28. package/dist/tools/domains.d.ts +33 -0
  29. package/dist/tools/email.d.ts +26 -0
  30. package/dist/tools/functions.d.ts +34 -0
  31. package/dist/tools/funnels.d.ts +3 -0
  32. package/dist/tools/invites.d.ts +22 -0
  33. package/dist/tools/secrets.d.ts +31 -0
  34. package/dist/tools/setup.d.ts +1 -1
  35. package/dist/tools/sites.d.ts +37 -0
  36. package/dist/tools/webhooks.d.ts +27 -0
  37. 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,113 @@
1
+ /**
2
+ * Data-driven MCP tool annotations (`title` + `readOnlyHint` /
3
+ * `destructiveHint`), derived from the tool name.
4
+ *
5
+ * ## Why this exists
6
+ *
7
+ * The Anthropic Connectors Directory and the OpenAI ChatGPT Apps
8
+ * directory both REQUIRE that every advertised MCP tool carry a human
9
+ * `title` and at least one of `readOnlyHint` / `destructiveHint`. Missing
10
+ * annotations are one of the most common directory-listing rejections.
11
+ * Amba registers 200+ `amba_*` tools, so annotating each by hand would
12
+ * (a) be a huge mechanical diff and (b) immediately rot — every new tool
13
+ * group (funnels, domains, …) would re-introduce the gap.
14
+ *
15
+ * Instead we derive annotations from the tool NAME at registration time,
16
+ * centrally, in the one chokepoint every tool flows through
17
+ * (`registerTool` / `registerPublicTool` in `with-pat.ts`). New tools get
18
+ * correct annotations for free as long as they follow the
19
+ * `amba_<resource>_<verb>` (or legacy `amba_<verb>_<resource>`) naming
20
+ * convention — which the `tool-naming.test.ts` suite already enforces.
21
+ *
22
+ * ## The verb → annotation mapping
23
+ *
24
+ * A tool name is split into `_`-delimited tokens (after the `amba_`
25
+ * prefix). Each token is checked against three verb sets. Because a name
26
+ * can contain more than one recognised verb token (e.g.
27
+ * `amba_currency_grant_rules_list` carries both the `grant` write-noun and
28
+ * the `list` read-verb), classification resolves by PRECEDENCE:
29
+ *
30
+ * 1. destructive (delete / remove / revoke / drop / reset)
31
+ * 2. read-only (get / list / find / describe / stats / export / …)
32
+ * 3. write (create / update / set / grant / send / deploy / …)
33
+ *
34
+ * The precedence is validated against the full registered tool set in
35
+ * `annotations.test.ts` — every real Amba tool name classifies, and the
36
+ * handful of multi-verb names land in the semantically-correct bucket
37
+ * (e.g. `amba_content_delete_schedule` → destructive, not write;
38
+ * `amba_currency_grant_rules_list` → read-only, not write).
39
+ *
40
+ * ## Annotation semantics (MCP spec)
41
+ *
42
+ * - read-only → `{ readOnlyHint: true }`. Per spec, `destructiveHint` is
43
+ * only meaningful when `readOnlyHint` is false, so it is omitted.
44
+ * - destructive→ `{ readOnlyHint: false, destructiveHint: true }`.
45
+ * - write → `{ readOnlyHint: false, destructiveHint: false }`. An
46
+ * additive/idempotent mutation that does not destroy existing state
47
+ * (create / update / grant / …). Still carries both hints so directory
48
+ * validators see an explicit answer.
49
+ *
50
+ * `title` is always present.
51
+ */
52
+ import type { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js';
53
+ /**
54
+ * Read-only verbs. A tool whose name contains one of these (and no
55
+ * destructive verb) does not mutate project state — it reads/derives/
56
+ * exports data. Maps to `readOnlyHint: true`.
57
+ */
58
+ export declare const READ_VERBS: ReadonlySet<string>;
59
+ /**
60
+ * Destructive verbs. A tool whose name contains one of these MAY perform
61
+ * a destructive update (irreversible removal of existing state). Maps to
62
+ * `destructiveHint: true`. `reset` is included because `*_reset_sandbox`
63
+ * wipes end-user data.
64
+ */
65
+ export declare const DESTRUCTIVE_VERBS: ReadonlySet<string>;
66
+ /**
67
+ * Write verbs — mutations that are additive / non-destructive (create a
68
+ * row, update fields, grant currency, deploy a function, send a push, …).
69
+ * Maps to `destructiveHint: false`. This set exists so a write tool that
70
+ * matches NONE of the read/destructive verbs is still positively
71
+ * classified (rather than falling through unannotated). Kept explicit so
72
+ * an unrecognised verb surfaces as a test failure instead of silently
73
+ * defaulting.
74
+ */
75
+ export declare const WRITE_VERBS: ReadonlySet<string>;
76
+ /** The three behavioural classes a tool name resolves to. */
77
+ export type ToolVerbClass = 'read' | 'destructive' | 'write';
78
+ /**
79
+ * Split a tool name into lowercase verb-candidate tokens, dropping the
80
+ * `amba_` prefix. Exported for the test suite.
81
+ */
82
+ export declare function toolNameTokens(name: string): string[];
83
+ /**
84
+ * Classify a tool name into its behavioural class by PRECEDENCE
85
+ * (destructive > read > write). Returns `null` if no token matches any
86
+ * known verb — that should never happen for a real Amba tool and the
87
+ * test suite asserts it doesn't, so a `null` at runtime means a new tool
88
+ * used an unrecognised verb and needs a verb-set entry here.
89
+ */
90
+ export declare function classifyToolVerb(name: string): ToolVerbClass | null;
91
+ /**
92
+ * Derive a human-readable Title-Case title from the tool name. The
93
+ * operative action verb (see `operativeVerbIndex`) is moved to the front
94
+ * so the title reads as an imperative; remaining tokens keep their order:
95
+ *
96
+ * amba_currencies_create → "Create Currencies"
97
+ * amba_content_delete_schedule → "Delete Content Schedule"
98
+ * amba_currency_grant_rules_create → "Create Currency Grant Rules"
99
+ * amba_get_friendship_stats → "Get Friendship Stats"
100
+ * amba_pause_function_schedule → "Pause Function Schedule"
101
+ * amba_catalog_items_set_price → "Set Catalog Items Price"
102
+ * amba_users_get → "Get Users"
103
+ *
104
+ * When there is no action verb (`billing_status`, `sessions_analytics`)
105
+ * the tokens are title-cased in order — a readable noun-phrase fallback.
106
+ */
107
+ export declare function deriveToolTitle(name: string): string;
108
+ /**
109
+ * Derive the full `ToolAnnotations` (title + read-only/destructive hints)
110
+ * for a tool name. This is the single function `with-pat.ts` calls for
111
+ * every tool it registers, so annotations stay uniform and automatic.
112
+ */
113
+ export declare function deriveToolAnnotations(name: string): ToolAnnotations;
@@ -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_spend` | Debit currency from a specific user. Atomic; returns INSUFFICIENT_FUNDS if balance is too low (no partial debit). | `{ project_id, app_user_id, currency_code: \"gems\", amount: 5, reason: \"ai_generation\" }` |\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\nAdmin tools authenticate the developer/agent (pass `pat` or send it as the inbound Bearer) and take `project_id`. Client tools authenticate an end-user and take `api_key` (+ `session_token`) \u2014 NOT `project_id` and NOT a `pat`. Every row tool names the collection with `name`, never `collection`.\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_collections_create` | Create a typed collection. Pass `shared: true` for developer-seeded GLOBAL content (question banks, lookup tables) so `user_id` is nullable. | `{ 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 }], shared: false }` |\n| `amba_collections_list` | List collections in this project. | `{ project_id }` |\n| `amba_collections_get` | Read one collection's schema. | `{ project_id, name: \"todos\" }` |\n| `amba_collections_alter` | Exactly ONE of: `add_column`, `add_index`, `drop_column`, or `relax_user_id` per call. `relax_user_id: true` converts an existing collection to shared (drops the `user_id` NOT NULL). | `{ project_id, name: \"todos\", add_column: { name: \"priority\", type: \"int\", nullable: true } }` |\n| `amba_collections_delete` | Drop the table (destructive). `confirm` must equal the collection name. | `{ project_id, name: \"todos\", confirm: \"todos\" }` |\n| `amba_admin_insert_row` | Insert one row as the developer (bypasses user-scope; `user_id` honored if present). | `{ project_id, name: \"todos\", row: { title: \"Sample\", done: false } }` |\n| `amba_admin_insert_rows` | Bulk-insert up to 500 rows in one atomic statement \u2014 the canonical seeding/migration path. `on_conflict`: `\"error\"` (default) or `\"skip\"`. | `{ project_id, name: \"questions\", rows: [{ q: \"...\" }, { q: \"...\" }], on_conflict: \"skip\" }` |\n| `amba_admin_list_rows` | Read rows as the developer. | `{ project_id, name: \"todos\", limit: 100 }` |\n| `amba_client_insert_row` | Insert as an end-user. Requires `api_key` (+ `session_token`). | `{ api_key, session_token, name: \"todos\", row: {...} }` |\n| `amba_client_list_rows` | Read as an end-user (auto user-scoped). | `{ api_key, session_token, name: \"todos\" }` |\n| `amba_client_get_row` | Get one row by id (end-user). | `{ api_key, session_token, name: \"todos\", id }` |\n| `amba_client_update_row` | Update one row by id (end-user). Fields go in `set`. Omit `id` + pass `where` for a bulk update. | `{ api_key, session_token, name: \"todos\", id, set: {...} }` |\n| `amba_client_delete_row` | Soft-delete one row by id (end-user). | `{ api_key, session_token, name: \"todos\", id }` |\n| `amba_client_count_rows` | Count rows matching an optional `where`. | `{ api_key, session_token, name: \"todos\", where: {...} }` |\n| `amba_client_find_rows` | Filter / sort / paginate rows (SDK-shaped `filter`). | `{ api_key, session_token, name: \"todos\", filter: {...}, order: [\"created_at desc\"], limit: 50 }` |\n| `amba_client_find_nearest_rows` | Vector-similarity search (rows with a `vector(<dim>)` column). | `{ api_key, session_token, name: \"todos\", column: \"embedding\", to_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_create_conversation` | Create a conversation server-side (zero or more initial participants \u2014 for matchmaker-built circles). | `{ project_id, type: \"group\", name: \"Circle\" }` |\n| `amba_messaging_add_participant` | Add a user to a conversation (idempotent). The matchmaker primitive for growing membership over time. | `{ project_id, conversation_id, user_id }` |\n| `amba_messaging_remove_participant` | Remove a user from a conversation (idempotent). | `{ project_id, conversation_id, user_id }` |\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";