@smartico/public-api 0.0.402 → 0.0.404

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 (153) hide show
  1. package/.claude/skills/api-tsdoc/SKILL.md +675 -0
  2. package/.claude/skills/api-tsdoc-final/SKILL.md +266 -0
  3. package/.claude/skills/api-tsdoc-howto/SKILL.md +143 -0
  4. package/dist/MiniGames/SAWTemplate.d.ts +4 -0
  5. package/dist/NodeCache.d.ts +1 -0
  6. package/dist/OCache.d.ts +7 -0
  7. package/dist/SmarticoGlobal.d.ts +0 -6
  8. package/dist/WSAPI/WSAPITypes.d.ts +43 -0
  9. package/dist/WSAPI/WSAPIUser.d.ts +3 -0
  10. package/dist/index.js +92 -33
  11. package/dist/index.js.map +1 -1
  12. package/dist/index.modern.mjs +60 -10
  13. package/dist/index.modern.mjs.map +1 -1
  14. package/docs/api/interfaces/TUserProfile.md +136 -0
  15. package/docs/capabilities/_responses/buyStoreItem.json +4 -0
  16. package/docs/capabilities/_responses/claimBonus.json +4 -0
  17. package/docs/capabilities/_responses/gamePickGetActiveRound.json +95 -0
  18. package/docs/capabilities/_responses/gamePickGetActiveRounds.json +191 -0
  19. package/docs/capabilities/_responses/gamePickGetBoard.json +47 -0
  20. package/docs/capabilities/_responses/gamePickGetGameInfo.json +355 -0
  21. package/docs/capabilities/_responses/gamePickGetHistory.json +191 -0
  22. package/docs/capabilities/_responses/gamePickGetUserInfo.json +14 -0
  23. package/docs/capabilities/_responses/getAchCategories.json +12 -0
  24. package/docs/capabilities/_responses/getActivityLog.json +1 -0
  25. package/docs/capabilities/_responses/getAvatarPrompts.json +16 -0
  26. package/docs/capabilities/_responses/getAvatarsCustomized.json +12 -0
  27. package/docs/capabilities/_responses/getAvatarsList.json +24 -0
  28. package/docs/capabilities/_responses/getBadges.json +98 -0
  29. package/docs/capabilities/_responses/getBonuses.json +18 -0
  30. package/docs/capabilities/_responses/getClanInfo.json +39 -0
  31. package/docs/capabilities/_responses/getClans.json +35 -0
  32. package/docs/capabilities/_responses/getCurrentLevel.json +20 -0
  33. package/docs/capabilities/_responses/getCustomSections.json +25 -0
  34. package/docs/capabilities/_responses/getInboxMessageBody.json +7 -0
  35. package/docs/capabilities/_responses/getInboxMessages.json +18 -0
  36. package/docs/capabilities/_responses/getInboxUnreadCount.json +1 -0
  37. package/docs/capabilities/_responses/getJackpotEligibleGames.json +20 -0
  38. package/docs/capabilities/_responses/getJackpotWinners.json +28 -0
  39. package/docs/capabilities/_responses/getLeaderBoard.json +45 -0
  40. package/docs/capabilities/_responses/getLevels.json +40 -0
  41. package/docs/capabilities/_responses/getMiniGames.json +208 -0
  42. package/docs/capabilities/_responses/getMiniGamesHistory.json +1 -0
  43. package/docs/capabilities/_responses/getMissions.json +117 -0
  44. package/docs/capabilities/_responses/getRaffleDrawRun.json +62 -0
  45. package/docs/capabilities/_responses/getRaffleDrawRunsHistory.json +34 -0
  46. package/docs/capabilities/_responses/getRaffles.json +140 -0
  47. package/docs/capabilities/_responses/getStoreCategories.json +12 -0
  48. package/docs/capabilities/_responses/getStoreItems.json +47 -0
  49. package/docs/capabilities/_responses/getStorePurchasedItems.json +49 -0
  50. package/docs/capabilities/_responses/getTournamentInstanceInfo.json +75 -0
  51. package/docs/capabilities/_responses/getTournamentsList.json +66 -0
  52. package/docs/capabilities/_responses/getTranslations.json +25 -0
  53. package/docs/capabilities/_responses/getUserLevelExtraCounters.json +4 -0
  54. package/docs/capabilities/_responses/getUserProfile.json +34 -0
  55. package/docs/capabilities/_responses/jackpotGet.json +113 -0
  56. package/docs/capabilities/_responses/jackpotOptIn.json +9 -0
  57. package/docs/capabilities/_responses/jackpotOptOut.json +9 -0
  58. package/docs/capabilities/_responses/markAllInboxMessagesAsRead.json +4 -0
  59. package/docs/capabilities/_responses/markInboxMessageAsRead.json +4 -0
  60. package/docs/capabilities/_responses/markUnmarkInboxMessageAsFavorite.json +4 -0
  61. package/docs/capabilities/_responses/miniGameWinAcknowledgeRequest.json +10 -0
  62. package/docs/capabilities/_responses/playMiniGame.json +6 -0
  63. package/docs/capabilities/_responses/playMiniGameBatch.json +9 -0
  64. package/docs/capabilities/_responses/registerInTournament.json +4 -0
  65. package/docs/capabilities/_responses/requestMissionOptIn.json +4 -0
  66. package/docs/capabilities/_responses/setAvatar.json +4 -0
  67. package/docs/capabilities/avatarsCustomize.md +115 -0
  68. package/docs/capabilities/buyStoreItem.md +179 -0
  69. package/docs/capabilities/checkSegmentListMatch.md +69 -0
  70. package/docs/capabilities/checkSegmentMatch.md +74 -0
  71. package/docs/capabilities/claimBonus.md +125 -0
  72. package/docs/capabilities/claimRafflePrize.md +116 -0
  73. package/docs/capabilities/deleteAllInboxMessages.md +66 -0
  74. package/docs/capabilities/deleteInboxMessage.md +60 -0
  75. package/docs/capabilities/gamePickGetActiveRound.md +248 -0
  76. package/docs/capabilities/gamePickGetActiveRounds.md +259 -0
  77. package/docs/capabilities/gamePickGetBoard.md +202 -0
  78. package/docs/capabilities/gamePickGetGameInfo.md +318 -0
  79. package/docs/capabilities/gamePickGetHistory.md +244 -0
  80. package/docs/capabilities/gamePickGetRoundInfoForUser.md +189 -0
  81. package/docs/capabilities/gamePickGetUserInfo.md +91 -0
  82. package/docs/capabilities/gamePickSubmitSelection.md +222 -0
  83. package/docs/capabilities/gamePickSubmitSelectionQuiz.md +198 -0
  84. package/docs/capabilities/getAchCategories.md +119 -0
  85. package/docs/capabilities/getActivityLog.md +108 -0
  86. package/docs/capabilities/getAvatarPrompts.md +81 -0
  87. package/docs/capabilities/getAvatarsCustomized.md +96 -0
  88. package/docs/capabilities/getAvatarsList.md +101 -0
  89. package/docs/capabilities/getBadges.md +237 -0
  90. package/docs/capabilities/getBonuses.md +159 -0
  91. package/docs/capabilities/getClanInfo.md +164 -0
  92. package/docs/capabilities/getClanTournamentPlayers.md +112 -0
  93. package/docs/capabilities/getClans.md +168 -0
  94. package/docs/capabilities/getCurrentLevel.md +123 -0
  95. package/docs/capabilities/getCustomSections.md +96 -0
  96. package/docs/capabilities/getInboxMessageBody.md +87 -0
  97. package/docs/capabilities/getInboxMessages.md +146 -0
  98. package/docs/capabilities/getInboxUnreadCount.md +54 -0
  99. package/docs/capabilities/getJackpotEligibleGames.md +93 -0
  100. package/docs/capabilities/getJackpotWinners.md +113 -0
  101. package/docs/capabilities/getLeaderBoard.md +187 -0
  102. package/docs/capabilities/getLeaderBoards.md +88 -0
  103. package/docs/capabilities/getLevels.md +118 -0
  104. package/docs/capabilities/getMiniGames.md +295 -0
  105. package/docs/capabilities/getMiniGamesHistory.md +183 -0
  106. package/docs/capabilities/getMissions.md +253 -0
  107. package/docs/capabilities/getRaffleDrawRun.md +210 -0
  108. package/docs/capabilities/getRaffleDrawRunsHistory.md +116 -0
  109. package/docs/capabilities/getRaffleWonPrizes.md +153 -0
  110. package/docs/capabilities/getRaffles.md +249 -0
  111. package/docs/capabilities/getRelatedItemsForGame.md +236 -0
  112. package/docs/capabilities/getStoreCategories.md +128 -0
  113. package/docs/capabilities/getStoreItems.md +260 -0
  114. package/docs/capabilities/getStorePurchasedItems.md +280 -0
  115. package/docs/capabilities/getTournamentInstanceInfo.md +301 -0
  116. package/docs/capabilities/getTournamentsList.md +234 -0
  117. package/docs/capabilities/getTranslations.md +95 -0
  118. package/docs/capabilities/getUserLevelExtraCounters.md +92 -0
  119. package/docs/capabilities/getUserProfile.md +184 -0
  120. package/docs/capabilities/jackpotGet.md +173 -0
  121. package/docs/capabilities/jackpotOptIn.md +94 -0
  122. package/docs/capabilities/jackpotOptOut.md +92 -0
  123. package/docs/capabilities/joinClan.md +169 -0
  124. package/docs/capabilities/markAllInboxMessagesAsRead.md +72 -0
  125. package/docs/capabilities/markInboxMessageAsRead.md +69 -0
  126. package/docs/capabilities/markUnmarkInboxMessageAsFavorite.md +75 -0
  127. package/docs/capabilities/miniGameWinAcknowledgeRequest.md +98 -0
  128. package/docs/capabilities/playMiniGame.md +217 -0
  129. package/docs/capabilities/playMiniGameBatch.md +129 -0
  130. package/docs/capabilities/registerInTournament.md +187 -0
  131. package/docs/capabilities/reportClickEvent.md +74 -0
  132. package/docs/capabilities/reportImpressionEvent.md +67 -0
  133. package/docs/capabilities/requestMissionClaimReward.md +117 -0
  134. package/docs/capabilities/requestMissionOptIn.md +98 -0
  135. package/docs/capabilities/requestRaffleOptin.md +108 -0
  136. package/docs/capabilities/setAvatar.md +132 -0
  137. package/docs/capabilities/smarticoCallbacks.md +165 -0
  138. package/docs/capabilities/smarticoGlobal.md +62 -0
  139. package/package.json +3 -2
  140. package/readme-smartico-dev.md +208 -0
  141. package/scripts/capture-responses-browser.mjs +301 -0
  142. package/scripts/gen-capabilities.js +368 -0
  143. package/scripts/run-public-api.mjs +105 -0
  144. package/src/MiniGames/SAWGetTemplatesResponse.ts +2 -0
  145. package/src/MiniGames/SAWTemplate.ts +5 -0
  146. package/src/NodeCache.ts +8 -0
  147. package/src/OCache.ts +17 -0
  148. package/src/SmarticoGlobal.ts +0 -7
  149. package/src/WSAPI/WSAPI.ts +3 -3
  150. package/src/WSAPI/WSAPIJackpots.ts +4 -1
  151. package/src/WSAPI/WSAPIMiniGames.ts +1 -1
  152. package/src/WSAPI/WSAPITypes.ts +44 -0
  153. package/src/WSAPI/WSAPIUser.ts +30 -5
@@ -0,0 +1,62 @@
1
+ # _smartico — global SDK surface (non-`.api.*`)
2
+
3
+ > The `window._smartico` control surface: init/identify, events, deep links, widgets, visitor-mode `vapi`, push, and utilities — everything that is NOT `_smartico.api.*` (those have their own capability pages).
4
+ > Import: typed via `SmarticoGlobal` from '@smartico/public-api'; at runtime the object is on `window._smartico` (no import).
5
+ > Search terms: api, vapi, checkSuccessfullyIdentify, init, initVisitorMode, identify, online, login, logout, clear, changeLanguage, on, off, dp, showWidget, miniGame, showBanner, sendDataToBanner, gamificationBlock, getPublicProps, updatePublicTags, setUTMs, setLoginEntry, setAvatar, setNickname, setSkin, requestPushPermissions, registerNativePushToken, event, action, sendServerError, sendServerDebug, suspendPopups, suspendInbox, suspendMiniGames, showVisitorGame, convertVisitorGame, startLpAnalytics, sendAnalytics, isMobile, parseUrl, parseUrlHash, _smartico, global, window._smartico, SmarticoGlobal, visitor mode, callbacks, events, onWin, onReady, deep link, getWidgetParams, IWidgetParams, widget params, readiness, lifecycle, push
6
+
7
+ ## Readiness gate
8
+ Call `window._smartico?.checkSuccessfullyIdentify()` (synchronous) before any `_smartico.api.*` — `true` once init + identify completed. Or subscribe `_smartico.on('identify', cb)` (see the callbacks page).
9
+
10
+ ## Methods
11
+ - `_smartico.api: WSAPI` — The authenticated WebSocket API surface (`_smartico.api.*`). Available once the SDK has initialized and identified the user — gate calls on `SmarticoGlobal.checkSuccessfullyIdentify`.
12
+ - `_smartico.vapi(language: string): WSAPI` — Visitor-mode (unauthenticated) API surface for the given language. Returns a `WSAPI` scoped to anonymous visitors.
13
+ - `_smartico.checkSuccessfullyIdentify(): boolean` — `true` once the SDK is initialized **and** the user has been successfully identified. Use as a synchronous gate before calling any `api.*` method.
14
+ - `_smartico.init(labelApiKey: string, params?: SmarticoInitParams): void` — Initialize the SDK for an identified integration.
15
+ - `_smartico.initVisitorMode(labelApiKey: string, params?: SmarticoInitParams): void` — Initialize the SDK in visitor (anonymous) mode.
16
+ - `_smartico.identify(extUserId: string, hash: string, payload?: Record<string, unknown>): Promise<void>` — Identify the current user against the label.
17
+ - `_smartico.online(extUserId: string, language: string, userHash: string, payload?: Record<string, unknown>): Promise<void>` — Mark an identified user as online for the given language.
18
+ - `_smartico.login(language?: string, payload?: Record<string, unknown>): void` — Re-run the login flow for the current session.
19
+ - `_smartico.logout(payload?: Record<string, unknown>): void` — Log the current user out of the SDK session.
20
+ - `_smartico.clear(params?: Record<string, unknown>): void` — Clear SDK state/cookies.
21
+ - `_smartico.changeLanguage(language: string): void` — Switch the active UI language.
22
+ - `_smartico.on(event: string, cb: null | ((...args: any[]) => void), params?: Record<string, unknown>): void` — Subscribe to a lifecycle/engagement callback. Pass `null` as the handler to unsubscribe. See the callbacks reference for available keys.
23
+ - `_smartico.off(event: string, cb: null | ((...args: any[]) => void)): void` — Remove a previously registered callback.
24
+ - `_smartico.dp(deepLink: string): void` — Trigger a Smartico deep link.
25
+ - `_smartico.showWidget(widgetType: SmarticoWidgetType, params?: SmarticoWidgetParams): void` — Open a Smartico widget overlay/inline.
26
+ - `_smartico.miniGame(sawTemplateId: string, params: SmarticoWidgetParams, pendingMessageId?: string | number): void` — Open a mini-game widget for the given template.
27
+ - `_smartico.showBanner(params: { dom_id: string; placement_key: string }): void` — Render a banner into a host element.
28
+ - `_smartico.sendDataToBanner(params: { placement_key: string; data: Record<string, unknown> }): void` — Push data into an already-rendered banner.
29
+ - `_smartico.gamificationBlock(params: { dom_id: string; url: string; fit?: 'content' | 'fill' }): void` — Embed the gamification block into a host element.
30
+ - `_smartico.getPublicProps(): PublicProperties` — Snapshot of the user's public gamification properties.
31
+ - `_smartico.updatePublicTags(operation: 'add' | 'remove' | 'replace' | 'clear', tags?: string[]): void` — Add/remove/replace/clear the user's public tags.
32
+ - `_smartico.setUTMs(source: string, campaign: string): void` — Set UTM attribution for the session.
33
+ - `_smartico.setLoginEntry(entry: string): void` — Record the last login entry point.
34
+ - `_smartico.setAvatar(avatarId: string): void` — Set the user's avatar.
35
+ - `_smartico.setNickname(nickname: string): void` — Set the user's nickname.
36
+ - `_smartico.setSkin(skinName: string): void` — Set the active UI skin.
37
+ - `_smartico.requestPushPermissions(): void` — Prompt the browser for web-push permission.
38
+ - `_smartico.registerNativePushToken(token: string, platform?: number, appPackageId?: string): void` — Register a native push token (mobile webviews).
39
+ - `_smartico.event(eventType: string, payload?: Record<string, unknown>): void` — Send a client event.
40
+ - `_smartico.action(actionValue: unknown, extraPayload?: Record<string, unknown>): void` — Send a client action.
41
+ - `_smartico.sendServerError(message: string): void` — Report a client-side error to the server.
42
+ - `_smartico.sendServerDebug(message: string): void` — Send a client-side debug message to the server.
43
+ - `_smartico.suspendPopups(flag: boolean): void` — Temporarily suspend/resume automatic popups.
44
+ - `_smartico.suspendInbox(flag: boolean): void` — Temporarily suspend/resume the inbox.
45
+ - `_smartico.suspendMiniGames(flag: boolean): void` — Temporarily suspend/resume mini-games.
46
+ - `_smartico.showVisitorGame(params: Record<string, unknown>): void` — Show a visitor-mode game.
47
+ - `_smartico.convertVisitorGame(visitorWinUuid: string): void` — Convert a visitor-mode win into an identified reward.
48
+ - `_smartico.startLpAnalytics(params: { lp_template_uuid: string; shadow_root?: ShadowRoot | null }): void` — Start landing-page analytics for a template.
49
+ - `_smartico.sendAnalytics(activityId: number, viewTimeSec?: number): void` — Send a landing-page analytics activity.
50
+ - `_smartico.isMobile(): boolean` — `true` on mobile viewports.
51
+ - `_smartico.parseUrl(): Record<string, string>` — Parse the current URL query into a map.
52
+ - `_smartico.parseUrlHash(): Record<string, string>` — Parse the current URL hash into a map.
53
+
54
+ ## Visitor mode (`vapi`)
55
+ `_smartico.vapi(language)` returns a `WSAPI` scoped to anonymous visitors — same method shapes as `_smartico.api.*`, for unidentified users (pre-login surfaces).
56
+
57
+ ## Reading widget params
58
+ A widget embedded via `showWidget` / `miniGame` receives `IWidgetParams` (theme, height, inline, …). Read them from the embedding snippet / `window.__SMARTICO_LP_PARAMS__`, not from an API call.
59
+
60
+ ## Related
61
+ - `_smartico.on — events & callbacks` (the callbacks capability page)
62
+ - Full prose reference: the SmarticoObject doc (search source `api-raw`).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartico/public-api",
3
- "version": "0.0.402",
3
+ "version": "0.0.404",
4
4
  "description": "Smartico public API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -24,7 +24,8 @@
24
24
  "bump-ach": "cd ../tracker/ach && npm install @smartico/public-api@latest --legacy-peer-deps",
25
25
  "bump-bo-server": "cd ../bo_server/server && source ~/.nvm/nvm.sh && nvm use v13.13.0 && npm install @smartico/public-api@latest",
26
26
  "bump-bo-client": "cd ../bo_server/client && source ~/.nvm/nvm.sh && nvm use v13.13.0 && npm install @smartico/public-api@latest",
27
- "doc": "typedoc && node scripts/postdoc-cleanup.js"
27
+ "doc": "typedoc && node scripts/postdoc-cleanup.js",
28
+ "gen-capabilities": "node scripts/gen-capabilities.js"
28
29
  },
29
30
  "author": "",
30
31
  "license": "ISC",
@@ -0,0 +1,208 @@
1
+ # Smartico public-api — docs & RAG pipeline (developer guide)
2
+
3
+ How the `@smartico/public-api` documentation is produced, how the IDE coding
4
+ agent retrieves it, and **how to keep it all up to date**. Read this before
5
+ touching TSDoc, the capability pages, the capture scripts, or the bo_server
6
+ ingestion.
7
+
8
+ ---
9
+
10
+ ## 1. The big picture
11
+
12
+ ```
13
+ SOURCE OF TRUTH (you edit) GENERATED (never edit) CONSUMED BY
14
+ ───────────────────────── ────────────────────── ───────────
15
+ src/WSAPI/WSAPI<Domain>.ts ─┐
16
+ method TSDoc (usage) │
17
+ src/WSAPI/WSAPITypes.ts + │ npm run gen-capabilities
18
+ domain types (field docs) ├──────────────────────────► docs/capabilities/<method>.md ─┐
19
+ docs/ui/<domain>/UIGuide_*.md │ (scripts/gen-capabilities.js) (one self-contained │
20
+ (behavioral contract) │ "API capability page") │
21
+ docs/capabilities/_responses/ │ │
22
+ <method>.json (REAL shapes)─┘ │
23
+ │ JobLLM2 (env2 cron)
24
+ src/**.ts (TSDoc) ──────────────► docs/api/** (TypeDoc reference) ───┐ ├──► llm.docs2 (Postgres, GAMES db)
25
+ npm run doc (scripts/postdoc-cleanup.js) │ ingested as │ + embeddings (llm.chunks2)
26
+ │ PUBLIC_API_DOCS │
27
+ │ ("api-raw" lane) ▼
28
+ └──────────────► IDE coding agent
29
+ docs/capabilities/** ────────────────────────────────────────────────────► ingested as PUBLIC_API_CAPABILITY
30
+ (the PRIMARY "api" retrieval lane)
31
+ ```
32
+
33
+ **The IDE agent retrieves capability pages, not raw source.** Each page is one
34
+ task-completable unit: signature + full inline return shape + behavioral
35
+ contract + a **real example response** + errors + related links. Design rules
36
+ live in `../bo_server/server/src/ide/SMARTICO_DOC_STRUCTURE_GUIDE.md`.
37
+
38
+ ---
39
+
40
+ ## 2. Source of truth — what to edit where
41
+
42
+ | Edit | For | Owned by |
43
+ |---|---|---|
44
+ | `src/WSAPI/WSAPI<Domain>.ts` method TSDoc | narrative usage: preconditions, error tables, refresh/cache, idempotency, `@example`, `{@link}` | `api-tsdoc` skill |
45
+ | `src/WSAPI/WSAPITypes.ts` + `src/<Domain>/*.ts` field comments | terse one-line field definitions (what + type/units + null semantics) | `api-tsdoc` skill |
46
+ | `docs/ui/<domain>/UIGuide_<method>.md` | UI/behavioral contract prose | `api-tsdoc` skill |
47
+ | `docs/capabilities/_responses/<method>.json` | the **captured real response** (anonymized) | capture scripts (§4) |
48
+ | `tsconfig.json` `typedocOptions.entryPoints` | add new public types/classes so they appear in `docs/api` | manual |
49
+
50
+ **NEVER hand-edit** (regenerated, overwritten):
51
+ - `docs/api/**` — TypeDoc output (`cleanOutputDir: true`).
52
+ - `docs/capabilities/*.md` — generated by `gen-capabilities.js`.
53
+
54
+ ---
55
+
56
+ ## 3. Keeping docs updated — the per-release workflow
57
+
58
+ Run after any change to a method's signature, behavior, or types.
59
+
60
+ 1. **Enrich the source** — TSDoc + field comments + UI guide. Use `/api-tsdoc <method>` (one method) or `/api-tsdoc-final` (whole release batch). These never commit and never run the build steps below.
61
+ 2. **Capture / refresh the real response** for the method → `docs/capabilities/_responses/<method>.json` (§4).
62
+ 3. **Generate capability pages**: `npm run gen-capabilities`. Rewrites all `docs/capabilities/*.md` from TSDoc + types + the captured responses.
63
+ 4. **(Reference site)** `npm run doc` — TypeDoc → `docs/api/**` (+ `postdoc-cleanup.js`). Human-triggered; confirm zero new warnings.
64
+ 5. **Ship**: commit; publish (`npm version patch && npm publish`) so the npm package + `run_public_api` see new methods; `npm run bump-tracker` to flow into the live `_smartico.api`.
65
+ 6. **RAG ingest is automatic**: `JobLLM2` (bo_server, env2 cron, every 60s) clones `public-api`, re-indexes changed `docs/capabilities/**` under `PUBLIC_API_CAPABILITY`. No manual step. It emails `MAIL_TO_AI_PRODUCT` when done.
66
+
67
+ **Coverage check:** `ls docs/capabilities/_responses/*.json | wc -l` vs `ls docs/capabilities/*.md | wc -l`. Methods without a `_responses` file still get a complete Returns shape from the type registry (fallback), just no real example payload.
68
+
69
+ ---
70
+
71
+ ## 4. Capturing real response shapes
72
+
73
+ Two mechanisms. **Prefer `run_public_api`** for everything it can reach; use the browser script only for the auth-only gaps.
74
+
75
+ ### 4a. Server-side — `mcp__hive__run_public_api` (visitor mode, no browser)
76
+
77
+ Runs the real published WSAPI bound to label+brand+user, returns `JSON.stringify(result)`. No user-hash, no CORS, no SDK loader.
78
+
79
+ - **Captures:** almost all `getX()` fetch methods.
80
+ - **Cannot capture** (runs in *visitor / no-tracker* mode): `getUserProfile`, `getInboxMessageBody` (read tracker state), all `gamePick*` ("not available in visitor mode").
81
+ - **Caveat:** runs the **published npm** SDK → lags local `main`. Newly-added methods aren't there until `npm publish`.
82
+
83
+ ### 4b. Headless real-user — `scripts/capture-responses-browser.mjs`
84
+
85
+ Loads the real `smartico.js` tracker bundle, `init` + `identify` as a real player, then sweeps every `_smartico.api.*` and writes anonymized `_responses/<method>.json`. Covers the auth-only gaps.
86
+
87
+ ```sh
88
+ npm i -D playwright && npx playwright install chromium # one-time
89
+ node scripts/capture-responses-browser.mjs # read-only sweep
90
+ SM_MUTATIONS=1 node scripts/capture-responses-browser.mjs # + execute mutations (test acct only)
91
+ SM_DEBUG=1 node scripts/capture-responses-browser.mjs # full console + per-WS-frame trace
92
+ npm run gen-capabilities # rebuild pages after capture
93
+ ```
94
+
95
+ Progress/diagnostics stream to **`/tmp/sm-capture.log`** (stdout is buffered in background).
96
+
97
+ **Env overrides** (defaults = the env4 ICE **test** label):
98
+
99
+ | Var | Default | Meaning |
100
+ |---|---|---|
101
+ | `SM_LABEL` | `a6e7ac26-…-4` | label key (last char = env) |
102
+ | `SM_BRAND` | `f86271e6` | brand key |
103
+ | `SM_USER` | `4579cace-…c45b` | player ext id |
104
+ | `SM_SALT` | `null` | label hash salt (env4 ICE = literal `'null'`) |
105
+ | `SM_ORIGIN` | `https://app.smartico.ai/` | self-served CSP-free page origin |
106
+ | `SM_SCRIPT_URL` | `https://libs.smartico.ai/smartico.js` | tracker bundle |
107
+ | `SM_UA` | desktop Chrome | user-agent |
108
+ | `SM_MUTATIONS` | off | run the mutation pass |
109
+ | `SM_DEBUG` | off | verbose console + WS frames |
110
+
111
+ **How identify works (hard-won):**
112
+ - `smartico.js` builds `_smartico`; identify is the **method form** `_smartico.identify(user, hash, {})` — the queue form `_smartico('identify', …)` silently no-ops after load.
113
+ - hash = `md5( lower(`${user}:${salt}:${ts}`) ) + ':' + ts`, `ts = floor(now/1000)*1000 + 24h`. Server accepts it when the label salt matches (`'null'` for env4 ICE).
114
+ - realtime WS = `wss://api{ENV}.smartico.ai/websocket/services`.
115
+ - **Why a self-served origin:** `libs.smartico.ai` 403s headless clients and its error page's CSP blocks injected scripts; the script routes a blank CSP-free page on `app.smartico.ai` and fetches `smartico.js` cross-origin with a realistic UA.
116
+
117
+ **Dynamic ids + richest sampling:** the sweep fetches list methods first and harvests every param id (tournament instance, clan, jp_template, raffle/draw/run, inbox guid, gamepick matchx/quiz template) — **no hardcoded ids**. For list methods it stores the *most-populated* item so the captured shape isn't a degenerate first element.
118
+
119
+ **Anonymization (automatic):** CDN hosts (`cloudfront.net`/`smartico.ai`/`smr.vc`) → `cdn.example`; any UUID → zeros; `clean_ext_user_id` → `0`. Keep product config (names, prize tables) — only the field *shape* matters.
120
+
121
+ ### 4c. Mutations (`SM_MUTATIONS=1`)
122
+
123
+ Executes write methods on the **test account** to capture result shapes; reversibles are restored. Captured: jackpot opt in/out, inbox mark-read/favorite/mark-all, setAvatar (restored), mission opt-in, claimBonus, registerInTournament, buyStoreItem, playMiniGame + acknowledge + batch. Skipped by guards when no suitable entity exists (claim-reward, raffle-optin, joinClan). **Never runs `deleteAllInboxMessages` / single delete** (destructive) or the gamepick submit (charges buy-in). Real error codes are valuable too (e.g. `buyStoreItem → 11002`).
124
+
125
+ ---
126
+
127
+ ## 5. The generator — `scripts/gen-capabilities.js`
128
+
129
+ Pure, deterministic Node + the `typescript` compiler API (no extra deps). Per public WSAPI method it emits `docs/capabilities/<method>.md`:
130
+
131
+ - **facts header** (title, one-line summary, import, Search-terms) — high retrieval weight.
132
+ - **Signature / Parameters** — from the method node + `@param`.
133
+ - **Returns** — annotates the **captured response** fields with the **return type's own** comments (per-type resolved, inheritance-aware, nested via the property's element type). No captured response → falls back to the resolved **type** shape (still complete).
134
+ - **Behavioral contract** — the method's `@remarks` (with an Error-codes block split out into `## Errors`).
135
+ - **Example** + **Example response (REAL shape)** — the `@example` + the captured JSON.
136
+ - **Related** — from `{@link}` targets.
137
+
138
+ Run: `npm run gen-capabilities`. Reads `_responses/<method>.json` if present. Never commits.
139
+
140
+ ---
141
+
142
+ ## 6. RAG ingestion (bo_server)
143
+
144
+ | Piece | File | What |
145
+ |---|---|---|
146
+ | Source-type enum | `server/src/Managers/LLMManager.ts` | `LLMSourceTypeId.PUBLIC_API_CAPABILITY = 8` |
147
+ | Ingest job | `server/src/Jobs/JobLLM2.ts` → `processPublicAPI()` | indexes `docs/capabilities/**` under the new type; the raw walk skips `capabilities`/`_responses` (`LLM_SKIP_DIRS`). env2 only, 60s refresh. |
148
+ | IDE routing | `server/src/ide/ideSmarticoTools.ts` | `SOURCE_TYPE_MAP.api → [PUBLIC_API_CAPABILITY]` (primary); `api-raw → [PUBLIC_API_DOCS]` (raw source fallback) |
149
+ | IDE tools | `ideSmarticoTools.ts` | `search_smartico_reference` (semantic + Cohere rerank → top distinct docs), `read_smartico_reference` (one doc by id), `list_smartico_reference` |
150
+ | Design rules | `server/src/ide/SMARTICO_DOC_STRUCTURE_GUIDE.md` | the capability-page spec |
151
+
152
+ The IDE `api` lane reads **only** capability pages. A method with no capability page is invisible to the agent — always run `gen-capabilities` after enriching.
153
+
154
+ ---
155
+
156
+ ## 7. Skills
157
+
158
+ - **`/api-tsdoc <method>`** — enrich one method end-to-end (TSDoc + UI guide + capture a response + regenerate the page). Never commits / never runs `npm run doc`.
159
+ - **`/api-tsdoc-final`** — the whole release doc batch (callbacks, `_smartico` object, native protocol, all method TSDocs, capability capture + generation), stops at a prepared commit message.
160
+
161
+ Skill files: `~/.claude/skills/api-tsdoc/SKILL.md`, `~/.claude/skills/api-tsdoc-final/SKILL.md`.
162
+
163
+ ---
164
+
165
+ ## 8. Adding a NEW method — checklist
166
+
167
+ 1. Implement the method in `src/WSAPI/WSAPI<Domain>.ts`; add return/types to `WSAPITypes.ts` (or a domain types file).
168
+ 2. Add new public types to `tsconfig.json` `typedocOptions.entryPoints`.
169
+ 3. `/api-tsdoc <method>` → TSDoc + field comments + UI guide.
170
+ 4. Capture a real response (§4) → `_responses/<method>.json`.
171
+ 5. `npm run gen-capabilities` → new `docs/capabilities/<method>.md`. Spot-check Returns has the right per-domain field comments + a real example.
172
+ 6. Commit; `npm publish`; `npm run bump-tracker`. RAG re-indexes automatically.
173
+
174
+ ---
175
+
176
+ ## 9. Field-type facts (server-determined; verified via `run_public_api`)
177
+
178
+ - `*_ts` / `*_date_ts` → **epoch-ms number**. Round/event/store/raffle timestamps → number ms.
179
+ - Plain Java date strings (`complete_date`, `unlock_date`, `sent_date`, bonus `create_date`/`redeem_date`) → **`"dd/MM/yyyy HH:mm:ss"` string, NOT ISO-8601** (`new Date()` won't parse them).
180
+ - GamePick (Node `r-games-server`) date strings → real ISO-8601; `match_date` / `event_resolution_date` / `last_wallet_sync_time` are **strings**, not numbers.
181
+ - `TActivityLog.create_date` → epoch **seconds** (the lone outlier).
182
+ - Avatar `dt_created` → epoch-ms number.
183
+ - GamePick request params `ext_user_id` / `smartico_ext_user_id` are SDK-injected (optional for consumers).
184
+
185
+ ---
186
+
187
+ ## 10. Troubleshooting
188
+
189
+ | Symptom | Cause / fix |
190
+ |---|---|
191
+ | `run_public_api`: "Tracker is not initialized" | tracker-state method (`getUserProfile`). Use the browser script. |
192
+ | `run_public_api`: "not available in visitor mode" | auth-only (`gamePick*`). Use the browser script. |
193
+ | `run_public_api`: method "is not a function" | published SDK lags local `main`. `npm publish` first. |
194
+ | Browser: `smartico.js → error` / never loads | origin blocked/CSP. Set `SM_ORIGIN` to an allowlisted origin; ensure realistic `SM_UA`. |
195
+ | Browser: `NOT identified (timeout)` | wrong hash/salt, or identify called the queue form. Confirm `SM_SALT`; identify must be method form (already is). Inspect with `SM_DEBUG=1` (`/tmp/sm-capture.log`). |
196
+ | Capture hangs | per-call 8s + stage caps already bound it; check `/tmp/sm-capture.log`. |
197
+ | `undefined.filter`/`.map` from a getter | SDK robustness bug on empty data (`getBonuses`, `getJackpotEligibleGames`) — capture once a relevant entity exists, or fix the SDK guard. |
198
+ | Capability page shows another domain's field comment | the return type is missing that field → add it to the type (don't edit the page). |
199
+ | Internal leak in a page (DB table, ClassId) | a source **field comment** leaked it → fix the comment (public-contract rule). |
200
+
201
+ ---
202
+
203
+ ## 11. Reference
204
+
205
+ **npm scripts:** `gen-capabilities`, `doc` (TypeDoc), `build`, `bump-tracker`.
206
+ **Scripts:** `scripts/gen-capabilities.js`, `scripts/capture-responses-browser.mjs`, `scripts/postdoc-cleanup.js`.
207
+ **Test creds (env4 ICE — dev/test label only):** label `a6e7ac26-c368-4892-9380-96e7ff82cf3e-4`, brand `f86271e6`, user `4579cace-9069-4d65-93ff-9b8b6c83c45b`, salt `null`.
208
+ **Current coverage:** 52 / 70 methods have a real captured response; the rest use the type-fallback Returns.
@@ -0,0 +1,301 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * capture-responses-browser.mjs — REAL-USER response-shape capture (headless).
4
+ *
5
+ * Loads the real Smartico tracker bundle (smartico.js) in a headless browser,
6
+ * does `init` + `identify` as an actual identified player, then calls every
7
+ * `_smartico.api.*` GET method and writes one anonymized sample per method to
8
+ * `docs/capabilities/_responses/<method>.json` (the generator inputs for
9
+ * gen-capabilities.js).
10
+ *
11
+ * Why a browser (vs the server-side `run_public_api`): methods that read tracker
12
+ * state — `getUserProfile`, `getInboxMessageBody` — and the auth-only GamePick
13
+ * methods are NOT available in the server's visitor mode. Real identify fills
14
+ * those gaps and gives shapes identical to what a browser consumer sees.
15
+ *
16
+ * Setup + run:
17
+ * npm i -D playwright && npx playwright install chromium
18
+ * node scripts/capture-responses-browser.mjs
19
+ *
20
+ * Creds default to the env4 ICE test label (salt_key 'null' → the user-hash is
21
+ * computable client-side; no server secret needed). Override via env:
22
+ * SM_LABEL, SM_BRAND, SM_USER, SM_SALT, SM_SCRIPT_URL
23
+ */
24
+ import { chromium } from 'playwright';
25
+ import fs from 'fs';
26
+ import path from 'path';
27
+ import crypto from 'crypto';
28
+ import { fileURLToPath } from 'url';
29
+
30
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
31
+ const RESP_DIR = path.join(__dirname, '..', 'docs', 'capabilities', '_responses');
32
+
33
+ const LABEL = process.env.SM_LABEL || 'a6e7ac26-c368-4892-9380-96e7ff82cf3e-4';
34
+ const BRAND = process.env.SM_BRAND || 'f86271e6';
35
+ const USER = process.env.SM_USER || '4579cace-9069-4d65-93ff-9b8b6c83c45b';
36
+ const SALT = process.env.SM_SALT || 'null';
37
+ const SCRIPT_URL = process.env.SM_SCRIPT_URL || 'https://libs.smartico.ai/smartico.js';
38
+
39
+ // hash = md5(lower(`user:salt:ts`)) + ':' + ts ; ts = now + 24h, ms (matches the
40
+ // tracker's expo/public/index.html harness exactly).
41
+ function userHash(user, salt) {
42
+ const ts = Math.floor(Date.now() / 1000) * 1000 + 24 * 3600 * 1000;
43
+ const input = `${user.toLowerCase()}:${salt}:${ts}`.toLowerCase();
44
+ return crypto.createHash('md5').update(input).digest('hex') + ':' + ts;
45
+ }
46
+
47
+ const anon = (v) => {
48
+ if (typeof v === 'string') return v
49
+ .replace(/https?:\/\/[a-z0-9.\-]+\.(cloudfront\.net|smartico\.ai|smrcdn\.cloud|smr\.vc)/gi, 'https://cdn.example')
50
+ .replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, '00000000-0000-0000-0000-000000000000');
51
+ if (Array.isArray(v)) return v.map(anon);
52
+ if (v && typeof v === 'object') { const o = {}; for (const k of Object.keys(v)) o[k] = (k === 'clean_ext_user_id') ? '0' : anon(v[k]); return o; }
53
+ return v;
54
+ };
55
+
56
+ // Bound a captured value so a method returning a huge map/array (e.g.
57
+ // getTranslations → ~800 keys) doesn't produce an 800-line input file. A bit more
58
+ // generous than the page generator (which re-trims to depth 3 / 1 item): keeps a
59
+ // representative sample. strings→200 · arrays→2 · dictionary objects (>50 keys)→20
60
+ // · nesting beyond depth 5 collapses.
61
+ const bound = (v) => {
62
+ if (typeof v === 'string') return v.length > 200 ? v.slice(0, 197) + '…' : v;
63
+ if (Array.isArray(v)) return v.slice(0, 2).map(bound); // no depth collapse — keep the real nested shape
64
+ if (v && typeof v === 'object') {
65
+ const keys = Object.keys(v);
66
+ const cap = keys.length > 50; // only dictionary-sized maps (getTranslations) are capped
67
+ const o = {};
68
+ for (const k of keys.slice(0, cap ? 20 : keys.length)) o[k] = bound(v[k]);
69
+ if (cap) o['…'] = `(+${keys.length - 20} more keys)`;
70
+ return o;
71
+ }
72
+ return v;
73
+ };
74
+
75
+ (async () => {
76
+ const LOG = '/tmp/sm-capture.log';
77
+ const log = (m) => { const line = '[capture] ' + m; console.log(line); try { fs.appendFileSync(LOG, line + '\n'); } catch {} };
78
+ try { fs.writeFileSync(LOG, ''); } catch {}
79
+ const DEBUG = !!process.env.SM_DEBUG; // SM_DEBUG=1 → full console + per-frame WS trace
80
+ const nodeTimeout = (p, ms, label) => Promise.race([p, new Promise((_, rej) => setTimeout(() => rej(new Error('node-timeout ' + ms + 'ms @ ' + label)), ms))]);
81
+
82
+ const hash = userHash(USER, SALT);
83
+ log('launching chromium…');
84
+ const browser = await chromium.launch({ headless: true });
85
+ const context = await browser.newContext({
86
+ userAgent: process.env.SM_UA || 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36',
87
+ viewport: { width: 1280, height: 800 }, locale: 'en-US',
88
+ });
89
+ const page = await context.newPage();
90
+ page.on('console', m => { if (DEBUG || ['error', 'warning'].includes(m.type())) log(' [console:' + m.type() + '] ' + m.text().slice(0, 260)); });
91
+ page.on('requestfailed', r => log(' [requestfailed] ' + r.url().slice(0, 120) + ' — ' + (r.failure()?.errorText || '')));
92
+ page.on('pageerror', e => log(' [pageerror] ' + String(e.message || e).slice(0, 240)));
93
+ let wsFrames = 0;
94
+ page.on('websocket', ws => {
95
+ log(' [ws OPEN] ' + ws.url().slice(0, 140));
96
+ ws.on('socketerror', e => log(' [ws ERROR] ' + String(e).slice(0, 200)));
97
+ ws.on('close', () => log(' [ws CLOSE] ' + ws.url().slice(0, 80)));
98
+ ws.on('framesent', f => { if (DEBUG && wsFrames++ < 40) log(' [ws →] ' + String(f.payload).slice(0, 160)); });
99
+ ws.on('framereceived', f => { if (DEBUG && wsFrames++ < 40) log(' [ws ←] ' + String(f.payload).slice(0, 160)); });
100
+ });
101
+
102
+ // Serve a blank, CSP-free page on a stable https origin. libs.smartico.ai 403s
103
+ // the headless client and its error page carries a CSP that blocks injected
104
+ // scripts; a self-served origin sidesteps both. smartico.js is still fetched
105
+ // over the real network with the realistic UA above. Override origin via SM_ORIGIN.
106
+ const ORIGIN = process.env.SM_ORIGIN || 'https://app.smartico.ai/';
107
+ await page.route(ORIGIN + '**', r => r.fulfill({ status: 200, contentType: 'text/html', body: '<!doctype html><html><head></head><body></body></html>' }));
108
+ log('goto sandbox origin ' + ORIGIN + ' …');
109
+ await nodeTimeout(page.goto(ORIGIN, { waitUntil: 'domcontentloaded' }), 20000, 'goto').catch(e => log('goto: ' + e.message));
110
+
111
+ log('injecting smartico.js …');
112
+ const loadState = await nodeTimeout(page.evaluate((SCRIPT_URL) => new Promise((resolve) => {
113
+ window._smartico_script_location = SCRIPT_URL;
114
+ const s = document.createElement('script');
115
+ s.src = SCRIPT_URL;
116
+ s.onload = () => resolve('loaded');
117
+ s.onerror = () => resolve('error');
118
+ document.head.appendChild(s);
119
+ }), SCRIPT_URL), 25000, 'inject').catch(e => 'timeout:' + e.message);
120
+ log('smartico.js → ' + loadState);
121
+
122
+ await page.waitForFunction(() => typeof window._smartico !== 'undefined', { timeout: 10000 }).catch(() => {});
123
+ log('init + identify …');
124
+ const idDiag = await page.evaluate(({ LABEL, BRAND, USER, hash }) => {
125
+ if (typeof window._smartico === 'undefined') return { reason: 'no _smartico' };
126
+ const t = { smartico: typeof window._smartico, identify: typeof window._smartico.identify, api: typeof window._smartico.api };
127
+ try {
128
+ window._smartico_user_id = USER;
129
+ window._smartico_user_hash = hash;
130
+ window._smartico.init(LABEL, { brand_key: BRAND, debug: true });
131
+ if (typeof window._smartico.identify === 'function') { window._smartico.identify(USER, hash, {}); t.call = 'identify()'; }
132
+ else { window._smartico('identify', USER, hash, {}); t.call = 'queue'; }
133
+ } catch (e) { t.error = e.message; }
134
+ return t;
135
+ }, { LABEL, BRAND, USER, hash }).catch(e => ({ evalError: e.message }));
136
+ log('identify diag: ' + JSON.stringify(idDiag));
137
+
138
+ const identified = await page.waitForFunction(() => window._smartico?.checkSuccessfullyIdentify?.() === true, { timeout: 35000 })
139
+ .then(() => true).catch(() => false);
140
+ log(identified ? 'identified ✓ — calling api methods' : 'NOT identified (timeout) — calling anyway (auth methods will error)');
141
+ if (await page.evaluate(() => typeof window._smartico) === 'undefined') { log('FATAL: _smartico never loaded (smartico.js blocked). Run from an allowed origin/network.'); await browser.close(); process.exit(2); }
142
+ if (!identified) {
143
+ log('identify did not complete — skipping the api sweep (auth-only methods need a live identified session).');
144
+ log('likely cause: WS backend unreachable from here, origin not allowlisted for the label, or hash/salt rejected. Run from an allowed origin/network, or set SM_SALT / a precomputed hash.');
145
+ await browser.close();
146
+ process.exit(3);
147
+ }
148
+
149
+ const results = await nodeTimeout(page.evaluate(async ({ MUTATE }) => {
150
+ const api = window._smartico.api;
151
+ const clip = (v) => {
152
+ if (typeof v === 'string') return v.length > 200 ? v.slice(0, 197) + '…' : v;
153
+ if (Array.isArray(v)) return v.map(clip);
154
+ if (v && typeof v === 'object') { const o = {}; for (const k of Object.keys(v)) o[k] = clip(v[k]); return o; }
155
+ return v;
156
+ };
157
+ // "richness" = count of populated (non-null/non-empty) fields — pick the most
158
+ // complete item so the captured shape isn't a degenerate first element.
159
+ const score = (o) => (o && typeof o === 'object' && !Array.isArray(o))
160
+ ? Object.values(o).filter(v => v !== null && v !== '' && !(Array.isArray(v) && v.length === 0)).length : 0;
161
+ const rich = (arr, n = 2) => Array.isArray(arr) ? arr.slice().sort((a, b) => score(b) - score(a)).slice(0, n).map(clip) : clip(arr);
162
+ const one = (r) => clip(Array.isArray(r) ? r.slice(0, 1) : r);
163
+ const out = {};
164
+ const withTimeout = (p, ms = 8000) => Promise.race([
165
+ Promise.resolve().then(() => p),
166
+ new Promise((_, rej) => setTimeout(() => rej(new Error('timeout ' + ms + 'ms')), ms)),
167
+ ]);
168
+ const safe = async (n, fn, post) => { try { const r = await withTimeout(Promise.resolve().then(fn)); out[n] = (post || (Array.isArray(r) ? rich : clip))(r); } catch (e) { out[n] = 'ERR: ' + (e && e.message || String(e)); } };
169
+ const raw = async (fn) => { try { return await withTimeout(Promise.resolve().then(fn)); } catch (e) { return null; } };
170
+ const now = Math.floor(Date.now() / 1000);
171
+
172
+ // ---- profile (tracker-state, browser-only) ----
173
+ try { out.getUserProfile = clip(api.getUserProfile()); } catch (e) { out.getUserProfile = 'ERR: ' + e.message; }
174
+
175
+ // ---- list fetches (raw kept for id-harvesting; richest sample stored) ----
176
+ const missions = await raw(() => api.getMissions()); out.getMissions = rich(missions);
177
+ const badges = await raw(() => api.getBadges()); out.getBadges = rich(badges);
178
+ await safe('getAchCategories', () => api.getAchCategories());
179
+ const bonuses = await raw(() => api.getBonuses()); out.getBonuses = rich(bonuses);
180
+ const store = await raw(() => api.getStoreItems()); out.getStoreItems = rich(store);
181
+ await safe('getStoreCategories', () => api.getStoreCategories());
182
+ await safe('getStorePurchasedItems', () => api.getStorePurchasedItems({ limit: 3 }));
183
+ const tours = await raw(() => api.getTournamentsList()); out.getTournamentsList = rich(tours);
184
+ const clans = await raw(() => api.getClans()); out.getClans = clip(clans);
185
+ const jackpot = await raw(() => api.jackpotGet()); out.jackpotGet = clip(jackpot);
186
+ const raffles = await raw(() => api.getRaffles()); out.getRaffles = rich(raffles);
187
+ const minigames = await raw(() => api.getMiniGames()); out.getMiniGames = rich(minigames);
188
+ await safe('getMiniGamesHistory', () => api.getMiniGamesHistory({ limit: 3 }));
189
+ const avatars = await raw(() => api.getAvatarsList()); out.getAvatarsList = rich(avatars);
190
+ await safe('getAvatarsCustomized', () => api.getAvatarsCustomized());
191
+ await safe('getAvatarPrompts', () => api.getAvatarPrompts());
192
+ const inbox = await raw(() => api.getInboxMessages()); out.getInboxMessages = rich(inbox);
193
+ await safe('getInboxUnreadCount', () => api.getInboxUnreadCount(), clip);
194
+ await safe('getCustomSections', () => api.getCustomSections());
195
+ await safe('getTranslations', () => api.getTranslations('EN'), clip);
196
+ await safe('getCurrentLevel', () => api.getCurrentLevel(), clip);
197
+ await safe('getLevels', () => api.getLevels());
198
+ await safe('getUserLevelExtraCounters', () => api.getUserLevelExtraCounters(), clip);
199
+ await safe('getActivityLog', () => api.getActivityLog({ startTimeSeconds: now - 2592000, endTimeSeconds: now, from: 0, to: 20 }));
200
+
201
+ // ---- detail / param methods — every id harvested from the lists above (no hardcodes) ----
202
+ const ids = {};
203
+ ids.tournamentInstance = tours?.[0]?.instance_id;
204
+ if (ids.tournamentInstance != null) out.getTournamentInstanceInfo = clip(await raw(() => api.getTournamentInstanceInfo(ids.tournamentInstance)));
205
+ ids.clanId = clans?.clans?.[0]?.clan_id;
206
+ if (ids.clanId != null) out.getClanInfo = clip(await raw(() => api.getClanInfo(ids.clanId)));
207
+ ids.jpTemplate = Array.isArray(jackpot) ? jackpot?.[0]?.jp_template_id : jackpot?.jp_template_id;
208
+ if (ids.jpTemplate != null) {
209
+ await safe('getJackpotWinners', () => api.getJackpotWinners({ jp_template_id: ids.jpTemplate }));
210
+ await safe('getJackpotEligibleGames', () => api.getJackpotEligibleGames({ jp_template_id: ids.jpTemplate }));
211
+ }
212
+ ids.inboxGuid = inbox?.[0]?.message_guid;
213
+ if (ids.inboxGuid) await safe('getInboxMessageBody', () => api.getInboxMessageBody(ids.inboxGuid), clip);
214
+ const raffle0 = raffles?.[0]; const draw0 = raffle0?.draws?.[0];
215
+ if (raffle0 && draw0) {
216
+ await safe('getRaffleDrawRun', () => api.getRaffleDrawRun({ raffle_id: raffle0.id, run_id: draw0.run_id }), clip);
217
+ await safe('getRaffleDrawRunsHistory', () => api.getRaffleDrawRunsHistory({ raffle_id: raffle0.id }));
218
+ }
219
+ await safe('getLeaderBoard', () => api.getLeaderBoard(1), clip);
220
+
221
+ // ---- GamePick: find a usable MatchX/Quiz template from the minigame list ----
222
+ const gpGames = (minigames || []).filter(g => g.saw_game_type === 'matchx' || g.saw_game_type === 'quiz');
223
+ for (const g of gpGames) {
224
+ const info = await raw(() => api.gamePickGetGameInfo({ saw_template_id: g.id }));
225
+ if (info && info.errCode === 0) {
226
+ out.gamePickGetGameInfo = clip(info);
227
+ const rounds = await raw(() => api.gamePickGetActiveRounds({ saw_template_id: g.id }));
228
+ out.gamePickGetActiveRounds = one(rounds);
229
+ const roundId = rounds?.data?.[0]?.round_id;
230
+ ids.gpTemplate = g.id; ids.gpRound = roundId;
231
+ if (roundId) {
232
+ out.gamePickGetActiveRound = one(await raw(() => api.gamePickGetActiveRound({ saw_template_id: g.id, round_id: roundId })));
233
+ out.gamePickGetBoard = one(await raw(() => api.gamePickGetBoard({ saw_template_id: g.id, round_id: roundId })));
234
+ }
235
+ out.gamePickGetHistory = one(await raw(() => api.gamePickGetHistory({ saw_template_id: g.id })));
236
+ out.gamePickGetUserInfo = one(await raw(() => api.gamePickGetUserInfo({ saw_template_id: g.id })));
237
+ break;
238
+ }
239
+ }
240
+
241
+ if (!MUTATE) return out;
242
+
243
+ // =================== MUTATIONS (opt-in via SM_MUTATIONS=1) ===================
244
+ // TEST ACCOUNT ONLY. Reversible mutations are restored; one-shot/consuming ones
245
+ // (claim/buy/register/play) intentionally change state to capture the result shape.
246
+ const prof = (() => { try { return api.getUserProfile() || {}; } catch { return {}; } })();
247
+
248
+ if (ids.jpTemplate != null) { // jackpot opt in → out (net opted-out)
249
+ await safe('jackpotOptIn', () => api.jackpotOptIn({ jp_template_id: ids.jpTemplate }), clip);
250
+ await safe('jackpotOptOut', () => api.jackpotOptOut({ jp_template_id: ids.jpTemplate }), clip);
251
+ }
252
+ if (ids.inboxGuid) { // inbox mark read + favorite toggle (favorite restored)
253
+ await safe('markInboxMessageAsRead', () => api.markInboxMessageAsRead(ids.inboxGuid), clip);
254
+ await safe('markUnmarkInboxMessageAsFavorite', () => api.markUnmarkInboxMessageAsFavorite(ids.inboxGuid, true), clip);
255
+ await raw(() => api.markUnmarkInboxMessageAsFavorite(ids.inboxGuid, false));
256
+ await safe('markAllInboxMessagesAsRead', () => api.markAllInboxMessagesAsRead(), clip);
257
+ }
258
+ const av = (avatars || [])[0]; // setAvatar (restore original)
259
+ const newUrl = av?.avatar_url || av?.public_meta?.image_url;
260
+ const newReal = av?.avatar_real_id ?? av?.id;
261
+ if (newUrl && newReal != null) {
262
+ await safe('setAvatar', () => api.setAvatar({ avatar_url: newUrl, avatar_real_id: newReal }), clip);
263
+ if (prof.avatar_url && prof.avatar_real_id != null) await raw(() => api.setAvatar({ avatar_url: prof.avatar_url, avatar_real_id: prof.avatar_real_id }));
264
+ }
265
+ const optMission = (missions || []).find(m => m.is_requires_optin && !m.is_opted_in && !m.is_locked);
266
+ if (optMission) await safe('requestMissionOptIn', () => api.requestMissionOptIn(optMission.id), clip);
267
+ const claimMission = (missions || []).find(m => m.requires_prize_claim && m.ach_completed_id);
268
+ if (claimMission) await safe('requestMissionClaimReward', () => api.requestMissionClaimReward(claimMission.id, claimMission.ach_completed_id), clip);
269
+ let rOpt = null; // raffle opt-in (a draw that requires it, not yet opted-in)
270
+ for (const rf of (raffles || [])) { const d = (rf.draws || []).find(d => d.requires_optin && !d.user_opted_in && d.is_active); if (d) { rOpt = { raffle_id: rf.id, draw_id: d.id, raffle_run_id: d.run_id }; break; } }
271
+ if (rOpt) await safe('requestRaffleOptin', () => api.requestRaffleOptin(rOpt), clip);
272
+ const bonus = (bonuses || []).find(b => b.is_redeemable ?? b.redeemable) || (bonuses || [])[0];
273
+ if (bonus) await safe('claimBonus', () => api.claimBonus(bonus.id), clip); // consumes a bonus
274
+ const regT = (tours || []).find(t => t.is_can_register) || (tours || [])[0];
275
+ if (regT) await safe('registerInTournament', () => api.registerInTournament(regT.instance_id), clip); // success or insufficient-funds shape
276
+ const buyItem = (store || []).filter(s => s.purchase_type === 'points').sort((a, b) => (a.price || 0) - (b.price || 0))[0] || (store || [])[0];
277
+ if (buyItem) await safe('buyStoreItem', () => api.buyStoreItem(buyItem.id), clip); // success or insufficient-balance shape
278
+ const myClan = prof.core_clan_id ? Number(prof.core_clan_id) : null;
279
+ if (myClan) await safe('joinClan', () => api.joinClan(myClan), clip); // re-join current clan = harmless, still returns the shape
280
+ const freeGame = (minigames || []).find(g => g.saw_buyin_type === 'free' && Number(g.spin_count) !== 0) || (minigames || []).find(g => g.saw_buyin_type === 'free');
281
+ if (freeGame) { // play a FREE minigame (no currency) without auto-ack, then acknowledge
282
+ const play = await raw(() => api.playMiniGame(freeGame.id, { acknowledge: false }));
283
+ if (play) out.playMiniGame = clip(play);
284
+ if (play && play.request_id) await safe('miniGameWinAcknowledgeRequest', () => api.miniGameWinAcknowledgeRequest(play.request_id), clip);
285
+ await safe('playMiniGameBatch', () => api.playMiniGameBatch(freeGame.id, 1), clip);
286
+ }
287
+
288
+ return out;
289
+ }, { MUTATE: !!process.env.SM_MUTATIONS }), 240000, 'sweep');
290
+
291
+ let ok = 0, skip = [];
292
+ for (const [name, val] of Object.entries(results)) {
293
+ if (typeof val === 'string' && val.startsWith('ERR')) { skip.push(`${name}: ${val.slice(0, 70)}`); continue; }
294
+ fs.writeFileSync(path.join(RESP_DIR, `${name}.json`), JSON.stringify(bound(anon(val)), null, 2));
295
+ ok++;
296
+ }
297
+ log(`wrote ${ok} response files`);
298
+ if (skip.length) log('skipped (errors):\n ' + skip.join('\n '));
299
+ log('now run: npm run gen-capabilities');
300
+ await browser.close();
301
+ })().catch(e => { try { fs.appendFileSync('/tmp/sm-capture.log', '[capture] fatal: ' + e.message + '\n'); } catch {} console.error('[capture] fatal:', e.message); process.exit(1); });