@wix/agent-skills 1.16.0 → 1.16.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 (94) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-app/SKILL.md +72 -8
  3. package/skills/wix-app/references/CODE_QUALITY.md +0 -1
  4. package/skills/wix-app/references/DASHBOARD_MODAL.md +21 -14
  5. package/skills/wix-app/references/DASHBOARD_PAGE.md +30 -184
  6. package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +19 -3
  7. package/skills/wix-app/references/WIX_PATTERNS_DOCS.md +172 -0
  8. package/skills/wix-app/references/dashboard-page/WDS_LAYOUT.md +187 -0
  9. package/skills/wix-app/references/editor-react-component/A11Y-REVIEW.md +12 -7
  10. package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +31 -2
  11. package/skills/wix-app/references/editor-react-component/COMPONENT-API.md +6 -1
  12. package/skills/wix-app/references/editor-react-component/COMPONENT-CONFIGURATION.md +60 -0
  13. package/skills/wix-app/references/editor-react-component/DIRECTIONALITY.md +6 -6
  14. package/skills/wix-app/references/editor-react-component/FUNCTION-HANDLERS.md +167 -0
  15. package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +2 -2
  16. package/skills/wix-app/references/editor-react-component/SITE-CONTEXT-HOOKS.md +187 -0
  17. package/skills/wix-app/scripts/scan-a11y-code.cjs +777 -0
  18. package/skills/wix-app/scripts/scan-a11y-eslint.cjs +201 -0
  19. package/skills/wix-base44-connector/SKILL.md +185 -0
  20. package/skills/wix-base44-connector/scripts/utils.js +212 -0
  21. package/skills/wix-design-system/SKILL.md +19 -15
  22. package/skills/wix-docs/SKILL.md +75 -1
  23. package/skills/wix-headless/references/CAPABILITIES.md +16 -3
  24. package/skills/wix-headless/references/SDK_HANDOFF.md +1 -1
  25. package/skills/wix-headless/references/SEED.md +1 -1
  26. package/skills/wix-headless/references/inline-recipes/how-to-code-forms.md +560 -102
  27. package/skills/wix-headless/references/inline-recipes/setup-forms.md +210 -143
  28. package/skills/wix-manage/SKILL.md +20 -17
  29. package/skills/wix-manage/references/accessibility/scan-site-accessibility.md +98 -0
  30. package/skills/wix-manage/references/analytics/analytics-dashboard-navigation.md +1 -2
  31. package/skills/wix-manage/references/domains/domain-search-purchase-and-connect.md +2603 -0
  32. package/skills/wix-manage/references/events/create-wix-event.md +112 -90
  33. package/skills/wix-manage/references/events/manage-wix-events.md +33 -94
  34. package/skills/wix-manage/references/sites/create-headless-site.md +1 -0
  35. package/skills/wix-manage/references/sites/manage-oauth-apps.md +154 -0
  36. package/skills/wix-manage/references/sites/read-site-context.md +125 -0
  37. package/skills/wix-manage/references/sites/site-import.md +67 -15
  38. package/skills/wix-manage/references/stores/create-product-catalog-v3.md +273 -0
  39. package/skills/wix-replatform/resources/rp-target-wix/lib/wix-writers.js +1 -1
  40. package/skills/wix-vibe-headless/SKILL.md +2 -1
  41. package/skills/wix-vibe-headless/install/deploy.cjs +63 -3
  42. package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +104 -0
  43. package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +74 -0
  44. package/skills/wix-vibe-headless/platforms/base44.md +15 -40
  45. package/skills/wix-vibe-headless/references/blog/INSTRUCTIONS.md +7 -11
  46. package/skills/wix-vibe-headless/references/blog/seed/SEED.md +1 -1
  47. package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +7 -11
  48. package/skills/wix-vibe-headless/references/bookings/seed/SEED.md +1 -1
  49. package/skills/wix-vibe-headless/references/cms/INSTRUCTIONS.md +1 -2
  50. package/skills/wix-vibe-headless/references/events/INSTRUCTIONS.md +7 -12
  51. package/skills/wix-vibe-headless/references/events/seed/SEED.md +1 -1
  52. package/skills/wix-vibe-headless/references/members/INSTRUCTIONS.md +7 -11
  53. package/skills/wix-vibe-headless/references/portfolio/INSTRUCTIONS.md +7 -11
  54. package/skills/wix-vibe-headless/references/portfolio/seed/SEED.md +1 -1
  55. package/skills/wix-vibe-headless/references/pricing-plans/INSTRUCTIONS.md +7 -11
  56. package/skills/wix-vibe-headless/references/pricing-plans/seed/SEED.md +1 -1
  57. package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +7 -11
  58. package/skills/wix-vibe-headless/references/restaurants/seed/SEED.md +1 -1
  59. package/skills/wix-vibe-headless/references/shared/app/components/WixManageBanner.jsx +1 -1
  60. package/skills/wix-vibe-headless/references/shared/app/rest/wix-config.js +5 -3
  61. package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +75 -13
  62. package/skills/wix-vibe-headless/references/storefront/app/components/ProductCard.jsx +29 -86
  63. package/skills/wix-vibe-headless/references/storefront/app/components/ProductGrid.jsx +3 -8
  64. package/skills/wix-vibe-headless/references/storefront/app/components/VariantPicker.jsx +78 -105
  65. package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductCard.js +92 -0
  66. package/skills/wix-vibe-headless/references/storefront/app/hooks/useVariantOptions.js +76 -0
  67. package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +1 -1
  68. package/skills/wix-vibe-headless/references/storefront/seed/seed-store.js +44 -5
  69. package/skills/wix-app/references/AUTO_PATTERNS_DASHBOARD.md +0 -326
  70. package/skills/wix-app/references/auto-patterns-dashboard/action-cell.md +0 -115
  71. package/skills/wix-app/references/auto-patterns-dashboard/app-config-structure.md +0 -264
  72. package/skills/wix-app/references/auto-patterns-dashboard/app-context.md +0 -37
  73. package/skills/wix-app/references/auto-patterns-dashboard/bulk-actions.md +0 -86
  74. package/skills/wix-app/references/auto-patterns-dashboard/collection-page-actions.md +0 -119
  75. package/skills/wix-app/references/auto-patterns-dashboard/collection-page.md +0 -103
  76. package/skills/wix-app/references/auto-patterns-dashboard/custom-actions-override.md +0 -76
  77. package/skills/wix-app/references/auto-patterns-dashboard/custom-columns-override.md +0 -82
  78. package/skills/wix-app/references/auto-patterns-dashboard/custom-components-override.md +0 -89
  79. package/skills/wix-app/references/auto-patterns-dashboard/custom-header-override.md +0 -124
  80. package/skills/wix-app/references/auto-patterns-dashboard/custom-sections-override.md +0 -91
  81. package/skills/wix-app/references/auto-patterns-dashboard/custom-slots-override.md +0 -71
  82. package/skills/wix-app/references/auto-patterns-dashboard/entity-page-actions.md +0 -70
  83. package/skills/wix-app/references/auto-patterns-dashboard/entity-page-view-actions.md +0 -73
  84. package/skills/wix-app/references/auto-patterns-dashboard/entity-page.md +0 -98
  85. package/skills/wix-app/references/auto-patterns-dashboard/example-patterns.json +0 -278
  86. package/skills/wix-app/references/auto-patterns-dashboard/pages-configuration.md +0 -94
  87. package/skills/wix-app/references/auto-patterns-dashboard/resolved-action.md +0 -43
  88. package/skills/wix-app/references/auto-patterns-dashboard/sdk-utilities.md +0 -66
  89. package/skills/wix-app/references/auto-patterns-dashboard/views.md +0 -76
  90. package/skills/wix-app/scripts/generate-auto-patterns.js +0 -510
  91. package/skills/wix-manage/references/domains/domain-search-and-purchase.md +0 -437
  92. package/skills/wix-manage/references/stores/create-product-from-image.md +0 -1031
  93. package/skills/wix-manage/references/stores/create-product-with-options-catalog-v3.md +0 -623
  94. package/skills/wix-manage/references/stores/setup-online-store-catalog-v3.md +0 -106
@@ -0,0 +1,201 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // oxlint-disable no-console preserve-caught-error
5
+
6
+ const path = require('path');
7
+ const { createRequire } = require('module');
8
+
9
+ const ROOT = process.cwd();
10
+ const LOCAL_REQUIRE = createRequire(__filename);
11
+ const ROOT_REQUIRE = createRequire(path.join(ROOT, 'package.json'));
12
+ const { ESLint } = loadModule('eslint');
13
+ const jsxA11y = loadModule('eslint-plugin-jsx-a11y');
14
+ const tsParser = loadModule('@typescript-eslint/parser');
15
+
16
+ const RULE_CONFIG = {
17
+ 'jsx-a11y/alt-text': 'error',
18
+ 'jsx-a11y/anchor-has-content': 'error',
19
+ 'jsx-a11y/anchor-is-valid': 'error',
20
+ 'jsx-a11y/aria-activedescendant-has-tabindex': 'error',
21
+ 'jsx-a11y/aria-props': 'error',
22
+ 'jsx-a11y/aria-proptypes': 'error',
23
+ 'jsx-a11y/aria-role': 'error',
24
+ 'jsx-a11y/aria-unsupported-elements': 'error',
25
+ 'jsx-a11y/click-events-have-key-events': 'error',
26
+ 'jsx-a11y/heading-has-content': 'error',
27
+ 'jsx-a11y/iframe-has-title': 'error',
28
+ 'jsx-a11y/img-redundant-alt': 'error',
29
+ 'jsx-a11y/interactive-supports-focus': 'error',
30
+ 'jsx-a11y/label-has-associated-control': 'error',
31
+ 'jsx-a11y/media-has-caption': 'error',
32
+ 'jsx-a11y/mouse-events-have-key-events': 'error',
33
+ 'jsx-a11y/no-access-key': 'error',
34
+ 'jsx-a11y/no-aria-hidden-on-focusable': 'error',
35
+ 'jsx-a11y/no-autofocus': 'error',
36
+ 'jsx-a11y/no-distracting-elements': 'error',
37
+ 'jsx-a11y/no-interactive-element-to-noninteractive-role': 'error',
38
+ 'jsx-a11y/no-noninteractive-element-interactions': 'error',
39
+ 'jsx-a11y/no-noninteractive-element-to-interactive-role': 'error',
40
+ 'jsx-a11y/no-noninteractive-tabindex': 'error',
41
+ 'jsx-a11y/no-redundant-roles': 'error',
42
+ 'jsx-a11y/no-static-element-interactions': 'error',
43
+ 'jsx-a11y/prefer-tag-over-role': 'error',
44
+ 'jsx-a11y/role-has-required-aria-props': 'error',
45
+ 'jsx-a11y/role-supports-aria-props': 'error',
46
+ 'jsx-a11y/scope': 'error',
47
+ 'jsx-a11y/tabindex-no-positive': 'error',
48
+ };
49
+
50
+ function toRelative(filePath) {
51
+ return path.relative(ROOT, filePath) || filePath;
52
+ }
53
+
54
+ function severityLabel(severity) {
55
+ if (severity === 2) return 'error';
56
+ if (severity === 1) return 'warning';
57
+ return 'off';
58
+ }
59
+
60
+ function loadModule(name) {
61
+ try {
62
+ return LOCAL_REQUIRE(name);
63
+ } catch (localError) {
64
+ try {
65
+ return ROOT_REQUIRE(name);
66
+ } catch (rootError) {
67
+ throw new Error(
68
+ `Missing dependency "${name}". Local resolution failed: ${localError.message}. Root resolution failed: ${rootError.message}`,
69
+ );
70
+ }
71
+ }
72
+ }
73
+
74
+ function createEslint() {
75
+ try {
76
+ return new ESLint({
77
+ overrideConfigFile: true,
78
+ overrideConfig: [
79
+ {
80
+ files: ['**/*.{tsx,jsx,ts,js}'],
81
+ languageOptions: {
82
+ parser: tsParser,
83
+ parserOptions: {
84
+ ecmaFeatures: { jsx: true },
85
+ ecmaVersion: 2022,
86
+ sourceType: 'module',
87
+ },
88
+ },
89
+ plugins: {
90
+ 'jsx-a11y': jsxA11y,
91
+ },
92
+ rules: RULE_CONFIG,
93
+ },
94
+ ],
95
+ });
96
+ } catch (flatConfigError) {
97
+ try {
98
+ return new ESLint({
99
+ useEslintrc: false,
100
+ overrideConfig: {
101
+ parser: '@typescript-eslint/parser',
102
+ plugins: ['jsx-a11y'],
103
+ parserOptions: {
104
+ ecmaFeatures: { jsx: true },
105
+ ecmaVersion: 2022,
106
+ sourceType: 'module',
107
+ },
108
+ rules: RULE_CONFIG,
109
+ },
110
+ extensions: ['.tsx', '.jsx', '.ts', '.js'],
111
+ });
112
+ } catch (legacyConfigError) {
113
+ throw new Error(
114
+ `Failed to initialize ESLint. Flat config error: ${flatConfigError.message}. Legacy config error: ${legacyConfigError.message}`,
115
+ );
116
+ }
117
+ }
118
+ }
119
+
120
+ async function main() {
121
+ const files = process.argv.slice(2);
122
+ if (files.length === 0) {
123
+ console.log(
124
+ JSON.stringify(
125
+ {
126
+ error: 'No files specified.',
127
+ usage: 'node <SKILL_ROOT>/scripts/scan-a11y-eslint.cjs <file1> [file2] ...',
128
+ },
129
+ null,
130
+ 2,
131
+ ),
132
+ );
133
+ process.exit(1);
134
+ }
135
+
136
+ const absoluteFiles = files.map((f) => path.resolve(f));
137
+
138
+ const eslint = createEslint();
139
+
140
+ const results = await eslint.lintFiles(absoluteFiles);
141
+
142
+ const findings = [];
143
+ const parseErrors = [];
144
+
145
+ for (const result of results) {
146
+ const relFile = toRelative(result.filePath);
147
+
148
+ for (const msg of result.messages) {
149
+ if (msg.fatal) {
150
+ parseErrors.push({
151
+ file: relFile,
152
+ line: msg.line,
153
+ column: msg.column,
154
+ message: msg.message,
155
+ });
156
+ continue;
157
+ }
158
+
159
+ if (!msg.ruleId || !msg.ruleId.startsWith('jsx-a11y/')) continue;
160
+
161
+ findings.push({
162
+ file: relFile,
163
+ line: msg.line,
164
+ column: msg.column,
165
+ endLine: msg.endLine ?? null,
166
+ endColumn: msg.endColumn ?? null,
167
+ rule: msg.ruleId,
168
+ severity: severityLabel(msg.severity),
169
+ message: msg.message,
170
+ });
171
+ }
172
+ }
173
+
174
+ const ruleBreakdown = {};
175
+ for (const f of findings) {
176
+ ruleBreakdown[f.rule] = (ruleBreakdown[f.rule] || 0) + 1;
177
+ }
178
+
179
+ const output = {
180
+ meta: {
181
+ filesScanned: files.length,
182
+ engine: 'eslint + eslint-plugin-jsx-a11y',
183
+ rulesEnabled: Object.keys(RULE_CONFIG).length,
184
+ parseErrors,
185
+ },
186
+ findings,
187
+ summary: {
188
+ totalFindings: findings.length,
189
+ filesWithFindings: new Set(findings.map((f) => f.file)).size,
190
+ cleanFiles: files.length - new Set(findings.map((f) => f.file)).size,
191
+ ruleBreakdown,
192
+ },
193
+ };
194
+
195
+ console.log(JSON.stringify(output, null, 2));
196
+ }
197
+
198
+ main().catch((err) => {
199
+ console.error(JSON.stringify({ error: err.message }, null, 2));
200
+ process.exit(1);
201
+ });
@@ -0,0 +1,185 @@
1
+ ---
2
+ name: wix-base44-connector
3
+ description: "Build on Wix from the Base44 builder sandbox — this file is the complete guide (site context, doc discovery, API contracts, who calls Wix from where), and scripts/utils.js is its helper module, loaded from disk per exec. Triggers: build a Base44 app on a connected Wix site, look up a Wix API from the sandbox, decide frontend vs backend for a Wix call."
4
+ ---
5
+
6
+ # Building on Wix from Base44
7
+
8
+ The Wix connector is connected; this is how the app gets built on it — gather the site's context,
9
+ find the APIs and learn their contracts from the docs, write the code. **Discover everything**: endpoints, paths, doc URLs,
10
+ request and response fields all come from the calls below, never from memory or pattern. 404 or
11
+ empty ⇒ discover, not permute. Examples teach mechanics and go stale — verify before relying.
12
+
13
+ ## What are you building?
14
+
15
+ The app's audience picks the token, and the token picks the architecture: the **visitor token is
16
+ public** — anyone can mint it from the site's `clientId` — and the **admin token is a secret**,
17
+ the connector's, server-side only.
18
+
19
+ ```
20
+ browser ──(visitor token)─► wixapis.com the visitor's own reads & actions
21
+ base44/functions/* ──(admin token)───► wixapis.com work that needs the owner's identity
22
+ exec_tool ──(admin token)───► wixapis.com you: ad hoc probing/managing while building
23
+ ```
24
+
25
+ **A site for visitors** — storefront, blog, booking. Headless means the Wix site has no pages of
26
+ its own: your app IS its frontend. **The complete visitor experience —
27
+ every page, every read, every action a visitor takes — is browser calls on the visitor token;
28
+ none of it needs a backend function.** Public reads
29
+ included (the visitor token queries the catalog directly), and `carts/current/*` + checkout act
30
+ on the CALLER's cart, so only the visitor token reaches the visitor's cart. One file carries it
31
+ all: `src/lib/wixClient.js` (Write the code, below). `base44/functions/*` appear only where work
32
+ needs the owner's identity — elevated-permission ops a visitor triggers, webhooks, scheduled
33
+ jobs — and for the app's non-Wix backend.
34
+
35
+ **An admin tool for the owner** — dashboard, back office. The pages act as the owner, whose token
36
+ is a secret: `pages → base44/functions/* ──(admin token)──► wixapis.com`.
37
+
38
+ ## The helpers
39
+
40
+ Research and probing run in exec_tool, and one loader opens every exec (execs share no state —
41
+ reload each round; the module lives on disk next to this file, network only as first-touch
42
+ fallback):
43
+
44
+ ```js
45
+ const fs = require("fs"), P = ".agents/skills/wix-base44-connector/utils.js";
46
+ if (!fs.existsSync(P)) { fs.mkdirSync(".agents/skills/wix-base44-connector", { recursive: true });
47
+ fs.writeFileSync(P, await (await fetch("https://www.wix.com/skills/wix-base44-connector/scripts/utils.js")).text()); }
48
+ const wx = (() => { const m = { exports: {} };
49
+ new Function("module", "exports", "require", fs.readFileSync(P, "utf8"))(m, m.exports, require); return m.exports; })();
50
+ ```
51
+
52
+ Results ≤ 4,000 chars come back inline (exec results clip at ~5,000); anything bigger is saved
53
+ under `.agents/skills/wix-base44-connector/scratch/` and returns `{ path, bytes, lines, outline }` —
54
+ the outline is the map. Read a saved file the way you already know how:
55
+ `wx.bash("grep -n 'term' <path> | head -40")` to find (GNU grep/sed, awk is mawk, no rg;
56
+ across everything saved: `grep -rn 'term' .agents/skills/wix-base44-connector/scratch/`), and
57
+ `read_file` to quote — a window via `offset`/`limit` at the lines grep named, or the whole
58
+ file when it fits read_file's 45K cap. **API responses are site data and never land in
59
+ scratch** — project them to facts. Fetch every URL inside exec with `fetch()` — website/browser
60
+ tools clip at 10,000 chars silently. One exec per round; timeout 10s, up to 120 via `{timeout}`.
61
+
62
+ ## Gather context — the dynamic context report
63
+
64
+ ```js
65
+ const { accessToken } = await base44.asServiceRole.connectors.getConnection("wix");
66
+ return await wx.context(accessToken, "Apps"); // no section arg → the report's outline
67
+ ```
68
+
69
+ One report: installed apps **with ids** (incl. Stores' catalog version — V1 vs V3 decides its
70
+ endpoints), the OAuth app id (**also the visitor `clientId`**), locale, currency, CMS collections.
71
+ An empty report = bad token, never an empty site.
72
+
73
+ ## Learn Wix — find the APIs, learn their contracts
74
+
75
+ ```js
76
+ // know the product? browse is deterministic — menuUrl alone orients (children + counts);
77
+ // filter before listing methods, unfiltered listings clip
78
+ await wx.browse("https://dev.wix.com/docs/api-reference/business-solutions/bookings/bookings",
79
+ { include: ["METHOD"], filter: "resched", depth: 4 });
80
+
81
+ // don't know where it lives? search ranks, never says "no match" — drop wrong-product hits
82
+ await wx.search("pause a pricing plan subscription and resume it");
83
+ // → { hits: [{ method, endpoint /* callable */, docsUrl, gist }] } — hits often ARE the answer
84
+ ```
85
+
86
+ Go deeper for fields, enums, or absence — only the spec index proves absence.
87
+
88
+ ### Read a doc page
89
+
90
+ Method pages are 100 KB+, twin REST and SDK halves repeating field names at different types —
91
+ map and window in the SAME exec; coordinates are for your code, not for a second round:
92
+
93
+ ```js
94
+ const pg = await wx.page(docsUrl); // whole text when small; else { path, bytes, lines, outline }
95
+ // your half, windowed to your term and its Examples (a complete working request — URL, headers,
96
+ // body — usually all you need), one round:
97
+ return wx.bash(`sed -n '/^## REST API/,/^## JavaScript SDK/p' ${pg.path} | grep -B5 -A40 -i 'examples\\|<term>' | head -c 3800`);
98
+ // a giant fenced example → its field vocabulary instead of paging it:
99
+ // wx.bash(`sed -n '<a>,<b>p' ${pg.path} | grep -oE '"[a-zA-Z]+":' | sort -u | head -60`)
100
+ ```
101
+
102
+ `search` also saves its raw content beside the inline hits — grep its `path` when a hit's six
103
+ lines weren't enough.
104
+
105
+ ### The spec index
106
+
107
+ Answers questions about pages you already found — arrive with a `docsUrl`, match by it. Also the
108
+ only proof an API does NOT exist:
109
+
110
+ ```js
111
+ await wx.spec(`
112
+ const r = lightIndex.find(x => x.docsUrl === "<docsUrl from browse/search>");
113
+ return r.methods.map(m => ({ op: m.operationId.split(".").pop(), verb: m.httpMethod,
114
+ call: m.publicUrl })); // publicUrl is callable — path is PARTIAL
115
+ `);
116
+ // request fields next round: getResourceSchemaByUrl(docsUrl) →
117
+ // m.requestBody.content["application/json"].schema.properties — names and types, drill deeper
118
+ // per round; $circular stubs resolve via s.components.schemas["<name>"]
119
+ ```
120
+
121
+ ### Management recipes — check before composing admin flows
122
+
123
+ ~100 curated multi-step recipes (install apps, seed catalogs, set up whole verticals):
124
+
125
+ ```js
126
+ await wx.recipes(); // categories with counts
127
+ await wx.recipes("stores"); // a category's list — or any task word: wx.recipes("coupon")
128
+ await wx.page(url); // read the chosen recipe — whole when small, saved + outline when
129
+ // big; then grep / read_file windows by the outline's line numbers
130
+ ```
131
+
132
+ A matching recipe beats composing the flow from single endpoints: it carries ordering and
133
+ cross-step gotchas no method page mentions.
134
+
135
+ ## Write the code
136
+
137
+ **Response shapes obey the discover rule in every lane**: code against fields you saw in a live
138
+ response or the schema — remembered names are often from older versions. Probe one real row first.
139
+
140
+ ### Admin calls — exec ad hoc, backend functions deployed
141
+
142
+ ```js
143
+ const { accessToken } = await base44.asServiceRole.connectors.getConnection("wix");
144
+ const data = await wx.post("https://www.wixapis.com/contacts/v5/contacts/query", // spec-index publicUrl
145
+ { query: { cursorPaging: { limit: 10 } } }, accessToken);
146
+ // let it throw — the thrown message is your result; .catch hides the answer
147
+ return wx.clip({ error: null, count: data.contacts?.length, first: data.contacts?.[0] });
148
+ // ↑ one real row, whole — the shapes you code against live here, not in key names
149
+ ```
150
+
151
+ The same call deploys as `base44/functions/*` (work the app does as the owner) — shipped code
152
+ carries its own four-line fetch; the helpers are a build tool, not a runtime dependency.
153
+
154
+ ### A visitor client — src/lib/wixClient.js
155
+
156
+ The "site for visitors" shape (What are you building?), in code — one file pages import. Neither
157
+ `clientId` (from the context report) nor the minted token is a secret; together they are "an
158
+ anonymous visitor", safe in shipped code:
159
+
160
+ ```js
161
+ // src/lib/wixClient.js
162
+ let token;
163
+ const mint = async (body) => {
164
+ const r = await (await fetch("https://www.wixapis.com/oauth2/token", { method: "POST",
165
+ headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) })).json();
166
+ token = r.access_token; sessionStorage.setItem("wixRefresh", r.refresh_token); // expires_in: 14400s = 4h
167
+ };
168
+ // first visit: mint({ clientId: WIX_CLIENT_ID, grantType: "anonymous" });
169
+ // on expiry: mint({ refreshToken: sessionStorage.getItem("wixRefresh"), grantType: "refresh_token" });
170
+ // a fresh anonymous mint is a NEW visitor — the old one's cart goes with it
171
+ export const wix = (path, opts = {}) => fetch("https://www.wixapis.com" + path, { ...opts,
172
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" } });
173
+ ```
174
+
175
+ Token contract: `…/headless/authentication/retrieve-tokens`. Prove the lane in one exec before
176
+ writing pages — mint a visitor, make one public read with it:
177
+
178
+ ```js
179
+ const { access_token } = await wx.post("https://www.wixapis.com/oauth2/token",
180
+ { clientId: WIX_CLIENT_ID, grantType: "anonymous" }); // clientId: from the context report
181
+ return await wx.post("<a public read from Learn Wix>", { query: {} }, access_token);
182
+ // 200 ⇒ every visitor-facing page in the app is this same call, no server between
183
+ // a lean default response isn't the whole shape — contracts often define a fields param
184
+ // that opts INTO heavier parts (formatted prices, media); read the contract for it
185
+ ```
@@ -0,0 +1,212 @@
1
+ // Wix docs helpers for the Base44 sandbox — the scratch-lane set. Small results return
2
+ // inline; anything over ~4,000 chars (exec results clip at ~5,000) is saved under the
3
+ // scratch dir and comes back as { path, bytes, lines, outline } with the read tools
4
+ // pre-pointed at it.
5
+ //
6
+ // Load per exec (execs share no state) — from disk, network only as first-touch fallback:
7
+ // const fs = require("fs"), P = ".agents/skills/wix-base44-connector/utils.js";
8
+ // if (!fs.existsSync(P)) { fs.mkdirSync(".agents/skills/wix-base44-connector", { recursive: true });
9
+ // fs.writeFileSync(P, await (await fetch("https://www.wix.com/skills/wix-base44-connector/scripts/utils.js")).text()); }
10
+ // const wx = (() => { const m = { exports: {} };
11
+ // new Function("module", "exports", "require", fs.readFileSync(P, "utf8"))(m, m.exports, require); return m.exports; })();
12
+ //
13
+ // Read a saved file the way you already know how: wx.bash("grep -n 'term' <path> | head -40")
14
+ // to find, read_file(<path>) with offset/limit to window (numbered lines, 45K cap — no exec
15
+ // round needed), pipelines for the rest (GNU grep/sed; awk is mawk; no rg).
16
+ //
17
+ // API responses are site data and stay OUT of scratch (scratch ships with the app):
18
+ // post() never saves — project responses to facts.
19
+
20
+ const fs = require("fs");
21
+ const path = require("path");
22
+
23
+ const BUDGET = 4000;
24
+ const SCRATCH = ".agents/skills/wix-base44-connector/scratch";
25
+
26
+ const clip = (out) => {
27
+ if (typeof out === "string")
28
+ return out.length <= BUDGET ? out : { truncated: true, total: out.length, head: out.slice(0, BUDGET) };
29
+ // absence must be visible: JSON.stringify silently ERASES undefined keys, so a probe like
30
+ // { lineItems: resp.cart?.lineItems } loses the very field that proves the call failed —
31
+ // render undefined as null instead
32
+ const s = JSON.stringify(out, (k, v) => v === undefined ? null : v);
33
+ return s.length <= BUDGET ? JSON.parse(s) : { truncated: true, total: s.length, head: s.slice(0, BUDGET) };
34
+ };
35
+
36
+ // One transport for every JSON call — Content-Type, optional Bearer, ok-guard.
37
+ // Admin calls are this with the connector token: post(publicUrl, body, accessToken).
38
+ async function post(url, body, token) {
39
+ const r = await fetch(url, { method: "POST", body: JSON.stringify(body),
40
+ headers: { "Content-Type": "application/json", ...(token && { Authorization: `Bearer ${token}` }) } });
41
+ if (!r.ok) {
42
+ 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
+ : ""));
46
+ }
47
+ return r.json();
48
+ }
49
+
50
+ function save(name, text) {
51
+ const dir = path.join(process.cwd(), SCRATCH);
52
+ fs.mkdirSync(dir, { recursive: true });
53
+ fs.writeFileSync(path.join(dir, name), text);
54
+ return { path: SCRATCH + "/" + name, bytes: Buffer.byteLength(text), lines: text.split("\n").length };
55
+ }
56
+
57
+ const outlineOf = (lines, cap = 30) => {
58
+ const heads = [];
59
+ lines.forEach((t, i) => { if (/^#{1,3} /.test(t)) heads.push({ line: i + 1, text: t.trim().slice(0, 80) }); });
60
+ return { outline: heads.slice(0, cap), outlineOmitted: Math.max(0, heads.length - cap) };
61
+ };
62
+
63
+ // A ref is a scratch path or the URL it came from — resolve to lines, fetching+saving
64
+ // URLs on first touch (the .md suffix is appended for extensionless docs URLs).
65
+ const slugOf = (url) => url.replace(/\?.*$/, "").split("/").pop().replace(/\.md$/, "") + ".md";
66
+ async function resolveRef(ref) {
67
+ const isUrl = /^https?:/.test(ref);
68
+ const name = isUrl ? slugOf(ref) : ref.split("/").pop();
69
+ const file = path.join(process.cwd(), SCRATCH, name);
70
+ if (fs.existsSync(file)) return { path: SCRATCH + "/" + name, lines: fs.readFileSync(file, "utf8").split("\n") };
71
+ if (!isUrl) throw new Error("no such scratch file: " + ref + " — pass a returned path or the source URL");
72
+ const mdUrl = /\.[a-z]{2,5}$/.test(ref.replace(/\?.*$/, "")) ? ref : ref.replace(/\?.*$/, "") + ".md";
73
+ const res = await fetch(mdUrl);
74
+ if (!res.ok) throw new Error(res.status + " on " + mdUrl + " — not a docs page; take URLs from output, don't compose");
75
+ const text = await res.text();
76
+ const saved = save(name, text);
77
+ return { path: saved.path, lines: text.split("\n") };
78
+ }
79
+
80
+ // ── gather context ────────────────────────────────────────────────────────────
81
+
82
+ // The dynamic context report — site data, never saved. No section → the whole report
83
+ // when it fits, else its header outline; with one → that section's text. Empty
84
+ // report = bad token, never an empty site.
85
+ async function context(token, section) {
86
+ const { markdown } = await post(
87
+ "https://www.wixapis.com/_api/dynamic-context/v1/dynamic-context/markdown", {}, token);
88
+ if (!section) {
89
+ if (markdown.length <= BUDGET) return markdown; // most sites: the whole report, one round
90
+ return { truncated: true, total: markdown.length, head: markdown.slice(0, BUDGET),
91
+ note: "site data — never saved (no path); narrow: wx.context(token, '<section name>')" };
92
+ }
93
+ const m = markdown.match(new RegExp("^#{1,3} .*" + section + "[\\s\\S]*?(?=\\n#{1,3} |$)", "im"));
94
+ return clip({ total: markdown.length, section: m ? m[0] : "not found — call context(token) for the outline" });
95
+ }
96
+
97
+ // ── find what to read ─────────────────────────────────────────────────────────
98
+
99
+ // Browse the docs tree — deterministic. menuUrl alone orients (children + counts);
100
+ // filter before listing methods. An oversized listing is saved with its outline.
101
+ async function browse(menuUrl, { include, filter, depth } = {}) {
102
+ const { content } = await post("https://www.wixapis.com/mcp-docs-search/v1/docs/menu/browse", {
103
+ menu_url: menuUrl, ...(include && { include }),
104
+ ...(filter && { name_filter: filter }), ...(depth && { depth }),
105
+ }); // 404 "No menu node found" ⇒ re-orient a level up
106
+ if (content.length <= BUDGET) return content;
107
+ const s = save("browse-" + (menuUrl.replace(/\/+$/, "").split("/").pop() || "root") + ".md", content);
108
+ return { ...s, next: `wx.bash("grep -in 'term' ${s.path} | head -40") // one line per node — or re-browse with a filter` };
109
+ }
110
+
111
+ // Semantic search — ranks, never says "no match". The reduced hits come back inline
112
+ // AND the full raw content is saved for grep/window follow-ups.
113
+ async function search(term, { type = "REST", max = 5, lines = 6 } = {}) {
114
+ const { content } = await post("https://www.wixapis.com/mcp-docs-search/v1/docs/search/markdown",
115
+ { search_term: term, document_type: type, maximum_results: max, lines_in_each_result: lines });
116
+ const hits = content.split(/\n---\n+(?=#### )/).map(b => ({
117
+ method: (b.match(/^# Method: (.+)$/m) || [])[1],
118
+ endpoint: (b.match(/^# Method API Endpoint: (.+)$/m) || [])[1], // callable
119
+ docsUrl: (b.match(/#### \[[^\]]+\]\((https:[^)]+)\)/) || [])[1],
120
+ gist: ((b.match(/## Method Description:\s*\n([\s\S]{0,400})/) || [])[1] || "")
121
+ .trim().replace(/\s+/g, " ").slice(0, 220),
122
+ })).filter(h => h.docsUrl);
123
+ const saved = save("search-" + term.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 40) + ".md", content);
124
+ if (!hits.length) return clip({ ...saved, head: content.slice(0, 1200),
125
+ note: `no method blocks parsed — raw head above; wx.bash("grep -in 'term' ${saved.path}") for the rest` });
126
+ return clip({ ...saved, hits });
127
+ }
128
+
129
+ // ── read a page (docs pages and recipes alike) ────────────────────────────────
130
+
131
+ // Fetch + save + map in one round: whole text inline when small, else
132
+ // { path, bytes, lines, outline } — the outline's line numbers feed grep and read_file windows.
133
+ async function page(url) {
134
+ const { path: p, lines } = await resolveRef(url);
135
+ const text = lines.join("\n");
136
+ if (text.length <= BUDGET) return text;
137
+ const o = outlineOf(lines);
138
+ const bytes = Buffer.byteLength(text);
139
+ return { path: p, bytes, lines: lines.length, ...o,
140
+ next: [
141
+ `wx.bash("grep -in 'term' ${p} | head -40")`,
142
+ bytes <= 45000 ? `read_file ${p} // whole (fits the 45K cap), or a window via offset/limit`
143
+ : `read_file ${p} with offset/limit // window a section by the outline's lines`,
144
+ ] };
145
+ }
146
+
147
+ // ── shell ─────────────────────────────────────────────────────────────────────
148
+
149
+ // Compose native pipelines over scratch — grep -n, sed -n, mawk, sort, uniq, wc
150
+ // (GNU grep/sed; awk is mawk; no rg). Cap your own output (| head -40); the return
151
+ // clips regardless. grep's exit 1 means no match, not failure — the return says so.
152
+ function bash(cmd) {
153
+ const { execSync } = require("child_process");
154
+ try {
155
+ const out = execSync(cmd, { timeout: 15000, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 });
156
+ return out.trim() ? clip(out)
157
+ : "(no output — a filter may have swallowed the signal; rerun without the reducer)";
158
+ } catch (e) {
159
+ const exit = e.status ?? null, err = (e.stderr || "").toString().trim();
160
+ return { exit, ...(err && { err: err.slice(0, 300) }),
161
+ out: clip((e.stdout || "").toString()),
162
+ ...(exit === 1 && !err && { note: "exit 1 with no stderr — a no-match, not a failure" }) };
163
+ }
164
+ }
165
+
166
+ // ── the spec index ────────────────────────────────────────────────────────────
167
+
168
+ // Run a query over the API spec index. In scope: lightIndex (RESOURCES with
169
+ // .methods — operationId, summary, httpMethod, path [PARTIAL — never call it],
170
+ // publicUrl [callable], docsUrl) and getResourceSchemaByUrl(docsUrl). Arrive with
171
+ // a docsUrl and match by it — also the only proof an API does NOT exist.
172
+ // A big result is saved as JSON; grep it for the keys you saw in its head.
173
+ async function spec(code) {
174
+ if (!/^\s*async function/.test(code)) code = `async function(){ ${code} }`;
175
+ const { result } = await post("https://mcp.wix.com/api/code-mode/search", { code });
176
+ if (result == null || (Array.isArray(result) && !result.length))
177
+ return { result, note: "empty — the query missed the shape, not proof of absence; find the resource by docsUrl on lightIndex" };
178
+ const text = JSON.stringify(result, null, 1);
179
+ if (text.length <= BUDGET) return result;
180
+ return { ...save("spec.json", text),
181
+ shape: Array.isArray(result) ? `Array(${result.length})` : Object.keys(result || {}).slice(0, 15),
182
+ head: text.slice(0, 600) };
183
+ }
184
+
185
+ // ── management recipes ────────────────────────────────────────────────────────
186
+
187
+ // ~100 curated multi-step admin flows. No arg → categories with counts; a category
188
+ // name → its recipes; any other term → search every recipe's name + gist for it.
189
+ // Read the chosen url with page(url), then grep/window/fields.
190
+ async function recipes(q) {
191
+ const { base, files } = await (await fetch("https://dev.wix.com/docs/skills/manage.manifest.json")).json();
192
+ const cat = f => (f.path.match(/^references\/([^/]+)\//) || [])[1];
193
+ const row = f => ({ name: f.name, cat: cat(f), gist: (f.description || "").slice(0, 120),
194
+ url: base + f.path, kb: Math.round(f.size / 1024) });
195
+ if (!q) {
196
+ const cats = {};
197
+ for (const f of files) { const c = cat(f); if (c) cats[c] = (cats[c] || 0) + 1; }
198
+ return cats;
199
+ }
200
+ const inCat = files.filter(f => cat(f) === q);
201
+ if (inCat.length) return clip(inCat.map(row));
202
+ const re = new RegExp(q, "i");
203
+ const m = files.filter(f => re.test(f.name + " " + (f.description || "")));
204
+ if (!m.length) {
205
+ const cats = {};
206
+ for (const f of files) { const c = cat(f); if (c) cats[c] = (cats[c] || 0) + 1; }
207
+ return { note: `nothing matches "${q}" — the categories:`, categories: cats };
208
+ }
209
+ return clip(m.map(row));
210
+ }
211
+
212
+ module.exports = { post, clip, context, browse, search, page, bash, spec, recipes };
@@ -95,27 +95,27 @@ Don't read these files fully. Grep for keywords, then read specific sections wit
95
95
 
96
96
  ## Quick Component Mapping (Design to WDS)
97
97
 
98
- | Design Element | WDS Component | Notes |
99
- | --- | --- | --- |
100
- | Rectangle/container | `<Box>` | Layout wrapper |
101
- | Text button | `<TextButton>` | Secondary actions |
102
- | Input with label | `<FormField>` + `<Input>` | Wrap inputs |
103
- | Toggle | `<ToggleSwitch>` | On/off settings |
104
- | Modal | `<Modal>` + `<CustomModalLayout>` | Use together |
105
- | Grid | `<Layout>` + `<Cell>` | Responsive |
98
+ | Design Element | WDS Component | Notes |
99
+ | ------------------- | --------------------------------- | ----------------- |
100
+ | Rectangle/container | `<Box>` | Layout wrapper |
101
+ | Text button | `<TextButton>` | Secondary actions |
102
+ | Input with label | `<FormField>` + `<Input>` | Wrap inputs |
103
+ | Toggle | `<ToggleSwitch>` | On/off settings |
104
+ | Modal | `<Modal>` + `<CustomModalLayout>` | Use together |
105
+ | Grid | `<Layout>` + `<Cell>` | Responsive |
106
106
 
107
107
  ## Spacing (px to SP conversion)
108
108
 
109
109
  When designer specifies pixels, convert to the nearest SP token:
110
110
 
111
111
  | Token | Classic | Studio |
112
- | --- | --- | --- |
113
- | `SP1` | 6px | 4px |
114
- | `SP2` | 12px | 8px |
115
- | `SP3` | 18px | 12px |
116
- | `SP4` | 24px | 16px |
117
- | `SP5` | 30px | 20px |
118
- | `SP6` | 36px | 24px |
112
+ | ----- | ------- | ------ |
113
+ | `SP1` | 6px | 4px |
114
+ | `SP2` | 12px | 8px |
115
+ | `SP3` | 18px | 12px |
116
+ | `SP4` | 24px | 16px |
117
+ | `SP5` | 30px | 20px |
118
+ | `SP6` | 36px | 24px |
119
119
 
120
120
  ```tsx
121
121
  <Box gap="SP2" padding="SP3">
@@ -130,6 +130,10 @@ import { Button, Card, Image } from "@wix/design-system";
130
130
  import { Add, Edit, Delete } from "@wix/wix-ui-icons-common";
131
131
  ```
132
132
 
133
+ ## GUIDELINES
134
+
135
+ 1. Don't ever override WDS CSS tokens. Only if it's not possible otherwise, mention that very clearly to the prompter, that this is an anti-pattern and should be avoided at all costs.
136
+
133
137
  ### Troubleshooting
134
138
 
135
139
  - **Components render unstyled** (plain HTML look, missing WDS spacing/typography): add `import "@wix/design-system/styles.global.css";` once to the root/main component (e.g. `page.tsx`, modal entry) — not child/tab/helper files.