@wix/agent-skills 1.20.0 → 1.21.0

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 (183) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-app/SKILL.md +14 -112
  3. package/skills/wix-app/references/APP_TOOLS.md +2 -1
  4. package/skills/wix-app/references/BACKEND_EVENT.md +3 -3
  5. package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +41 -152
  6. package/skills/wix-app/references/DASHBOARD_MODAL.md +4 -4
  7. package/skills/wix-app/references/SDK_MODULE_MAP.md +44 -0
  8. package/skills/wix-app/references/SITE_PLUGIN.md +36 -190
  9. package/skills/wix-app/references/app-tools/TOOLS.md +3 -1
  10. package/skills/wix-app/references/custom-element-widget/SETTINGS_PANEL.md +21 -0
  11. package/skills/wix-app/references/dashboard-page/ECOM_NAVIGATION.md +17 -0
  12. package/skills/wix-app/references/dashboard-plugin/SLOTS.md +4 -18
  13. package/skills/wix-app/references/site-plugin/EXAMPLES.md +1 -1
  14. package/skills/wix-headless-fast/SKILL.md +302 -51
  15. package/skills/wix-headless-fast/entry/skill.md +11 -2
  16. package/skills/wix-headless-fast/install/AGENTS.md +32 -0
  17. package/skills/wix-headless-fast/install/agents-md.mjs +39 -0
  18. package/skills/wix-headless-fast/install/attach.mjs +360 -0
  19. package/skills/wix-headless-fast/install/deploy.mjs +109 -9
  20. package/skills/wix-headless-fast/install/setup.mjs +239 -0
  21. package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +226 -60
  22. package/skills/wix-headless-fast/references/blog/app/components/blog/BlogFeedView.tsx +2 -3
  23. package/skills/wix-headless-fast/references/blog/app/components/blog/PostView.tsx +2 -3
  24. package/skills/wix-headless-fast/references/blog/app/hooks/blog/useBlogFeed.ts +24 -116
  25. package/skills/wix-headless-fast/references/blog/app/hooks/blog/usePost.ts +16 -64
  26. package/skills/wix-headless-fast/references/blog/app/wix/blog/blog-feed-store.ts +137 -0
  27. package/skills/wix-headless-fast/references/blog/app/wix/blog/post-store.ts +78 -0
  28. package/skills/wix-headless-fast/references/blog/app/wix/blog/posts-core.ts +117 -0
  29. package/skills/wix-headless-fast/references/blog/app/wix/blog/posts.ts +13 -60
  30. package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy-core.ts +47 -0
  31. package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy.ts +10 -28
  32. package/skills/wix-headless-fast/references/blog/app-astro/pages/blog/[...slug].astro +2 -4
  33. package/skills/wix-headless-fast/references/blog/rest/posts.ts +42 -0
  34. package/skills/wix-headless-fast/references/blog/rest/taxonomy.ts +62 -0
  35. package/skills/wix-headless-fast/references/blog/seed/SEED.md +9 -0
  36. package/skills/wix-headless-fast/references/blog/seed/read-site.mjs +22 -0
  37. package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +181 -33
  38. package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServiceBookingView.tsx +2 -1
  39. package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useBookingFlow.ts +26 -132
  40. package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useServices.ts +18 -49
  41. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-core.ts +218 -0
  42. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-flow-store.ts +169 -0
  43. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking.ts +57 -142
  44. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-core.ts +92 -0
  45. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-store.ts +90 -0
  46. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services.ts +22 -61
  47. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/types.ts +3 -1
  48. package/skills/wix-headless-fast/references/bookings/rest/booking.ts +113 -0
  49. package/skills/wix-headless-fast/references/bookings/rest/services.ts +55 -0
  50. package/skills/wix-headless-fast/references/bookings/seed/SEED.md +10 -0
  51. package/skills/wix-headless-fast/references/bookings/seed/read-site.mjs +28 -0
  52. package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +238 -62
  53. package/skills/wix-headless-fast/references/cms/app/hooks/cms/useCollection.ts +19 -52
  54. package/skills/wix-headless-fast/references/cms/app/hooks/cms/useItem.ts +16 -36
  55. package/skills/wix-headless-fast/references/cms/app/wix/cms/collection-store.ts +158 -0
  56. package/skills/wix-headless-fast/references/cms/app/wix/cms/item-store.ts +93 -0
  57. package/skills/wix-headless-fast/references/cms/app/wix/cms/items-core.ts +177 -0
  58. package/skills/wix-headless-fast/references/cms/app/wix/cms/items.ts +13 -41
  59. package/skills/wix-headless-fast/references/cms/rest/items.ts +125 -0
  60. package/skills/wix-headless-fast/references/cms/seed/SEED.md +9 -0
  61. package/skills/wix-headless-fast/references/cms/seed/read-site.mjs +20 -0
  62. package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +241 -83
  63. package/skills/wix-headless-fast/references/events/app/components/events/EventConfirmationView.tsx +1 -1
  64. package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx +7 -5
  65. package/skills/wix-headless-fast/references/events/app/components/events/EventsView.tsx +2 -2
  66. package/skills/wix-headless-fast/references/events/app/hooks/events/useEventRegistration.ts +28 -110
  67. package/skills/wix-headless-fast/references/events/app/hooks/events/useEvents.ts +19 -54
  68. package/skills/wix-headless-fast/references/events/app/wix/events/events-core.ts +130 -0
  69. package/skills/wix-headless-fast/references/events/app/wix/events/events-store.ts +97 -0
  70. package/skills/wix-headless-fast/references/events/app/wix/events/events.ts +13 -94
  71. package/skills/wix-headless-fast/references/events/app/wix/events/registration-core.ts +113 -0
  72. package/skills/wix-headless-fast/references/events/app/wix/events/registration-store.ts +156 -0
  73. package/skills/wix-headless-fast/references/events/app/wix/events/registration.ts +38 -69
  74. package/skills/wix-headless-fast/references/events/rest/events.ts +57 -0
  75. package/skills/wix-headless-fast/references/events/rest/registration.ts +83 -0
  76. package/skills/wix-headless-fast/references/events/seed/SEED.md +9 -0
  77. package/skills/wix-headless-fast/references/events/seed/read-site.mjs +26 -0
  78. package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +216 -26
  79. package/skills/wix-headless-fast/references/forms/app/hooks/forms/useWixForm.ts +35 -267
  80. package/skills/wix-headless-fast/references/forms/app/wix/forms/form-store.ts +332 -0
  81. package/skills/wix-headless-fast/references/forms/app/wix/forms/forms-core.ts +238 -0
  82. package/skills/wix-headless-fast/references/forms/app/wix/forms/forms.ts +28 -243
  83. package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions-core.ts +216 -0
  84. package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions.ts +32 -204
  85. package/skills/wix-headless-fast/references/forms/rest/forms.ts +34 -0
  86. package/skills/wix-headless-fast/references/forms/rest/submissions.ts +56 -0
  87. package/skills/wix-headless-fast/references/forms/seed/SEED.md +8 -0
  88. package/skills/wix-headless-fast/references/forms/seed/read-site.mjs +20 -0
  89. package/skills/wix-headless-fast/references/forms/seed/seed-forms.mjs +15 -8
  90. package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +316 -63
  91. package/skills/wix-headless-fast/references/members/app/components/members/LoginForm.tsx +1 -1
  92. package/skills/wix-headless-fast/references/members/app/wix/members/auth-core.ts +105 -0
  93. package/skills/wix-headless-fast/references/members/app/wix/members/auth.ts +36 -65
  94. package/skills/wix-headless-fast/references/members/app/wix/members/client.ts +1 -1
  95. package/skills/wix-headless-fast/references/members/app/wix/members/members-core.ts +43 -0
  96. package/skills/wix-headless-fast/references/members/app/wix/members/members.ts +11 -32
  97. package/skills/wix-headless-fast/references/members/rest/auth.ts +215 -0
  98. package/skills/wix-headless-fast/references/members/rest/members.ts +26 -0
  99. package/skills/wix-headless-fast/references/members/seed/read-site.mjs +15 -0
  100. package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +240 -59
  101. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionProjectsView.tsx +2 -2
  102. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx +2 -2
  103. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/GalleryMedia.tsx +35 -0
  104. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx +8 -38
  105. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts +23 -50
  106. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollections.ts +16 -34
  107. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useProjectDetail.ts +22 -49
  108. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collection-projects-store.ts +82 -0
  109. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collections-store.ts +58 -0
  110. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio-core.ts +177 -0
  111. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio.ts +31 -118
  112. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/project-detail-store.ts +81 -0
  113. package/skills/wix-headless-fast/references/portfolio/rest/portfolio.ts +75 -0
  114. package/skills/wix-headless-fast/references/portfolio/seed/SEED.md +8 -0
  115. package/skills/wix-headless-fast/references/portfolio/seed/read-site.mjs +20 -0
  116. package/skills/wix-headless-fast/references/portfolio/seed/seed-portfolio.mjs +19 -21
  117. package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +214 -51
  118. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlanDetailView.tsx +9 -20
  119. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx +7 -32
  120. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/SubscribeButton.tsx +41 -0
  121. package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts +12 -33
  122. package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts +17 -34
  123. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plan-purchase-store.ts +54 -0
  124. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-core.ts +106 -0
  125. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-store.ts +64 -0
  126. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans.ts +7 -55
  127. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase-core.ts +43 -0
  128. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase.ts +11 -34
  129. package/skills/wix-headless-fast/references/pricing-plans/rest/plans.ts +31 -0
  130. package/skills/wix-headless-fast/references/pricing-plans/rest/purchase.ts +20 -0
  131. package/skills/wix-headless-fast/references/pricing-plans/seed/SEED.md +8 -0
  132. package/skills/wix-headless-fast/references/pricing-plans/seed/read-site.mjs +21 -0
  133. package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +242 -47
  134. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/MenuView.tsx +2 -2
  135. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartDrawer.tsx +2 -2
  136. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useMenus.ts +14 -35
  137. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useReservation.ts +28 -171
  138. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-core.ts +155 -0
  139. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-store.ts +89 -0
  140. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu.ts +23 -117
  141. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering-core.ts +124 -0
  142. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering.ts +45 -114
  143. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservation-store.ts +193 -0
  144. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations-core.ts +94 -0
  145. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations.ts +15 -87
  146. package/skills/wix-headless-fast/references/restaurants/rest/menu.ts +60 -0
  147. package/skills/wix-headless-fast/references/restaurants/rest/ordering.ts +169 -0
  148. package/skills/wix-headless-fast/references/restaurants/rest/reservations.ts +68 -0
  149. package/skills/wix-headless-fast/references/restaurants/seed/SEED.md +8 -0
  150. package/skills/wix-headless-fast/references/restaurants/seed/read-site.mjs +25 -0
  151. package/skills/wix-headless-fast/references/restaurants/seed/seed-restaurants.mjs +25 -5
  152. package/skills/wix-headless-fast/references/shared/SUPPLIED-CONTENT.md +23 -0
  153. package/skills/wix-headless-fast/references/shared/app/wix/media.ts +45 -4
  154. package/skills/wix-headless-fast/references/shared/app/wix/sdk.ts +13 -0
  155. package/skills/wix-headless-fast/references/shared/rest/client.ts +155 -0
  156. package/skills/wix-headless-fast/references/shared/rest/config.ts +3 -0
  157. package/skills/wix-headless-fast/references/shared/rest/media.ts +65 -0
  158. package/skills/wix-headless-fast/references/shared/seed/read-site.mjs +99 -0
  159. package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +186 -63
  160. package/skills/wix-headless-fast/references/storefront/app/components/storefront/OptionPicker.tsx +95 -0
  161. package/skills/wix-headless-fast/references/storefront/app/components/storefront/QuickAdd.tsx +29 -58
  162. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +29 -163
  163. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +26 -300
  164. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-core.ts +121 -0
  165. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +49 -162
  166. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog-core.ts +314 -0
  167. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +47 -370
  168. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/product-detail-store.ts +147 -0
  169. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/shop-store.ts +289 -0
  170. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +2 -0
  171. package/skills/wix-headless-fast/references/storefront/rest/cart.ts +132 -0
  172. package/skills/wix-headless-fast/references/storefront/rest/catalog.ts +123 -0
  173. package/skills/wix-headless-fast/references/storefront/seed/SEED.md +31 -9
  174. package/skills/wix-headless-fast/references/storefront/seed/read-site.mjs +38 -0
  175. package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +57 -50
  176. package/skills/wix-manage/references/ecommerce/ecom-shipping.md +1 -1
  177. package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-create-coupon.md +10 -19
  178. package/skills/wix-manage/references/events/create-wix-event.md +5 -1
  179. package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +0 -40
  180. package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +3 -0
  181. package/skills/wix-vibe-headless/references/storefront/seed/seed-store.cjs +6 -0
  182. package/skills/wix-headless-fast/install/fast-path.mjs +0 -186
  183. package/skills/wix-headless-fast/install/pin-agents-md.mjs +0 -57
@@ -15,6 +15,7 @@
15
15
  // docs: https://dev.wix.com/docs/sdk/core-modules/sdk/oauth-strategy.md
16
16
  // docs: https://dev.wix.com/docs/go-headless/wix-managed-headless/authentication/about-the-astro-integration.md
17
17
  import { createClient, OAuthStrategy, EMPTY_TOKENS } from "@wix/sdk";
18
+ import { fetchWithAuth as ambientFetch } from "@wix/sdk/context";
18
19
  import type { IOAuthStrategy, TokenStorage, Tokens } from "@wix/sdk";
19
20
  import { WIX_CLIENT_ID } from "./config";
20
21
 
@@ -66,6 +67,18 @@ export function wixModule<T>(module: T): T {
66
67
  : module;
67
68
  }
68
69
 
70
+ /**
71
+ * An authenticated fetch of a Wix REST endpoint under the SAME auth as the SDK modules (ambient
72
+ * on managed Astro, the shared client elsewhere) — for the few APIs whose generated SDK module is
73
+ * too heavy to bundle (the Forms schema module alone is 15 MB: server-side it pushes a Wix
74
+ * deploy past its size limit, client-side every visitor downloads it). Same visitor identity,
75
+ * same token, no second mint. `path` is the wixapis.com path; returns the raw Response.
76
+ */
77
+ export function wixFetch(path: string, init?: RequestInit): Promise<Response> {
78
+ const url = path.startsWith("http") ? path : `https://www.wixapis.com${path}`;
79
+ return client ? client.fetchWithAuth(url, init) : ambientFetch(url, init);
80
+ }
81
+
69
82
  /**
70
83
  * The shared data client's manual-mode auth strategy, or null under ambient auth. The
71
84
  * Members vertical deliberately owns a separate explicit client for its custom in-app login;
@@ -0,0 +1,155 @@
1
+ // The one auth seam for every Wix REST call — the twin of app/wix/sdk.ts, over fetch. No
2
+ // dependencies. deploy.mjs --stack static copies it flat into js/wix/ next to the core files and
3
+ // strips it to JS (comments kept); a port to another language reads it as the specification.
4
+ // Imports carry the .js suffix so the stripped output runs as browser ESM unchanged.
5
+ //
6
+ // WIX_CLIENT_ID is the public OAuth client id (`appId` in wix.config.json). It is NOT a secret — it
7
+ // only mints anonymous VISITOR tokens — so it is fine in page source. deploy.mjs writes ./config.
8
+ //
9
+ // THE TOKEN IS THE CART. A visitor token is the shopper's identity: their cart lives on it. Mint
10
+ // once per shopper, persist it, refresh it — never mint per request (a fresh token is a new visitor
11
+ // with an empty cart). Where it persists depends on where this code runs:
12
+ // browser — localStorage, keyed by client id so two sites on one origin don't share a token
13
+ // (what this file does).
14
+ // server (a Python/PHP/Go port) — the SHOPPER'S SESSION, one token set per shopper. Never one
15
+ // process-wide token for everyone: that is one shared cart for all visitors.
16
+ // This is the only part of a port that isn't line-for-line.
17
+ // docs: https://dev.wix.com/docs/go-headless/coding/authentication/visitors.md
18
+ import { WIX_CLIENT_ID } from "./config.js";
19
+
20
+ export const WIX_API_BASE = "https://www.wixapis.com";
21
+ const TOKEN_URL = `${WIX_API_BASE}/oauth2/token`;
22
+ const STORAGE_KEY = `wix-visitor-${WIX_CLIENT_ID}`;
23
+
24
+ export interface Tokens {
25
+ accessToken: string;
26
+ refreshToken: string;
27
+ /** Epoch ms. */
28
+ expiresAt: number;
29
+ /** Whose tokens: absent or "visitor" for an anonymous visitor, "member" after a login (a vertical's rest/auth.ts). */
30
+ role?: "visitor" | "member";
31
+ }
32
+
33
+ /** Swap this for a server-session-backed store in a port; the browser default is localStorage. */
34
+ export interface TokenStore {
35
+ load(): Tokens | null;
36
+ save(tokens: Tokens): void;
37
+ }
38
+
39
+ let memory: Tokens | null = null;
40
+ export const browserTokenStore: TokenStore = {
41
+ load() {
42
+ if (memory) return memory;
43
+ if (typeof localStorage === "undefined") return null;
44
+ try {
45
+ const raw = localStorage.getItem(STORAGE_KEY);
46
+ if (raw) memory = JSON.parse(raw) as Tokens;
47
+ } catch {
48
+ /* disabled or corrupt storage — mint a fresh visitor */
49
+ }
50
+ return memory;
51
+ },
52
+ save(tokens) {
53
+ memory = tokens;
54
+ if (typeof localStorage === "undefined") return;
55
+ try {
56
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(tokens));
57
+ } catch {
58
+ /* storage full or disabled — the session just won't survive a reload */
59
+ }
60
+ },
61
+ };
62
+ let store: TokenStore = browserTokenStore;
63
+ /** A port calls this once with its session-backed store. */
64
+ export function useTokenStore(s: TokenStore): void {
65
+ store = s;
66
+ }
67
+
68
+ // POST /oauth2/token — grantType "anonymous" mints a visitor; "refresh_token" renews and KEEPS the
69
+ // identity (a member refresh token yields member tokens); "authorization_code" + codeVerifier turns
70
+ // a login's authorization code into MEMBER tokens (a vertical's rest/auth.ts). `role` records which.
71
+ export async function mintTokens(body: Record<string, string>, role: Tokens["role"] = "visitor"): Promise<Tokens> {
72
+ const res = await fetch(TOKEN_URL, {
73
+ method: "POST",
74
+ headers: { "Content-Type": "application/json" },
75
+ body: JSON.stringify({ clientId: WIX_CLIENT_ID, ...body }),
76
+ });
77
+ if (!res.ok) throw new Error(`Wix auth failed (${res.status}).`);
78
+ const data = await res.json();
79
+ return { accessToken: data.access_token, refreshToken: data.refresh_token, expiresAt: Date.now() + data.expires_in * 1000, role };
80
+ }
81
+
82
+ /** The persisted token set as it is (possibly expired), or null before the first mint. */
83
+ export function loadTokens(): Tokens | null {
84
+ return store.load();
85
+ }
86
+
87
+ /** Adopt a token set — a member's after login. Every call from here on runs as that identity. */
88
+ export function setTokens(tokens: Tokens): void {
89
+ store.save(tokens);
90
+ }
91
+
92
+ /** Drop the current identity (logout): the next call mints a fresh anonymous visitor. */
93
+ export function clearTokens(): void {
94
+ store.save({ accessToken: "", refreshToken: "", expiresAt: 0 });
95
+ }
96
+
97
+ /** A valid access token: cached, refreshed when expired, minted on first use. */
98
+ export async function accessToken(): Promise<string> {
99
+ const t = store.load();
100
+ if (t && t.expiresAt - 60_000 > Date.now()) return t.accessToken;
101
+ let fresh: Tokens | undefined;
102
+ if (t?.refreshToken) {
103
+ try {
104
+ fresh = await mintTokens({ grantType: "refresh_token", refreshToken: t.refreshToken }, t.role ?? "visitor");
105
+ } catch {
106
+ /* fall through to a new visitor */
107
+ }
108
+ }
109
+ fresh ??= await mintTokens({ grantType: "anonymous" });
110
+ store.save(fresh);
111
+ return fresh.accessToken;
112
+ }
113
+
114
+ export class WixApiError extends Error {
115
+ /** The response's `details` as Wix sent it (applicationError, validationError) — for callers that map codes. */
116
+ public details?: Record<string, any>;
117
+ constructor(message: string, public status: number, public code?: string, details?: Record<string, any>) {
118
+ super(message);
119
+ this.details = details;
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Every Wix call goes through here. `path` is relative to WIX_API_BASE; `query` becomes the query
125
+ * string (an array repeats the key: fields=A&fields=B). Throws WixApiError on a non-2xx, including
126
+ * 404 — callers that treat 404 as "not found" catch it. The header carries the RAW access token,
127
+ * no "Bearer " prefix.
128
+ */
129
+ export async function wixRequest<T = any>(
130
+ path: string,
131
+ { method = "POST", body, query }: { method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; body?: unknown; query?: Record<string, string | readonly string[]> } = {},
132
+ ): Promise<T> {
133
+ const url = new URL(path, WIX_API_BASE);
134
+ for (const [k, v] of Object.entries(query ?? {})) {
135
+ for (const item of Array.isArray(v) ? v : [v as string]) url.searchParams.append(k, String(item));
136
+ }
137
+ const res = await fetch(url, {
138
+ method,
139
+ headers: { Authorization: await accessToken(), "Content-Type": "application/json" },
140
+ body: body === undefined ? undefined : JSON.stringify(body),
141
+ });
142
+ if (!res.ok) {
143
+ let message = "", code: string | undefined, details: Record<string, any> | undefined;
144
+ try {
145
+ const j = await res.json();
146
+ message = j?.message ?? "";
147
+ code = j?.details?.applicationError?.code;
148
+ details = j?.details;
149
+ } catch {
150
+ /* no JSON body */
151
+ }
152
+ throw new WixApiError(message || `${method} ${url.pathname} failed (${res.status}).`, res.status, code, details);
153
+ }
154
+ return (res.status === 204 ? null : await res.json()) as T;
155
+ }
@@ -0,0 +1,3 @@
1
+ // Written by install/deploy.mjs --stack static from wix.config.json (or --client-id).
2
+ // The public OAuth client id — not a secret: it only mints anonymous visitor tokens.
3
+ export const WIX_CLIENT_ID: string = "";
@@ -0,0 +1,65 @@
1
+ // Wix media → browser-loadable URLs — the twin of app/wix/media.ts, without the SDK.
2
+ //
3
+ // A media value arrives as either an absolute https://static.wixstatic.com/... URL or a raw
4
+ // `wix:image://v1/<file>/<name>#originWidth=W&originHeight=H` identifier a browser cannot load
5
+ // (ERR_UNKNOWN_URL_SCHEME). The raw form is scaled through this ONE URL shape; any other hand-built
6
+ // form 403s: https://static.wixstatic.com/media/<file>/v1/fill/w_<W>,h_<H>,al_c,q_90/<file>
7
+ const STATIC = "https://static.wixstatic.com/media";
8
+ const FILL = /\/v1\/fill\/w_\d+,h_\d+/;
9
+
10
+ export type MediaLike = string | null | undefined | { image?: string | null; url?: string | null };
11
+
12
+ function rawOf(value: unknown): string {
13
+ if (!value) return "";
14
+ if (typeof value === "string") return value;
15
+ const o = value as { image?: string | null; url?: string | null };
16
+ return o.image ?? o.url ?? "";
17
+ }
18
+
19
+ /** Resolve any Wix media value to a URL sized w×h ("" when absent). */
20
+ export function imgSrc(value: unknown, width = 600, height = 600): string {
21
+ const v = rawOf(value);
22
+ if (!v) return "";
23
+ if (v.startsWith("wix:image://")) {
24
+ const file = v.slice("wix:image://v1/".length).split("/")[0].split("#")[0];
25
+ return `${STATIC}/${file}/v1/fill/w_${width},h_${height},al_c,q_90/${file}`;
26
+ }
27
+ // An absolute Wix media URL (bare, or already scaled to some other size): re-issue it through the
28
+ // scaler at the requested size, so every image path lands on one shape.
29
+ const m = v.match(/^https:\/\/static\.wixstatic\.com\/media\/([^/?#]+)/);
30
+ if (m) return `${STATIC}/${m[1]}/v1/fill/w_${width},h_${height},al_c,q_90/${m[1]}`;
31
+ return v;
32
+ }
33
+
34
+ /** srcset candidates at several widths; `ratio` is height/width. "" only when there is no image. */
35
+ export function imgSrcSet(value: unknown, widths: number[] = [320, 480, 640, 960], ratio = 1): string {
36
+ const v = rawOf(value);
37
+ if (!v) return "";
38
+ if (v.startsWith("wix:image://")) return widths.map((w) => `${imgSrc(value, w, Math.round(w * ratio))} ${w}w`).join(", ");
39
+ if (v.includes("static.wixstatic.com/") && FILL.test(v)) {
40
+ return widths.map((w) => `${v.replace(FILL, `/v1/fill/w_${w},h_${Math.round(w * ratio)}`)} ${w}w`).join(", ");
41
+ }
42
+ return v;
43
+ }
44
+
45
+ /**
46
+ * Everything an <img> needs for one Wix image, so a tile can never ship `srcset` without `src`.
47
+ * Attribute names as HTML spells them — spread into a template or apply with setAttribute:
48
+ * `<img ${attrs(imgAttrs(p.imageUrl, "(min-width: 1024px) 25vw, 50vw"))} alt="…">`
49
+ * `sizes` is the width the image renders at; `ratio` is height/width. {} when there is no image.
50
+ */
51
+ export function imgAttrs(value: unknown, sizes: string, ratio = 1): Record<string, string> {
52
+ const src = imgSrc(value, 640, Math.round(640 * ratio));
53
+ if (!src) return {};
54
+ return { src, srcset: imgSrcSet(value, undefined, ratio), sizes, loading: "lazy", decoding: "async" };
55
+ }
56
+
57
+ /** The media identity before scaling — de-duplicate galleries on this, never on a resolved URL. */
58
+ export function mediaKey(value: unknown): string {
59
+ const v = rawOf(value);
60
+ if (!v) return "";
61
+ // The file id, whichever form the value takes — a raw id and a resolved URL of one photo share it.
62
+ if (v.startsWith("wix:image://")) return v.slice("wix:image://v1/".length).split("/")[0].split("#")[0];
63
+ const m = v.match(/^https:\/\/static\.wixstatic\.com\/media\/([^/?#]+)/);
64
+ return m ? m[1] : v;
65
+ }
@@ -0,0 +1,99 @@
1
+ // The shared runner behind every vertical's `seed/read-site.mjs` — the build-time read of what a
2
+ // site holds, so the agent sizes the content without composing a single Wix request.
3
+ //
4
+ // node <SKILL_ROOT>/references/<vertical>/seed/read-site.mjs [--site <siteId>] [--limit <n>]
5
+ //
6
+ // The site is `--site`, else the `siteId` in ./wix.config.json. The token is minted by the Wix CLI
7
+ // inside this process and never written anywhere. Output is ONE JSON object on stdout:
8
+ // { vertical, siteId, installed, ...what the vertical reads..., calls: [{ method, path, status, docs }] }
9
+ // `calls` lists every request made, with the method's documentation URL — open one of those (the
10
+ // URL plus `.md` is the full page) when the brief needs more than the summary shows. A failed call
11
+ // is reported in `calls` with its status and message; the reader continues and marks the field null.
12
+ import { execFileSync } from "node:child_process";
13
+ import { readFileSync, existsSync } from "node:fs";
14
+
15
+ const API = "https://www.wixapis.com";
16
+ const DOCS_INSTALLED_APPS = "https://dev.wix.com/docs/api-reference/business-management/app-installation/app-installation/get-installed-apps";
17
+
18
+ function flag(name) {
19
+ const i = process.argv.indexOf(`--${name}`);
20
+ return i !== -1 && process.argv[i + 1] && !process.argv[i + 1].startsWith("--") ? process.argv[i + 1] : null;
21
+ }
22
+
23
+ export function siteIdFromArgsOrConfig() {
24
+ const fromFlag = flag("site");
25
+ if (fromFlag) return fromFlag;
26
+ if (existsSync("wix.config.json")) {
27
+ const c = JSON.parse(readFileSync("wix.config.json", "utf8"));
28
+ return c.siteId ?? c.projectId ?? null;
29
+ }
30
+ return null;
31
+ }
32
+
33
+ export function makeApi(siteId) {
34
+ const token = execFileSync("npx", ["-y", "@wix/cli@latest", "token", "--site", siteId], { encoding: "utf8" }).trim();
35
+ if (!token) throw new Error("the Wix CLI returned no token — run `npx @wix/cli@latest login` first");
36
+ const calls = [];
37
+ async function call({ method = "POST", path, body, docs }) {
38
+ const entry = { method, path: path.split("?")[0], docs };
39
+ calls.push(entry);
40
+ const res = await fetch(API + path, {
41
+ method,
42
+ headers: { Authorization: token, "wix-site-id": siteId, "Content-Type": "application/json" },
43
+ body: body === undefined ? undefined : JSON.stringify(body),
44
+ });
45
+ const text = await res.text();
46
+ entry.status = res.status;
47
+ if (!res.ok) {
48
+ entry.error = text.slice(0, 200) || "(empty body)";
49
+ const e = new Error(`${method} ${entry.path} → ${res.status}: ${entry.error}`);
50
+ e.status = res.status;
51
+ throw e;
52
+ }
53
+ try { return text ? JSON.parse(text) : {}; } catch { return {}; }
54
+ }
55
+ // Same call, but a failure becomes null and stays in `calls` — for reads whose absence is data.
56
+ async function tryCall(opts) {
57
+ try { return await call(opts); } catch { return null; }
58
+ }
59
+ async function installed(appId) {
60
+ const r = await tryCall({ method: "GET", path: "/apps-installer-service/v1/app-instances", docs: DOCS_INSTALLED_APPS });
61
+ if (!r) return null;
62
+ return (r.appInstances ?? []).some((a) => a.appDefId === appId || a.appId === appId);
63
+ }
64
+ return { call, tryCall, installed, calls };
65
+ }
66
+
67
+ export const limit = () => Number(flag("limit") ?? 12);
68
+
69
+ // read(api, { limit }) returns the vertical's summary; the runner adds the envelope and prints.
70
+ export async function runReader({ vertical, appId, read }) {
71
+ const siteId = siteIdFromArgsOrConfig();
72
+ if (!siteId) {
73
+ console.log(JSON.stringify({ error: "no site: pass --site <siteId> or run in a folder with wix.config.json" }));
74
+ process.exit(1);
75
+ }
76
+ const api = makeApi(siteId);
77
+ const out = { vertical, siteId, scope: null };
78
+ out.installed = appId ? await api.installed(appId) : undefined;
79
+ let data = {};
80
+ if (out.installed === false) {
81
+ out.scope = `${vertical}: the app is not installed on this site — nothing to read; the vertical's seed installs it`;
82
+ } else {
83
+ try { data = await read(api, { limit: limit() }); }
84
+ catch (e) { out.error = String(e.message).slice(0, 300); }
85
+ }
86
+ // What this output is and is not: a SIZING. Every list is a sample page; the counts are the site.
87
+ const partial = Object.entries(data)
88
+ .filter(([k, v]) => Array.isArray(v) && typeof data[`${k.replace(/s$/, "")}Count`] === "number" && data[`${k.replace(/s$/, "")}Count`] > v.length)
89
+ .map(([k, v]) => `${k}: ${v.length} of ${data[`${k.replace(/s$/, "")}Count`]} shown`);
90
+ if (!out.scope) {
91
+ out.scope = "A sizing, not the content. Lists are one page (--limit, max 100); counts are the whole site. " +
92
+ (partial.length ? `Partial here: ${partial.join("; ")}. ` : "") +
93
+ "The pages read everything live through the deployed data layer. To read more or filter, page the same call: its method and documentation URL are in `calls`.";
94
+ }
95
+ Object.assign(out, data);
96
+ out.calls = api.calls;
97
+ out.docs = [...new Set(api.calls.map((c) => c.docs).filter(Boolean))];
98
+ console.log(JSON.stringify(out, null, 2));
99
+ }