@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.
- package/package.json +1 -1
- package/skills/wix-app/SKILL.md +4 -4
- package/skills/wix-app/references/APP_VALIDATION.md +7 -33
- package/skills/wix-app/references/DASHBOARD_PAGE.md +18 -14
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +75 -92
- package/skills/wix-app/references/WIX_PATTERNS_DOCS.md +23 -26
- package/skills/wix-app/references/dashboard-page/COLLECTION_TOOLKIT.md +7 -6
- package/skills/wix-app/references/dashboard-page/ENTITY_PAGE_TOOLKIT.md +71 -4
- package/skills/wix-app/references/dashboard-page/PATTERNS_BUNDLE_READING.md +67 -7
- package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +1 -1
- package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +9 -6
- package/skills/wix-app/references/editor-react-component/COMPONENT-CONTRACT.md +59 -89
- package/skills/wix-app/references/editor-react-component/COMPONENT-PREVIEW.md +23 -4
- package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +29 -14
- package/skills/wix-base44-connector/SKILL.md +168 -48
- package/skills/wix-base44-connector/scripts/{utils.js → utils.cjs} +69 -48
- package/skills/wix-docs/SKILL.md +109 -111
- package/skills/wix-docs/references/API_SPEC_SEARCH.md +35 -23
- package/skills/wix-docs/references/CALLING.md +91 -0
- package/skills/wix-docs/references/EXTRACTING.md +5 -3
- package/skills/wix-headless/references/DOC_DISCOVERY.md +9 -3
- package/skills/wix-headless/references/inline-recipes/setup-forms.md +57 -10
- package/skills/wix-manage/SKILL.md +106 -92
- package/skills/wix-manage/references/dashboard-navigation/routes.json +12 -52
- package/skills/wix-manage/references/ecommerce/shipping/ecom-shipping-api.md +0 -2
- package/skills/wix-manage/references/forms/create-form.md +272 -184
- package/skills/wix-manage/references/forms/forms-dashboard-navigation.md +13 -10
- package/skills/wix-manage/references/forms/update-form.md +122 -0
- package/skills/wix-manage/references/media/generate-image-with-ai.md +86 -0
- package/skills/wix-manage/references/seo/manage-content-plan.md +247 -0
- package/skills/wix-manage/references/seo/manage-seo-tags.md +59 -6
- package/skills/wix-manage/references/stores/change-store-currency.md +10 -0
- package/skills/wix-manage/references/stores/create-product-catalog-v3.md +16 -1
- package/skills/wix-manage/references/stores/update-product-with-options.md +9 -0
- package/skills/wix-vibe-headless/SKILL.md +17 -18
- package/skills/wix-vibe-headless/install/pin-agents-md.cjs +1 -1
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed-with-install.md +118 -0
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +55 -62
- package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +31 -29
- package/skills/wix-vibe-headless/platforms/base44.md +12 -5
- package/skills/wix-vibe-headless/platforms/generic.md +13 -12
- package/skills/wix-vibe-headless/references/blog/INSTRUCTIONS.md +4 -13
- package/skills/wix-vibe-headless/references/blog/seed/SEED.md +4 -8
- package/skills/wix-vibe-headless/references/blog/seed/{seed-blog.js → seed-blog.cjs} +2 -3
- package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +5 -14
- package/skills/wix-vibe-headless/references/bookings/seed/SEED.md +5 -9
- package/skills/wix-vibe-headless/references/bookings/seed/{seed-bookings.js → seed-bookings.cjs} +2 -2
- package/skills/wix-vibe-headless/references/bookings/seed/{seed-rentals.js → seed-rentals.cjs} +3 -3
- package/skills/wix-vibe-headless/references/cms/INSTRUCTIONS.md +1 -1
- package/skills/wix-vibe-headless/references/cms/seed/SEED.md +2 -2
- package/skills/wix-vibe-headless/references/events/INSTRUCTIONS.md +4 -13
- package/skills/wix-vibe-headless/references/events/seed/SEED.md +3 -7
- package/skills/wix-vibe-headless/references/events/seed/{seed-events.js → seed-events.cjs} +2 -2
- package/skills/wix-vibe-headless/references/forms/INSTRUCTIONS.md +24 -7
- package/skills/wix-vibe-headless/references/forms/seed/SEED.md +91 -17
- package/skills/wix-vibe-headless/references/members/INSTRUCTIONS.md +4 -13
- package/skills/wix-vibe-headless/references/members/seed/SEED.md +2 -3
- package/skills/wix-vibe-headless/references/portfolio/INSTRUCTIONS.md +4 -13
- package/skills/wix-vibe-headless/references/portfolio/app/components/ProjectMedia.jsx +1 -1
- package/skills/wix-vibe-headless/references/portfolio/seed/SEED.md +3 -7
- package/skills/wix-vibe-headless/references/portfolio/seed/{seed-portfolio.js → seed-portfolio.cjs} +2 -2
- package/skills/wix-vibe-headless/references/pricing-plans/INSTRUCTIONS.md +5 -14
- package/skills/wix-vibe-headless/references/pricing-plans/app/components/OrderCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/pricing-plans/seed/SEED.md +3 -7
- package/skills/wix-vibe-headless/references/pricing-plans/seed/{seed-pricing-plans.js → seed-pricing-plans.cjs} +2 -2
- package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +5 -15
- package/skills/wix-vibe-headless/references/restaurants/seed/SEED.md +3 -7
- package/skills/wix-vibe-headless/references/restaurants/seed/{seed-restaurants.js → seed-restaurants.cjs} +2 -2
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +359 -327
- package/skills/wix-vibe-headless/references/storefront/app/context/CartContext.jsx +1 -0
- package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductDetail.js +2 -2
- package/skills/wix-vibe-headless/references/storefront/app/hooks/useShop.js +63 -78
- package/skills/wix-vibe-headless/references/storefront/app/rest/wix-store-catalog.js +59 -43
- package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +46 -10
- package/skills/wix-vibe-headless/references/storefront/seed/{seed-store.js → seed-store.cjs} +163 -25
- 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.
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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.
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
32
|
-
base44/functions/… ──(admin token)
|
|
33
|
-
exec_tool
|
|
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.
|
|
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.
|
|
61
|
-
const wx = (()
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
111
|
-
these same recipes
|
|
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
|
-
|
|
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");
|
|
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
|
-
//
|
|
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
|
-
###
|
|
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
|
-
|
|
274
|
-
|
|
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: {} },
|
|
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
|
-
|
|
286
|
-
|
|
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
|
-
|
|
290
|
-
|
|
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.
|
|
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.
|
|
9
|
-
// const wx = (()
|
|
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
|
|
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
|
-
|
|
44
|
-
? " —
|
|
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
|
-
//
|
|
91
|
-
//
|
|
92
|
-
|
|
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 (
|
|
97
|
-
|
|
98
|
-
|
|
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
|
|
157
|
-
//
|
|
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 =
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
|
177
|
-
const
|
|
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
|
-
}
|
|
200
|
-
|
|
201
|
-
|
|
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: ((
|
|
216
|
-
|
|
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
|
|
221
|
-
note: `no
|
|
222
|
-
//
|
|
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
|
|
226
|
-
const
|
|
227
|
-
const
|
|
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
|
|