@wix/agent-skills 1.18.0 → 1.18.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-app/SKILL.md +4 -4
  3. package/skills/wix-app/references/APP_VALIDATION.md +7 -33
  4. package/skills/wix-app/references/DASHBOARD_PAGE.md +18 -14
  5. package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +75 -92
  6. package/skills/wix-app/references/WIX_PATTERNS_DOCS.md +23 -26
  7. package/skills/wix-app/references/dashboard-page/COLLECTION_TOOLKIT.md +7 -6
  8. package/skills/wix-app/references/dashboard-page/ENTITY_PAGE_TOOLKIT.md +71 -4
  9. package/skills/wix-app/references/dashboard-page/PATTERNS_BUNDLE_READING.md +67 -7
  10. package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +1 -1
  11. package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +9 -6
  12. package/skills/wix-app/references/editor-react-component/COMPONENT-CONTRACT.md +59 -89
  13. package/skills/wix-app/references/editor-react-component/COMPONENT-PREVIEW.md +23 -4
  14. package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +29 -14
  15. package/skills/wix-base44-connector/SKILL.md +168 -48
  16. package/skills/wix-base44-connector/scripts/{utils.js → utils.cjs} +69 -48
  17. package/skills/wix-docs/SKILL.md +109 -111
  18. package/skills/wix-docs/references/API_SPEC_SEARCH.md +35 -23
  19. package/skills/wix-docs/references/CALLING.md +91 -0
  20. package/skills/wix-docs/references/EXTRACTING.md +5 -3
  21. package/skills/wix-headless/references/DOC_DISCOVERY.md +9 -3
  22. package/skills/wix-headless/references/inline-recipes/setup-forms.md +57 -10
  23. package/skills/wix-manage/SKILL.md +106 -92
  24. package/skills/wix-manage/references/dashboard-navigation/routes.json +12 -52
  25. package/skills/wix-manage/references/ecommerce/shipping/ecom-shipping-api.md +0 -2
  26. package/skills/wix-manage/references/forms/create-form.md +272 -184
  27. package/skills/wix-manage/references/forms/forms-dashboard-navigation.md +13 -10
  28. package/skills/wix-manage/references/forms/update-form.md +122 -0
  29. package/skills/wix-manage/references/media/generate-image-with-ai.md +86 -0
  30. package/skills/wix-manage/references/seo/manage-content-plan.md +247 -0
  31. package/skills/wix-manage/references/seo/manage-seo-tags.md +59 -6
  32. package/skills/wix-manage/references/stores/change-store-currency.md +10 -0
  33. package/skills/wix-manage/references/stores/create-product-catalog-v3.md +16 -1
  34. package/skills/wix-manage/references/stores/update-product-with-options.md +9 -0
  35. package/skills/wix-vibe-headless/SKILL.md +17 -18
  36. package/skills/wix-vibe-headless/install/pin-agents-md.cjs +1 -1
  37. package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed-with-install.md +118 -0
  38. package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +55 -62
  39. package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +31 -29
  40. package/skills/wix-vibe-headless/platforms/base44.md +12 -5
  41. package/skills/wix-vibe-headless/platforms/generic.md +13 -12
  42. package/skills/wix-vibe-headless/references/blog/INSTRUCTIONS.md +4 -13
  43. package/skills/wix-vibe-headless/references/blog/seed/SEED.md +4 -8
  44. package/skills/wix-vibe-headless/references/blog/seed/{seed-blog.js → seed-blog.cjs} +2 -3
  45. package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +5 -14
  46. package/skills/wix-vibe-headless/references/bookings/seed/SEED.md +5 -9
  47. package/skills/wix-vibe-headless/references/bookings/seed/{seed-bookings.js → seed-bookings.cjs} +2 -2
  48. package/skills/wix-vibe-headless/references/bookings/seed/{seed-rentals.js → seed-rentals.cjs} +3 -3
  49. package/skills/wix-vibe-headless/references/cms/INSTRUCTIONS.md +1 -1
  50. package/skills/wix-vibe-headless/references/cms/seed/SEED.md +2 -2
  51. package/skills/wix-vibe-headless/references/events/INSTRUCTIONS.md +4 -13
  52. package/skills/wix-vibe-headless/references/events/seed/SEED.md +3 -7
  53. package/skills/wix-vibe-headless/references/events/seed/{seed-events.js → seed-events.cjs} +2 -2
  54. package/skills/wix-vibe-headless/references/forms/INSTRUCTIONS.md +24 -7
  55. package/skills/wix-vibe-headless/references/forms/seed/SEED.md +91 -17
  56. package/skills/wix-vibe-headless/references/members/INSTRUCTIONS.md +4 -13
  57. package/skills/wix-vibe-headless/references/members/seed/SEED.md +2 -3
  58. package/skills/wix-vibe-headless/references/portfolio/INSTRUCTIONS.md +4 -13
  59. package/skills/wix-vibe-headless/references/portfolio/app/components/ProjectMedia.jsx +1 -1
  60. package/skills/wix-vibe-headless/references/portfolio/seed/SEED.md +3 -7
  61. package/skills/wix-vibe-headless/references/portfolio/seed/{seed-portfolio.js → seed-portfolio.cjs} +2 -2
  62. package/skills/wix-vibe-headless/references/pricing-plans/INSTRUCTIONS.md +5 -14
  63. package/skills/wix-vibe-headless/references/pricing-plans/app/components/OrderCard.jsx +1 -1
  64. package/skills/wix-vibe-headless/references/pricing-plans/seed/SEED.md +3 -7
  65. package/skills/wix-vibe-headless/references/pricing-plans/seed/{seed-pricing-plans.js → seed-pricing-plans.cjs} +2 -2
  66. package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +5 -15
  67. package/skills/wix-vibe-headless/references/restaurants/seed/SEED.md +3 -7
  68. package/skills/wix-vibe-headless/references/restaurants/seed/{seed-restaurants.js → seed-restaurants.cjs} +2 -2
  69. package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +359 -327
  70. package/skills/wix-vibe-headless/references/storefront/app/context/CartContext.jsx +1 -0
  71. package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductDetail.js +2 -2
  72. package/skills/wix-vibe-headless/references/storefront/app/hooks/useShop.js +63 -78
  73. package/skills/wix-vibe-headless/references/storefront/app/rest/wix-store-catalog.js +59 -43
  74. package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +46 -10
  75. package/skills/wix-vibe-headless/references/storefront/seed/{seed-store.js → seed-store.cjs} +163 -25
  76. package/skills/wix-vibe-headless/references/storefront/app/pages/Shop.jsx +0 -75
@@ -1,11 +1,13 @@
1
1
  # CSS Guidelines
2
2
 
3
3
  Use these rules for the scaffolded `<component-name>.module.css` file and any
4
- colocated CSS Modules owned by internal sub-components. Keep existing visual
5
- decisions outside the request unchanged. These rules keep the component
4
+ colocated CSS Modules owned by internal sub-components. These rules keep the component
6
5
  editable by zero config, responsive to its Wix-owned container, and safe in
7
6
  both LTR and RTL layouts.
8
7
 
8
+ **Visual intent.** When the request specifies how the component should look —
9
+ mood, palette, layout style, or a reference — follow that direction. Default to a modern, refined look — intentional layout, pleasing proportions, polished and deliberate styling.
10
+
9
11
  ## Contents
10
12
 
11
13
  - [Classes and Named Parts](#classes-and-named-parts)
@@ -210,20 +212,30 @@ and do not expose a visual prop solely for that purpose.
210
212
 
211
213
  ## Default Visual Quality
212
214
 
213
- When creating a component and the request has no visual direction, use a
214
- restrained accessible default. When editing an existing component, preserve its
215
- visual language unless the request changes it.
215
+ Create a clear hierarchy with at least two techniques that fit the component:
216
+ a defined surface, border or soft shadow, type scale or weight, spacing, an
217
+ accent, or visible interaction states. Use these neutral defaults unless the
218
+ request or existing design provides better values:
219
+
220
+ - 16px or larger body text, 14px or larger labels, and 600–700 weight for titles
221
+ - 8–16px spacing inside controls and 16–32px spacing inside containers
222
+ - 8–12px corner radii for cards and controls; `50%` for circular media
223
+ - a subtle border or soft shadow when a card, panel, or control needs separation
224
+ - touch targets at least 44 by 44px and visible keyboard focus states
225
+
226
+ ### Foreground and Background Ownership
227
+
228
+ If the component is a self-contained block (card, panel, banner, accordion item), give the root a background. If it is an inline control meant to blend in (link, ghost button, icon button), control may leave the root transparent instead.
229
+
230
+ - On a transparent root, don't hardcode text or icon colors; inherit them from the parent.
231
+ - Set the background and main text/icon color together on the same element. Any secondary text (captions, hints, icons) must still be readable against that same background.
232
+ - If an interactive state changes the background, set the foreground color in that state too. Don't assume a color inherited from a parent or from a previous state will still look right on the new background.
233
+ - In every default and interactive state, meet WCAG AA contrast: 4.5:1 for body text and 3:1 for large text and UI controls.
216
234
 
217
- - body text at least 16px and labels at least 14px, with an explicit `font-family`
218
- - WCAG AA contrast (4.5:1 body text, 3:1 large text and UI controls)
219
- - touch targets at least 44 by 44px
220
- - clear hierarchy and visible focus states
221
- - generous but consistent spacing
222
- - transparent root backgrounds unless the design requires a surface
235
+ A neutral owned surface may use a light background with dark primary and
236
+ secondary foregrounds, plus one accessible accent. The exact colors are not a
237
+ contract; the contrast relationship is.
223
238
 
224
- Avoid prescribing a brand palette in a generic component. Only when the prompt
225
- explicitly requests branded or themed output, use its named brand tokens rather
226
- than hardcoded generic color choices.
227
239
 
228
240
  ## Checklist
229
241
 
@@ -239,3 +251,6 @@ than hardcoded generic color choices.
239
251
  - [ ] Every part that sets `font-size` also sets `font-family`, on the part that
240
252
  should own the typography control.
241
253
  - [ ] Interactive elements expose pointer events and accessible design states.
254
+ - [ ] Visual containers have deliberate hierarchy rather than bare scaffolding.
255
+ - [ ] Every explicit foreground has a known contrasting background; transparent
256
+ components inherit foregrounds from their host.
@@ -18,36 +18,54 @@ does not inherit an earlier turn's context, so re-read it before building on or
18
18
  **A management or admin task starts at the recipes**: call `wx.mgmtRecipes` (Learn Wix) and follow
19
19
  the one that fits. A recipe states outright what an API does not support, which fields a bulk call
20
20
  actually writes, and the order two calls have to go in — from the schemas alone those get re-derived
21
- several errors at a time. A REST search ranks the matching recipes too (`recipes` in its return), so
21
+ several errors at a time. `wx.search` ranks the matching recipes too (recipe entries in `hits`), so
22
22
  they also surface from a search that began at the methods.
23
23
 
24
+ In this skill:
25
+
26
+ - **What are you building?** — route each feature to its identity: visitor token, admin token
27
+ in a backend function, or admin ad hoc in exec_tool
28
+ - **The helpers** — the `wx.*` loader every exec opens with
29
+ - **Gather context** — `wx.context`, the report of what the site actually has
30
+ - **Learn Wix** — find the APIs, learn their contracts
31
+ - **Management recipes** — first stop for an admin task (`wx.mgmtRecipes`)
32
+ - **Search and browse** — `wx.search` across methods, recipes and articles
33
+ - **Read a doc page** — `wx.page` for a located doc URL
34
+ - **The spec index** — `wx.spec`, a located method's exact schema
35
+ - **Write the code**
36
+ - **Admin ops while building** — you, in exec_tool
37
+ - **Backend functions** — the app, as the owner
38
+ - **Visitor authentication and Wix-hosted flows** — the visitor client, redirect sessions,
39
+ the headless OAuth app — and bootstrapping a whole vertical with `wix-vibe-headless`
40
+
24
41
  ## What are you building?
25
42
 
26
- The app's audience picks the token, and the token picks the architecture: the **visitor token is
27
- public** — anyone can mint it from the site's `clientId` — and the **admin token is a secret**,
28
- the connector's, server-side only.
43
+ Choose the token by who the code acts for. A headless app can serve visitors, provide admin
44
+ management tools, or do both.
45
+
46
+ **A site for visitors** — use a visitor token for public reads and actions on behalf of the
47
+ visitor, never the admin connector token. Call Wix directly from the browser through one shared
48
+ visitor client (Write the code, below). Redirect sessions for Wix-hosted flows also require a
49
+ visitor token minted for the headless OAuth app; see Visitor authentication and Wix-hosted flows below.
50
+ Anyone can mint an anonymous visitor token from the
51
+ OAuth app's public `clientId`; no visitor login is required. APIs for the "current visitor"
52
+ use that token to identify whose data and state to access. This applies both to a standalone
53
+ headless frontend and to a frontend extending an existing Wix site.
54
+
55
+ **An admin tool for the owner** — use the admin connector token in backend functions implementing
56
+ admin logic; keep it secret and server-side. The frontend calls those functions, not Wix with
57
+ visitor tokens. A custom headless management site extending the Wix back office follows this
58
+ flow too. Ad hoc management calls in `exec_tool` also use the admin token. Backend functions
59
+ also handle work requiring the owner's permissions, such as webhooks, scheduled jobs, and
60
+ explicitly authorized elevated operations. For an app with both visitor and admin features,
61
+ keep each feature on its corresponding flow.
29
62
 
30
63
  ```
31
- browser ──(visitor token)─► wixapis.com the visitor's own reads & actions
32
- base44/functions/… ──(admin token)───► wixapis.com work that needs the owner's identity
33
- exec_tool ──(admin token)───► wixapis.com you: ad hoc probing/managing while building
64
+ visitor pages ──(visitor token)────────────────────────► wixapis.com
65
+ admin pages ──► base44/functions/… ──(admin token)────► wixapis.com
66
+ exec_tool ──(admin token, ad hoc management)────────► wixapis.com
34
67
  ```
35
68
 
36
- **A site for visitors** — store, blog, booking, ecom, CMS, CRM, and the rest of the business solutions.
37
- Your app is the site's frontend — whether the site is headless (no pages of its own) or your
38
- frontend extends an existing site. **The complete visitor experience —
39
- every page, every read, every action a visitor takes — is browser calls on the visitor token;
40
- none of it needs a backend function.** Public reads
41
- included (the visitor token queries public content directly), and per-visitor state is scoped to
42
- the CALLER — an API that acts on "the current visitor's" data resolves the visitor from the
43
- token, so only the visitor token reaches that visitor's own state. One shared visitor client
44
- carries it all (Write the code, below). `base44/functions/…` appear only where work
45
- needs the owner's identity — elevated-permission ops a visitor triggers, webhooks, scheduled
46
- jobs — and for the app's non-Wix backend.
47
-
48
- **An admin tool for the owner** — dashboard, back office. Admin pages and agent act as the
49
- owner, using the secret admin token: `pages → base44/functions/… ──(admin token)──► wixapis.com`.
50
-
51
69
  ## The helpers
52
70
 
53
71
  Research and probing run in exec_tool, and one loader opens every exec (execs share no state —
@@ -55,26 +73,29 @@ reload each round; the module lives on disk next to this file, network only as f
55
73
  fallback):
56
74
 
57
75
  ```js
58
- const fs = require("fs"), P = ".agents/skills/wix-base44-connector/utils.js";
76
+ const fs = require("fs"), P = ".agents/skills/wix-base44-connector/utils.cjs";
59
77
  if (!fs.existsSync(P)) { fs.mkdirSync(".agents/skills/wix-base44-connector", { recursive: true });
60
- fs.writeFileSync(P, await (await fetch("https://www.wix.com/skills/wix-base44-connector/scripts/utils.js")).text()); }
61
- const wx = (() => { const m = { exports: {} };
62
- new Function("module", "exports", "require", fs.readFileSync(P, "utf8"))(m, m.exports, require); return m.exports; })();
78
+ fs.writeFileSync(P, await (await fetch("https://www.wix.com/skills/wix-base44-connector/scripts/utils.cjs")).text()); }
79
+ const wx = require(require("path").resolve(P));
63
80
  ```
64
81
 
65
82
  `wx` exports these helpers:
66
83
 
67
84
  - `wx.post/get/patch/put/del(url, [body], token?)` — JSON transports, one per verb (`get`/`del` take no body): Bearer from `token`, non-2xx **throws** the API's own error
68
85
  - `wx.clip(value)` — cap a return value: oversized → `{ truncated, total, head }`; renders `undefined` as `null` so absence stays visible
69
- - `wx.context(token, section?)` — the site's dynamic context report; no section → its outline
86
+ - `wx.context(token)` — the site's full dynamic context report; inline when small, otherwise a saved Markdown file with a heading outline
70
87
  - `wx.browse(menuUrl, { include, filter, depth })` — walk a docs-portal menu deterministically
71
- - `wx.search(term, { type, max, lines })` — ranked docs search; hits carry endpoint (`VERB url`) + docsUrl + gist, and the worked requests the docs publish for them. A REST search also ranks the **management recipes**, returned as their own `recipes` list ahead of the methods — each with its steps, the endpoints it calls, and `file` when the wix-manage skill is on disk
88
+ - `wx.search(term, { type, max, lines })` — ranked REST docs, Headless articles, and management recipes in one `hits` list; see the response type under Search and browse. Only method hits carry an endpoint.
72
89
  - `wx.page(docsUrl)` — read a doc page; its worked examples come back as titles + line numbers
73
90
  - `wx.bash(cmd)` — shell over saved files (GNU grep/sed; awk is mawk; no rg)
74
91
  - `wx.spec(docsUrl | code)` — a method's exact schema, plus the titles of the docs' own request examples saved at `examplesPath`; pass a hit's docsUrl (direct load), or raw code to query the index yourself
75
92
  - `wx.mgmtRecipes(q?)` — management-recipe index; no arg → categories, a word → matching recipes
76
93
  - `wx.installApp(appDefId, siteId, token)` — install a Wix app on the site (Apps Installer). If discovery finds an API whose app isn't installed on the site, install it first — that's a one-call prerequisite, **not** a reason to fall back to a hand-built alternative. `appDefId` from `search` or the Apps-Created-by-Wix table; `siteId` from `context` (the site report)
77
94
 
95
+ Search makes one combined request across REST, management recipes, and Headless by default.
96
+ The service ranks them together and interleaves methods and articles; the helper preserves that
97
+ order. `max` limits the combined result count (default 15).
98
+
78
99
  Every helper answers inline when the result fits (≤ 4,000 chars — exec results clip at ~5,000).
79
100
  A bigger result is saved under `.agents/skills/wix-base44-connector/tmp/` and comes back as
80
101
  `{ path, bytes, lines, outline }` — the outline is your map into the file. Work a saved file in
@@ -86,13 +107,16 @@ two moves: find with `wx.bash("grep -n 'term' <path> | head -40")` (or across ev
86
107
 
87
108
  ```js
88
109
  const { accessToken } = await base44.asServiceRole.connectors.getConnection("wix");
89
- return await wx.context(accessToken, "Apps"); // no section arg → the report's outline
110
+ return await wx.context(accessToken);
90
111
  ```
91
112
 
92
113
  One report: installed apps **with ids** (incl. Stores' catalog version — V1 vs V3 decides its
93
114
  endpoints), the OAuth app id (**also the visitor `clientId`**), locale, currency, CMS collections.
94
115
  An empty report = bad token, never an empty site.
95
116
 
117
+ Reports over 4,000 characters are saved in full to a temporary Markdown file. The result includes
118
+ its path, byte and line counts, and a heading outline. Read that file to inspect the site context.
119
+
96
120
  ## Learn Wix — find the APIs, learn their contracts
97
121
 
98
122
  ### Management recipes — first stop for an admin task
@@ -107,14 +131,36 @@ await wx.mgmtRecipes("stores"); // a category's list — or any task word: wx.
107
131
  // straight off disk — else `url`: wx.page(url), whole when small, saved + outline when big
108
132
  ```
109
133
 
110
- A recipe carries prerequisites, order, and gotchas that no method page has. A REST search ranks
111
- these same recipes as its own `recipes` list, so they surface either way.
134
+ A recipe carries prerequisites, order, and gotchas that no method page has. `wx.search` ranks
135
+ these same recipes in its `hits` list, so they surface either way.
136
+
137
+ ### Search and browse
138
+
139
+ `wx.search` returns a saved-file reference and ranked hits. Each hit is a method,
140
+ article, or recipe, identified by its title field; only methods have an endpoint.
141
+
142
+ ```ts
143
+ type Section = { title: string; line: number }; // 1-based line in the saved file
144
+ type SearchResult = {
145
+ path: string; bytes: number; lines: number; note?: string;
146
+ hits: ({ docsUrl: string } & (
147
+ | { method: string; endpoint: string | null; gist?: string; examples?: Section[] }
148
+ | { article: string | null; line: number; outline?: Section[] }
149
+ | { recipe: string | null; line: number; lines: number;
150
+ file?: string; steps?: string[]; calls?: string[] }
151
+ ))[];
152
+ };
153
+ ```
112
154
 
113
- ### Find a method — search and browse
155
+ The file at `path` keeps the full returned Markdown, while optional details and excess
156
+ hits may be omitted from the inline response. Null titles or endpoints were not parsed.
157
+ If no result blocks parse, `head` and `note` replace `hits`; the final size-limit fallback
158
+ is `{ truncated: true, total: number, head: string }` instead of the object above.
114
159
 
115
160
  ```js
116
161
  // name the method? search finds it in one call:
117
- await wx.search("stores v3 update product"); // → [{ method, endpoint: "VERB url", docsUrl, gist }]; call wx.<verb>(url, body, token); spec(docsUrl) for the full schema
162
+ const result = await wx.search("stores v3 update product"); // SearchResult above
163
+ // Keep all hit kinds; only a method hit has an endpoint to call and a schema for wx.spec.
118
164
  // exploring an unfamiliar product? browse is deterministic — menuUrl alone orients (children + counts);
119
165
  // filter before listing methods. browse works for both portals this skill uses — REST
120
166
  // (api-reference) and WIX_HEADLESS (go-headless) — just pass that portal's menu URL.
@@ -125,12 +171,7 @@ await wx.browse("https://dev.wix.com/docs/go-headless/authentication", { depth:
125
171
 
126
172
  // don't know where it lives? search ranks, never says "no match" — drop wrong-product hits
127
173
  await wx.search("pause a pricing plan subscription and resume it");
128
- // → { hits: [{ method, endpoint /* callable */, docsUrl, gist }] } — hits often ARE the answer
129
-
130
- // { type } picks the portal (default "REST" — the HTTP APIs this skill calls). Same query,
131
- // other corpus — search WIX_HEADLESS for headless/external client code (visitor auth,
132
- // JS SDK, quick-starts). It returns article-style hits (method gists thin out) — read the saved path.
133
- await wx.search("mint a visitor token and read the current cart", { type: "WIX_HEADLESS" });
174
+ // Same SearchResult: methods, articles, and recipes remain in ranked order.
134
175
  ```
135
176
 
136
177
  Products and their capabilities — the common ones, partial lists:
@@ -250,7 +291,7 @@ return (await res.json()).contacts;
250
291
  - One file per business area, not per call — each file is its own deploy, and deploys cost time.
251
292
  - Call every function you deploy and fix what breaks. Deploying is not testing.
252
293
 
253
- ### A visitor client — src/lib/wixClient.js
294
+ ### Visitor authentication and Wix-hosted flows
254
295
 
255
296
  The "site for visitors" shape (What are you building?), in code — one file pages import. Neither
256
297
  `clientId` (from the context report) nor the minted token is a secret; together they are "an
@@ -270,22 +311,101 @@ export const wix = (path, opts = {}) => fetch("https://www.wixapis.com" + path,
270
311
  headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" } });
271
312
  ```
272
313
 
273
- Token contract: `…/headless/authentication/retrieve-tokens`. Prove the lane in one exec before
274
- writing pages — mint a visitor, make one public read with it:
314
+ If you need to test a visitor API, including redirect sessions, you can do so in `exec_tool`. Mint a visitor
315
+ token and pass it to the API call so the test uses the same identity as the visitor frontend.
316
+ Token contract: [Retrieve Tokens](https://dev.wix.com/docs/api-reference/business-management/headless/authentication/retrieve-tokens.md).
317
+ For example, test a public read:
275
318
 
276
319
  ```js
277
- const { access_token } = await wx.post("https://www.wixapis.com/oauth2/token",
320
+ const { access_token: visitorToken } = await wx.post("https://www.wixapis.com/oauth2/token",
278
321
  { clientId: WIX_CLIENT_ID, grantType: "anonymous" }); // clientId: from the context report
279
- return await wx.post("<a public read from Learn Wix>", { query: {} }, access_token);
322
+ return await wx.post("<a public read from Learn Wix>", { query: {} }, visitorToken);
280
323
  // 200 ⇒ every visitor-facing page in the app is this same call, no server between
281
324
  // a lean default response isn't the whole shape — contracts often define a fields param
282
325
  // that opts INTO heavier parts (formatted prices, media); read the contract for it
283
326
  ```
284
327
 
285
- No OAuth app in the context report to take the `clientId` from? Create one (admin, one-time) —
286
- the returned `id` IS the `clientId`:
328
+ **Any Wix-hosted flow that returns the visitor to your app needs the headless OAuth app's
329
+ redirect config set.** This covers redirect sessions *and* sending a buyer to the Wix-hosted
330
+ checkout `checkoutUrl` and back — any flow where Wix redirects to a URL on your app. Use the
331
+ OAuth app's `clientId` from `wx.context()`; if the report has no OAuth app, create one with the
332
+ admin connector token as shown below, then mint a visitor token from its `clientId` (anonymous
333
+ visitors do not need to log in). **Always set `allowedRedirectUris` and `allowedRedirectDomains`
334
+ when you create it** — an OAuth app created with a name only cannot complete any return, and the
335
+ break is silent (create-checkout and the anonymous token still succeed) until a real buyer is
336
+ redirected and stranded on Wix.
337
+
338
+ ```js
339
+ // Register BOTH of this app's own URLs — its preview URL and its published URL — so returns work
340
+ // before AND after publish. Both are built from the Base44 app id (NOT the Wix OAuth client_id /
341
+ // appId used above):
342
+ // preview: https://preview-sandbox--<base44-app-id>.base44.app
343
+ // published: https://<app-name-slug>-<last 8 of the base44-app-id>.base44.app
344
+ // where <app-name-slug> is the app's name (the brand name) slugified — e.g.
345
+ // "Flash Sale Spark" -> flash-sale-spark-a4615088.base44.app.
346
+ // A custom domain the owner connects overrides the published one; the app name (hence the slug)
347
+ // can also change. So prefer the app's real current published URL when you have it, and update
348
+ // this same OAuth app whenever the name or domain changes — never create another.
349
+ const appOrigins = [previewUrl, publishedUrl]; // this app's actual preview + published URLs
350
+ const loginCallbacks = appOrigins.map(origin => new URL("/login-callback", origin).href);
351
+ const returnDomains = appOrigins.map(origin => new URL(origin).hostname);
352
+
353
+ // OAuth redirect configuration: exact login URLs versus domains for other returns.
354
+ // https://dev.wix.com/docs/go-headless/authentication/setup/allow-redirect-uris-and-domains.md
355
+ const { accessToken: adminToken } = await base44.asServiceRole.connectors.getConnection("wix");
356
+ const { oAuthApp } = await wx.post("https://www.wixapis.com/oauth-app/v1/oauth-apps", {
357
+ oAuthApp: {
358
+ name: "My App",
359
+ // Login callbacks: the authorization request's redirect URI must match exactly.
360
+ allowedRedirectUris: loginCallbacks,
361
+ // Returns from Wix-hosted flows: hostnames only, allowing URLs under each domain.
362
+ allowedRedirectDomains: returnDomains,
363
+ },
364
+ }, adminToken);
365
+ const clientId = oAuthApp.id; // Public visitor client ID, used by the frontend client above.
366
+
367
+ // If destinations change later, update this OAuth app rather than creating another.
368
+ // Read its existing lists and merge new entries before updating, preserving old entries.
369
+ // https://dev.wix.com/docs/api-reference/business-management/headless/oauth-apps/update-oauth-app.md
370
+
371
+ ```
372
+
373
+ Frontend redirect example, using the visitor client above after obtaining a visitor token:
287
374
 
288
375
  ```js
289
- const { oAuthApp } = await wx.post("https://www.wixapis.com/oauth-app/v1/oauth-apps",
290
- { oAuthApp: { name: "My App" } }, accessToken); // oAuthApp.id is the visitor clientId
376
+ import { wix } from "@/lib/wixClient";
377
+
378
+ // Flow prerequisites and supported intents:
379
+ // https://dev.wix.com/docs/go-headless/business-solutions/wix-hosted-pages/redirect-using-the-rest-api.md
380
+ export async function redirectToWix(intent, returnPath = "/") {
381
+ // wix sends the minted visitor token. Never use the admin token here.
382
+ // Admin tokens are for ad hoc management calls or backend functions implementing admin logic.
383
+ // Pass the intent required by the selected flow's schema.
384
+ const response = await wix("/headless/v1/redirect-session", {
385
+ method: "POST",
386
+ body: JSON.stringify({
387
+ ...intent,
388
+ callbacks: {
389
+ postFlowUrl: new URL(returnPath, window.location.origin).href,
390
+ },
391
+ }),
392
+ });
393
+ if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
394
+ const { redirectSession } = await response.json();
395
+ window.location.assign(redirectSession.fullUrl);
396
+ }
291
397
  ```
398
+
399
+ **Building a whole vertical? Bootstrap it with `wix-vibe-headless`.** When the build is a
400
+ visitor-facing site on a Wix vertical it supports — a storefront, bookings, pricing plans,
401
+ events, restaurants, rentals, a blog and more — the `wix-vibe-headless` skill stands the
402
+ vertical up fast: it is built entirely on the same Wix APIs as this skill, but ships working
403
+ code (REST transport, scaffolds, UI) plus management-API abstractions (seeding,
404
+ configuration), so the vertical bootstraps on Base44 in a fraction of the calls.
405
+ Read the entry point at
406
+ [`www.wix.com/skills/wix-vibe-headless/platforms/base44.md`](https://www.wix.com/skills/wix-vibe-headless/platforms/base44.md).
407
+ Its STEP 1 carries the install itself — the verbatim `npx skills add` call to run through
408
+ exec_tool, the companion skills, the vertical table and the scaffold deploy — after which the
409
+ guide lives at `.agents/skills/wix-vibe-headless/platforms/base44.md`. Reading it as
410
+ reference alone is fine, but when you do install, go through its flow end to end — the
411
+ shipped scaffolds and seed path are most of its value.
@@ -3,17 +3,16 @@
3
3
  // back as { path, bytes, lines, outline } with the read tools pre-pointed at it.
4
4
  //
5
5
  // Load per exec (execs share no state) — from disk, network only as first-touch fallback:
6
- // const fs = require("fs"), P = ".agents/skills/wix-base44-connector/utils.js";
6
+ // const fs = require("fs"), P = ".agents/skills/wix-base44-connector/utils.cjs";
7
7
  // if (!fs.existsSync(P)) { fs.mkdirSync(".agents/skills/wix-base44-connector", { recursive: true });
8
- // fs.writeFileSync(P, await (await fetch("https://www.wix.com/skills/wix-base44-connector/scripts/utils.js")).text()); }
9
- // const wx = (() => { const m = { exports: {} };
10
- // new Function("module", "exports", "require", fs.readFileSync(P, "utf8"))(m, m.exports, require); return m.exports; })();
8
+ // fs.writeFileSync(P, await (await fetch("https://www.wix.com/skills/wix-base44-connector/scripts/utils.cjs")).text()); }
9
+ // const wx = require(require("path").resolve(P));
11
10
  //
12
11
  // Read a saved file the way you already know how: wx.bash("grep -n 'term' <path> | head -40")
13
12
  // to find, read_file(<path>) with offset/limit to window (numbered lines, 45K cap — no exec
14
13
  // round needed), pipelines for the rest (GNU grep/sed; awk is mawk; no rg).
15
14
  //
16
- // API responses are site data and never saved — post() projects them to facts.
15
+ // API transports return data directly; oversized context reports are saved for reading.
17
16
 
18
17
  const fs = require("fs");
19
18
  const path = require("path");
@@ -40,9 +39,12 @@ async function req(method, url, body, token) {
40
39
  headers: { "Content-Type": "application/json", ...(token && { Authorization: `Bearer ${token}` }) } });
41
40
  if (!r.ok) {
42
41
  const head = (await r.text()).slice(0, 300);
43
- throw new Error(r.status + " " + head + (r.status < 500
44
- ? " — a 4xx means wrong body or reference: read this endpoint's contract before changing the call"
45
- : ""));
42
+ const guidance = r.status === 401 || r.status === 403
43
+ ? " — check the endpoint's required caller identity, token validity, and permissions"
44
+ : r.status >= 400 && r.status < 500
45
+ ? " — read the API error and endpoint contract before changing the call"
46
+ : "";
47
+ throw new Error(r.status + " " + head + guidance);
46
48
  }
47
49
  return r.json();
48
50
  }
@@ -87,19 +89,14 @@ async function resolveRef(ref) {
87
89
 
88
90
  // ── gather context ────────────────────────────────────────────────────────────
89
91
 
90
- // The dynamic context report — site data, never saved. No section → the whole report
91
- // when it fits, else its header outline; with one → that section's text. Empty
92
- // report = bad token, never an empty site.
93
- async function context(token, section) {
92
+ // Return the full dynamic context report inline when it fits. Larger reports use the
93
+ // same saved-file and heading-outline format as documentation pages.
94
+ async function context(token) {
94
95
  const { markdown } = await post(
95
96
  "https://www.wixapis.com/_api/dynamic-context/v1/dynamic-context/markdown", {}, token);
96
- if (!section) {
97
- if (markdown.length <= BUDGET) return markdown; // most sites: the whole report, one round
98
- return { truncated: true, total: markdown.length, head: markdown.slice(0, BUDGET),
99
- note: "site data — never saved (no path); narrow: wx.context(token, '<section name>')" };
100
- }
101
- const m = markdown.match(new RegExp("^#{1,3} .*" + section + "[\\s\\S]*?(?=\\n#{1,3} |$)", "im"));
102
- return clip({ total: markdown.length, section: m ? m[0] : "not found — call context(token) for the outline" });
97
+ if (markdown.length <= BUDGET) return markdown;
98
+ const saved = save("site-context-" + require("crypto").randomUUID() + ".md", markdown);
99
+ return { ...saved, ...outlineOf(markdown.split("\n")) };
103
100
  }
104
101
 
105
102
  // The wix-manage skill, when it is installed in the sandbox, is these same recipes on disk —
@@ -153,28 +150,22 @@ async function browse(menuUrl, { include, filter, depth } = {}) {
153
150
  // Semantic search — ranks, never says "no match". The reduced hits come back inline AND the full
154
151
  // raw content is saved for grep/window follow-ups. { type } picks the corpus, one per request:
155
152
  // REST (default) · SKILLS · WIX_HEADLESS · SDK · VELO · CLI · WDS · BUILD_APPS · OVERVIEW ·
156
- // BUSINESS_SOLUTIONS. A REST search also runs SKILLS — the management recipes rank as their own
157
- // hits, ahead of the methods, and land in the saved file whole. Each method hit lists the worked
153
+ // BUSINESS_SOLUTIONS. A REST search also runs SKILLS and WIX_HEADLESS — the management recipes appear as recipe hits
154
+ // alongside methods and articles, and land in the saved file whole. Each method hit lists the worked
158
155
  // requests the docs publish for it; every line number reads with read_file(path, offset: <line>).
159
- async function search(term, { type = "REST", max = 5, lines = 0, recipes = type === "REST" } = {}) {
160
- const ask = (document_type, maximum_results) =>
161
- post("https://www.wixapis.com/mcp-docs-search/v1/docs/search/markdown",
162
- // lines_in_each_result 0 skips the server's per-section budget, which otherwise cuts every
163
- // code example after the first 30 lines and the rest after 5, and every recipe at 20 —
164
- // the saved file would carry stubs instead of the requests and flows the hits point at
165
- { search_term: term, document_type, maximum_results, lines_in_each_result: lines });
166
- const [main, skills] = await Promise.all([ask(type, max),
167
- recipes ? ask("SKILLS", 3).catch(() => null) : null]); // a recipe corpus miss must not fail the search
168
- const recipeText = skills?.content ? skills.content.trimEnd() + "\n" : "";
169
- const content = recipeText + main.content;
156
+ async function search(term, { type = "REST", max = 15, lines = 0, recipes = type === "REST", headless = type === "REST" } = {}) {
157
+ const document_types = [...new Set([type, ...(recipes ? ["SKILLS"] : []), ...(headless ? ["WIX_HEADLESS"] : [])])];
158
+ // Keep full documentation in the saved file; compact only the inline index.
159
+ const { content } = await post("https://www.wixapis.com/mcp-docs-search/v1/docs/search/markdown",
160
+ { search_term: term, document_types, maximum_results: max, lines_in_each_result: lines });
170
161
  const nl = []; // newline offsets — a match's char offset becomes its line in the saved file
171
162
  for (let i = content.indexOf("\n"); i >= 0; i = content.indexOf("\n", i + 1)) nl.push(i);
172
163
  const lineAt = (off) => { let lo = 0, hi = nl.length; while (lo < hi) { const m = (lo + hi) >> 1; nl[m] < off ? lo = m + 1 : hi = m; } return lo + 1; };
173
164
  // recipes are articles — no "# Method:" header, no code-example delimiters, and no fixed body
174
165
  // shape. Two things every one of them has: headings, and the endpoints it calls. Those are the
175
166
  // outline — enough to tell whether this is the recipe for the task without reading 400 lines.
176
- const recipeHits = !recipeText ? [] : recipeText.split(/\n---\n+(?=#### )/).map(b => {
177
- const at = content.indexOf(b), rows = b.split("\n");
167
+ const parseRecipe = (b, at) => {
168
+ const rows = b.split("\n");
178
169
  const steps = [], calls = [];
179
170
  let verb = null;
180
171
  for (const t of rows) {
@@ -196,44 +187,74 @@ async function search(term, { type = "REST", max = 5, lines = 0, recipes = type
196
187
  line: at < 0 ? 1 : lineAt(at), lines: rows.length,
197
188
  ...(steps.length && { steps: steps.slice(0, 6) }),
198
189
  ...(calls.length && { calls: calls.slice(0, 4) }) };
199
- }).filter(h => h.docsUrl && h.recipe);
200
- let cursor = recipeText.length;
201
- const hits = main.content.split(/\n---\n+(?=#### )/).map(b => {
190
+ };
191
+ const articleOutline = (block, start) => {
192
+ const outline = [];
193
+ let fenced = false, offset = 0;
194
+ for (const row of block.split("\n")) {
195
+ if (/^\s*(```|~~~)/.test(row)) fenced = !fenced;
196
+ const heading = !fenced && row.match(/^#{2,3} (.+)$/);
197
+ if (heading && !/^(Resource|Article|Article Link|Article Content):/.test(heading[1])) {
198
+ outline.push({ title: heading[1].slice(0, 64), line: lineAt(start + offset) });
199
+ if (outline.length === 3) break;
200
+ }
201
+ offset += row.length + 1;
202
+ }
203
+ return outline;
204
+ };
205
+ let cursor = 0;
206
+ const hits = content.split(/\n---\n+(?=#### )/).map(b => {
202
207
  const start = content.indexOf(b, cursor); cursor = start + b.length;
203
208
  const examples = [...b.matchAll(/--- Code Example: (.+?) ---/g)]
204
209
  .map(m => ({ title: m[1].trim(), line: lineAt(start + m.index) }));
205
210
  const docsUrl = (b.match(/#### \[[^\]]+\]\((https:[^)]+)\)/) || [])[1];
206
211
  const method = (b.match(/^# Method: (.+)$/m) || [])[1];
212
+ if (!method && docsUrl && new URL(docsUrl).pathname.includes("/skills/")) return parseRecipe(b, start);
207
213
  // the REST corpus mixes guides in with the methods — an article has no method header, so
208
214
  // name it from its own title rather than returning a row of nulls
209
215
  if (!method) return { article: (b.match(/^## (?:Resource|Article): (.+)$/m) || [])[1], docsUrl,
210
- line: start < 0 ? 1 : lineAt(start) };
216
+ line: start < 0 ? 1 : lineAt(start),
217
+ outline: articleOutline(b, start) };
211
218
  return {
212
219
  method,
213
220
  endpoint: (b.match(/^# Method API Endpoint: (.+)$/m) || [])[1], // "VERB url" — read the verb + url; call wx.<verb>(url, body, token)
214
221
  docsUrl,
215
- gist: ((b.match(/## Method Description:\s*\n([\s\S]{0,400})/) || [])[1] || "")
216
- .trim().replace(/\s+/g, " ").slice(0, 220),
222
+ gist: (() => {
223
+ const description = ((b.match(/## Method Description:\s*\n([\s\S]*?)(?=\n## |$)/) || [])[1] || "")
224
+ .trim().replace(/\s+/g, " ");
225
+ return description.length > 160 ? description.slice(0, 159).trimEnd() + "…" : description;
226
+ })(),
217
227
  ...(examples.length && { examples }),
218
228
  }; }).filter(h => h.docsUrl);
219
229
  const saved = save("search-" + term.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 40) + ".md", content);
220
- if (!hits.length && !recipeHits.length) return clip({ ...saved, head: content.slice(0, 1200),
221
- note: `no method blocks parsed — raw head above; wx.bash("grep -in 'term' ${saved.path}") for the rest` });
222
- // one URL, one row: a long article is indexed in chunks that rank separately, and a stray skills
223
- // page sits in the REST corpus, so the same page can rank two or three times
230
+ if (!hits.length) return clip({ ...saved, head: content.slice(0, 1200),
231
+ note: `no result blocks parsed — raw head above; wx.bash("grep -in 'term' ${saved.path}") for the rest` });
232
+ // Keep the service's combined ranking/interleave; retain the first occurrence of each URL.
224
233
  const seen = new Set();
225
- const recipeRows = recipeHits.filter(r => !seen.has(r.docsUrl) && seen.add(r.docsUrl));
226
- const uniq = hits.filter(h => !seen.has(h.docsUrl) && seen.add(h.docsUrl));
227
- const out = { ...saved, ...(recipeRows.length && { recipes: recipeRows }), hits: uniq };
234
+ const ordered = hits.filter(h => !seen.has(h.docsUrl) && seen.add(h.docsUrl));
235
+ const recipeRows = ordered.filter(h => h.recipe);
236
+ const uniq = ordered.filter(h => !h.recipe);
237
+ const out = { ...saved, hits: ordered };
228
238
  // over budget, shed enrichment rather than structure — clip would drop the whole shape, and
229
239
  // every title, URL and line number stays useful with the outlines gone
230
240
  for (const shed of [() => recipeRows.forEach(r => delete r.calls),
231
241
  () => recipeRows.forEach(r => delete r.steps),
232
242
  () => uniq.forEach(h => { if (h.examples) h.examples = h.examples.slice(0, 3); }),
233
- () => uniq.forEach(h => delete h.examples)]) {
243
+ () => uniq.forEach(h => delete h.examples),
244
+ () => uniq.forEach(h => delete h.gist),
245
+ () => uniq.forEach(h => delete h.outline)]) {
234
246
  if (JSON.stringify(out).length <= BUDGET) break;
235
247
  shed();
236
248
  }
249
+ // Preserve at least one result of each kind when the inline index needs trimming.
250
+ while (JSON.stringify(out).length > BUDGET) {
251
+ const kind = h => h.recipe ? "recipe" : h.method ? "method" : "article";
252
+ const counts = out.hits.reduce((n, h) => (n[kind(h)] = (n[kind(h)] || 0) + 1, n), {});
253
+ const index = out.hits.findLastIndex(h => counts[kind(h)] > 1);
254
+ if (index < 0) break;
255
+ out.hits.splice(index, 1);
256
+ out.note = "Additional results are in the saved file.";
257
+ }
237
258
  return clip(out);
238
259
  }
239
260