@wix/agent-skills 1.16.1 → 1.16.3
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 +72 -8
- package/skills/wix-app/references/CODE_QUALITY.md +0 -1
- package/skills/wix-app/references/DASHBOARD_MODAL.md +21 -14
- package/skills/wix-app/references/DASHBOARD_PAGE.md +30 -184
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +14 -6
- package/skills/wix-app/references/EMBEDDED_SCRIPT.md +6 -0
- package/skills/wix-app/references/WIX_PATTERNS_DOCS.md +172 -0
- package/skills/wix-app/references/dashboard-page/WDS_LAYOUT.md +187 -0
- package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +32 -2
- package/skills/wix-app/references/editor-react-component/COMPONENT-API.md +38 -1
- package/skills/wix-app/references/editor-react-component/DESIGN-STATES.md +27 -4
- package/skills/wix-app/references/editor-react-component/FUNCTION-HANDLERS.md +167 -0
- package/skills/wix-app/references/editor-react-component/MANIFEST-ERRORS.md +19 -0
- package/skills/wix-app/references/editor-react-component/PARTS.md +1 -2
- package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +2 -2
- package/skills/wix-base44-connector/SKILL.md +185 -0
- package/skills/wix-base44-connector/scripts/utils.js +212 -0
- package/skills/wix-design-system/SKILL.md +19 -15
- package/skills/wix-docs/SKILL.md +75 -1
- package/skills/wix-manage/SKILL.md +16 -18
- package/skills/wix-manage/references/bookings/create-class-service.md +1 -1
- package/skills/wix-manage/references/domains/domain-search-purchase-and-connect.md +2603 -0
- package/skills/wix-manage/references/events/create-wix-event.md +112 -90
- package/skills/wix-manage/references/events/manage-wix-events.md +33 -94
- package/skills/wix-manage/references/rich-content/author-ricos-rich-content.md +182 -5
- package/skills/wix-manage/references/sites/create-headless-site.md +1 -0
- package/skills/wix-manage/references/sites/manage-oauth-apps.md +154 -0
- package/skills/wix-manage/references/sites/read-site-context.md +125 -0
- package/skills/wix-manage/references/stores/create-product-catalog-v3.md +273 -0
- package/skills/wix-vibe-headless/install/deploy.cjs +1 -2
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +104 -0
- package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +74 -0
- package/skills/wix-vibe-headless/platforms/base44.md +3 -3
- package/skills/wix-vibe-headless/references/blog/seed/SEED.md +1 -1
- package/skills/wix-vibe-headless/references/bookings/seed/SEED.md +1 -1
- package/skills/wix-vibe-headless/references/events/seed/SEED.md +1 -1
- package/skills/wix-vibe-headless/references/portfolio/seed/SEED.md +1 -1
- package/skills/wix-vibe-headless/references/pricing-plans/seed/SEED.md +1 -1
- package/skills/wix-vibe-headless/references/restaurants/seed/SEED.md +1 -1
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +68 -2
- package/skills/wix-vibe-headless/references/storefront/app/components/ProductCard.jsx +29 -86
- package/skills/wix-vibe-headless/references/storefront/app/components/ProductGrid.jsx +3 -8
- package/skills/wix-vibe-headless/references/storefront/app/components/VariantPicker.jsx +78 -105
- package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductCard.js +92 -0
- package/skills/wix-vibe-headless/references/storefront/app/hooks/useVariantOptions.js +76 -0
- package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +1 -1
- package/skills/wix-app/references/AUTO_PATTERNS_DASHBOARD.md +0 -326
- package/skills/wix-app/references/auto-patterns-dashboard/action-cell.md +0 -115
- package/skills/wix-app/references/auto-patterns-dashboard/app-config-structure.md +0 -264
- package/skills/wix-app/references/auto-patterns-dashboard/app-context.md +0 -37
- package/skills/wix-app/references/auto-patterns-dashboard/bulk-actions.md +0 -86
- package/skills/wix-app/references/auto-patterns-dashboard/collection-page-actions.md +0 -119
- package/skills/wix-app/references/auto-patterns-dashboard/collection-page.md +0 -103
- package/skills/wix-app/references/auto-patterns-dashboard/custom-actions-override.md +0 -76
- package/skills/wix-app/references/auto-patterns-dashboard/custom-columns-override.md +0 -82
- package/skills/wix-app/references/auto-patterns-dashboard/custom-components-override.md +0 -89
- package/skills/wix-app/references/auto-patterns-dashboard/custom-header-override.md +0 -124
- package/skills/wix-app/references/auto-patterns-dashboard/custom-sections-override.md +0 -91
- package/skills/wix-app/references/auto-patterns-dashboard/custom-slots-override.md +0 -71
- package/skills/wix-app/references/auto-patterns-dashboard/entity-page-actions.md +0 -70
- package/skills/wix-app/references/auto-patterns-dashboard/entity-page-view-actions.md +0 -73
- package/skills/wix-app/references/auto-patterns-dashboard/entity-page.md +0 -98
- package/skills/wix-app/references/auto-patterns-dashboard/example-patterns.json +0 -278
- package/skills/wix-app/references/auto-patterns-dashboard/pages-configuration.md +0 -94
- package/skills/wix-app/references/auto-patterns-dashboard/resolved-action.md +0 -43
- package/skills/wix-app/references/auto-patterns-dashboard/sdk-utilities.md +0 -66
- package/skills/wix-app/references/auto-patterns-dashboard/views.md +0 -76
- package/skills/wix-app/scripts/generate-auto-patterns.js +0 -510
- package/skills/wix-manage/references/domains/domain-search-and-purchase.md +0 -437
- package/skills/wix-manage/references/stores/create-product-from-image.md +0 -1031
- package/skills/wix-manage/references/stores/create-product-with-options-catalog-v3.md +0 -623
- package/skills/wix-manage/references/stores/setup-online-store-catalog-v3.md +0 -106
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Function Event Handlers
|
|
2
|
+
|
|
3
|
+
Rules and patterns for adding function event handler props to an Editor React component's TypeScript interface, wiring them to DOM events, and declaring custom callbacks.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Rules
|
|
8
|
+
|
|
9
|
+
### Standard SDK Event Handlers
|
|
10
|
+
|
|
11
|
+
These are the standard event handler props the editor SDK exposes to site owners. Use the exact prop names — ZeroConfig and the manifest system recognize them.
|
|
12
|
+
|
|
13
|
+
| Prop name | React DOM event | Type |
|
|
14
|
+
| ------------ | --------------- | ------------------------------------------------ |
|
|
15
|
+
| `onClick` | `onClick` | `(event: React.MouseEvent) => void` |
|
|
16
|
+
| `onDblClick` | `onDoubleClick` | `(event: React.MouseEvent) => void` |
|
|
17
|
+
| `onChange` | `onChange` | `(event: React.ChangeEvent<HTMLElement>) => void` |
|
|
18
|
+
| `onFocus` | `onFocus` | `(event: React.FocusEvent) => void` |
|
|
19
|
+
| `onBlur` | `onBlur` | `(event: React.FocusEvent) => void` |
|
|
20
|
+
| `onMouseIn` | `onMouseEnter` | `(event: React.MouseEvent) => void` |
|
|
21
|
+
| `onMouseOut` | `onMouseLeave` | `(event: React.MouseEvent) => void` |
|
|
22
|
+
|
|
23
|
+
Three props have a **name mismatch** between the SDK prop and the DOM event: `onDblClick` → `onDoubleClick`, `onMouseIn` → `onMouseEnter`, `onMouseOut` → `onMouseLeave`.
|
|
24
|
+
|
|
25
|
+
### Declaring Standard Handler Props
|
|
26
|
+
|
|
27
|
+
Declare standard handlers inline with the exact SDK prop names and React event types:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
interface MyComponentProps {
|
|
31
|
+
label?: string;
|
|
32
|
+
onClick?: (event: React.MouseEvent) => void;
|
|
33
|
+
onDblClick?: (event: React.MouseEvent) => void;
|
|
34
|
+
onChange?: (event: React.ChangeEvent<HTMLElement>) => void;
|
|
35
|
+
onFocus?: (event: React.FocusEvent) => void;
|
|
36
|
+
onBlur?: (event: React.FocusEvent) => void;
|
|
37
|
+
onMouseIn?: (event: React.MouseEvent) => void;
|
|
38
|
+
onMouseOut?: (event: React.MouseEvent) => void;
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Only declare handlers the component actually exposes. Do not add all seven if the component only handles two.
|
|
43
|
+
|
|
44
|
+
### Wiring Standard Handlers to DOM Events
|
|
45
|
+
|
|
46
|
+
Destructure the props and pass them to the correct DOM event — applying the name mappings:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
export const MyComponent = (props: MyComponentProps) => {
|
|
50
|
+
const { onClick, onDblClick, onFocus, onBlur, onMouseIn, onMouseOut } = props;
|
|
51
|
+
return (
|
|
52
|
+
<div
|
|
53
|
+
onClick={onClick}
|
|
54
|
+
onDoubleClick={onDblClick}
|
|
55
|
+
onFocus={onFocus}
|
|
56
|
+
onBlur={onBlur}
|
|
57
|
+
onMouseEnter={onMouseIn}
|
|
58
|
+
onMouseLeave={onMouseOut}
|
|
59
|
+
>
|
|
60
|
+
{/* ... */}
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Custom Callbacks
|
|
67
|
+
|
|
68
|
+
Component-specific callbacks that do not correspond to a DOM event (e.g., `onPlay`, `onPause`, `onEnded`, `onTimeupdate`) are **not** standard SDK event handlers. Type them as `() => void` — no React event parameter:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
interface VideoPlayerProps {
|
|
72
|
+
onPlay?: () => void;
|
|
73
|
+
onPause?: () => void;
|
|
74
|
+
onEnded?: () => void;
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Pass them directly to the underlying element — no name remapping needed:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
export const VideoPlayer = (props: VideoPlayerProps) => {
|
|
82
|
+
const { onPlay, onPause, onEnded } = props;
|
|
83
|
+
return <video onPlay={onPlay} onPause={onPause} onEnded={onEnded} />;
|
|
84
|
+
};
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Common Mistakes
|
|
90
|
+
|
|
91
|
+
### Passing SDK prop name directly to DOM instead of remapping
|
|
92
|
+
|
|
93
|
+
**❌ Wrong:**
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
export const MyComponent = (props: MyComponentProps) => {
|
|
97
|
+
const { onMouseIn, onMouseOut, onDblClick } = props;
|
|
98
|
+
return (
|
|
99
|
+
<div
|
|
100
|
+
onMouseIn={onMouseIn} // ❌ not a valid React DOM prop
|
|
101
|
+
onMouseOut={onMouseOut} // ❌ not a valid React DOM prop
|
|
102
|
+
onDblClick={onDblClick} // ❌ not a valid React DOM prop
|
|
103
|
+
/>
|
|
104
|
+
);
|
|
105
|
+
};
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**✅ Correct:**
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
export const MyComponent = (props: MyComponentProps) => {
|
|
112
|
+
const { onMouseIn, onMouseOut, onDblClick } = props;
|
|
113
|
+
return (
|
|
114
|
+
<div
|
|
115
|
+
onMouseEnter={onMouseIn} // ✅ onMouseIn → onMouseEnter
|
|
116
|
+
onMouseLeave={onMouseOut} // ✅ onMouseOut → onMouseLeave
|
|
117
|
+
onDoubleClick={onDblClick} // ✅ onDblClick → onDoubleClick
|
|
118
|
+
/>
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Adding a React event parameter to custom callbacks
|
|
124
|
+
|
|
125
|
+
**❌ Wrong:**
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
interface VideoPlayerProps {
|
|
129
|
+
onPlay?: (event: React.SyntheticEvent) => void; // ❌ event param on custom callback
|
|
130
|
+
onEnded?: (event: React.SyntheticEvent) => void; // ❌ event param on custom callback
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**✅ Correct:**
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
interface VideoPlayerProps {
|
|
138
|
+
onPlay?: () => void; // ✅ no event param
|
|
139
|
+
onEnded?: () => void; // ✅ no event param
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Why:** Custom callbacks are component-level notifications to the site owner, not DOM event forwarders. They carry no event object — the SDK passes no event to them.
|
|
144
|
+
|
|
145
|
+
### Using the DOM event name as the SDK prop name
|
|
146
|
+
|
|
147
|
+
**❌ Wrong:**
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
interface MyComponentProps {
|
|
151
|
+
onDoubleClick?: (event: React.MouseEvent) => void; // ❌ DOM name, not SDK name
|
|
152
|
+
onMouseEnter?: (event: React.MouseEvent) => void; // ❌ DOM name, not SDK name
|
|
153
|
+
onMouseLeave?: (event: React.MouseEvent) => void; // ❌ DOM name, not SDK name
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**✅ Correct:**
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
interface MyComponentProps {
|
|
161
|
+
onDblClick?: (event: React.MouseEvent) => void; // ✅ SDK prop name
|
|
162
|
+
onMouseIn?: (event: React.MouseEvent) => void; // ✅ SDK prop name
|
|
163
|
+
onMouseOut?: (event: React.MouseEvent) => void; // ✅ SDK prop name
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Why:** The SDK prop names (`onDblClick`, `onMouseIn`, `onMouseOut`) are what ZeroConfig and the manifest system register. Using DOM names breaks the editor's event wiring.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Manifest Build Errors
|
|
2
|
+
|
|
3
|
+
The error name is always the first token before the colon (`IoError:`, `ParseError:`, `NotFoundError:`). Find the matching row, apply the fix, then re-run both commands.
|
|
4
|
+
|
|
5
|
+
| Error | Fix |
|
|
6
|
+
|---|---|
|
|
7
|
+
| `IoError: Failed to load compiled bundle` | Re-run `npx wix build`; fix any TypeScript/build errors it reports first |
|
|
8
|
+
| `IoError: Failed to access component "X" from compiled bundle` | Corrupted artifact — delete `dist/`, re-run `npx wix build` from scratch |
|
|
9
|
+
| `IoError: CSS selector matching failed for "X"` | Fix invalid selector syntax in `<componentName>.module.css` |
|
|
10
|
+
| `IoError: Component "X" render produced no root element` | Component returns null or bare text — ensure it always renders a root JSX element |
|
|
11
|
+
| `IoError` (render phase, no other pattern) | SSR crash — remove browser-only APIs (`window`, `document`, `localStorage`) used at render time; ensure props have defaults |
|
|
12
|
+
| `NotFoundError: File not found` | Source `.tsx` missing — restore it or re-scaffold with `npx wix generate` |
|
|
13
|
+
| `NotFoundError: Component "X" was not found as a named export` | Confirm `component.tsx` has a default export and re-run `npx wix build` |
|
|
14
|
+
| `ParseError: Failed to parse TypeScript config` | Fix `tsconfig.json`; run `npx tsc --noEmit` to surface the error |
|
|
15
|
+
| `ParseError: Failed to extract component types from "X"` | TypeScript error in the component — run `npx tsc --noEmit` and fix |
|
|
16
|
+
| `ParseError: Failed to compile "<file.scss>"` | SCSS syntax error — line/column is in the error message |
|
|
17
|
+
| `ParseError: Failed to parse CSS file for component "X"` | CSS syntax error in `.module.css` — line/column is in the error message |
|
|
18
|
+
|
|
19
|
+
After each fix, re-run `npx wix build && npx wix generate manifest` to confirm.
|
|
@@ -23,7 +23,7 @@ An element deserves its own named part if a site owner would plausibly need to i
|
|
|
23
23
|
- ❌ **State or variant** (`active`, `selected`, `current`, `disabled`, `open`, `checked`) — not a part, it's a **design state** of a part. Implement it per [`DESIGN-STATES.md`](DESIGN-STATES.md): native states from interactive markup; custom states as a flat global modifier on the element's own (component-name-prefixed) class (e.g. `carousel-slide--active`, toggled in JSX) plus a `:global(.carousel-slide--active)` CSS rule. The modifier is a state, not a named part.
|
|
24
24
|
- ❌ **Hidden/shown state of an existing part** — the hidden and shown states of a part are not separate parts. The element itself should be a named part (so the editor can control its visibility per breakpoint); creating an additional part to represent its hidden or shown variant is wrong.
|
|
25
25
|
- ❌ **Pure grouping wrapper** — not a part. A `<div>` whose only role is to hold already-named siblings has no independent editor surface; let layout live in the parent's CSS.
|
|
26
|
-
- ❌ **Child with no independent editor surface** — not a part if its styling and data are already fully owned by the parent. This applies regardless of how many siblings it has. Two ways a child's editor surface can already be covered by the parent: (1) its CSS properties are inherited or set via the parent's rule; (2) its data is defined on the parent. **Canonical example:** an `<img>` inside a carousel slide whose `src` and `alt` come from the parent slide's data, and whose visual properties (object-fit, border-radius) could equally be set on the slide's own CSS rule — the slide already owns both data and styling, so the `<img>` is not a named part. Use a CSS module class for any structural CSS it needs. **Exception — interactive HTML elements (`<button>`, `<a>`, `<input>`) are never excluded by this criterion.** They always carry an independent styling surface: background, border, border-radius,
|
|
26
|
+
- ❌ **Child with no independent editor surface** — not a part if its styling and data are already fully owned by the parent. This applies regardless of how many siblings it has. Two ways a child's editor surface can already be covered by the parent: (1) its CSS properties are inherited or set via the parent's rule; (2) its data is defined on the parent. **Canonical example:** an `<img>` inside a carousel slide whose `src` and `alt` come from the parent slide's data, and whose visual properties (object-fit, border-radius) could equally be set on the slide's own CSS rule — the slide already owns both data and styling, so the `<img>` is not a named part. Use a CSS module class for any structural CSS it needs. **Exception — interactive HTML elements (`<button>`, `<a>`, `<input>`) are never excluded by this criterion.** They always carry an independent styling surface: background, border, border-radius, interaction states, color, and typography are all meaningfully tunable per element in the editor. Apply this exclusion only to purely static structural elements (layout wrappers, grouping containers, decorative dividers). This makes an interactive element a named part **when it is an inner element** — if it is the elected root (Step 0), it is already named by `'<component-name>'` and must not be given a prefixed inner-part class as well.
|
|
27
27
|
- ❌ **Positional duplicate** — not separate parts. Elements that are semantically identical and differ only in position (e.g. prev/next buttons) are one part; differentiate position with CSS (`:first-of-type` / `:last-of-type`, `data-` attribute, or `:nth-child`).
|
|
28
28
|
- ✅ **Passes all checks** — a named part. Classify as **Semantic** (needs `elementProps`: data, behavior, direction, event handlers) or **Styling-only** (CSS class is sufficient).
|
|
29
29
|
|
|
@@ -36,4 +36,3 @@ Before finalising, verify each proposed part against its parent:
|
|
|
36
36
|
If yes — the part adds no independent editor surface and should be removed. This catches rationalisation after-the-fact ("the parent *could* expose this CSS property too, but so could the child separately"). When in doubt, fewer parts is usually better.
|
|
37
37
|
|
|
38
38
|
Then run the same question in reverse, on the root: if the root has exactly one child part and no editor surface of its own, it is a plumbing wrapper — delete it and promote the child per Step 0.
|
|
39
|
-
|
|
@@ -9,6 +9,7 @@ This guide defines rules and guidance on **how to implement** production-quality
|
|
|
9
9
|
- [`DIRECTIONALITY.md`](DIRECTIONALITY.md) — RTL/LTR rules and patterns
|
|
10
10
|
- [`PROPS-VS-CSS.md`](PROPS-VS-CSS.md) — What should be a React prop vs CSS
|
|
11
11
|
- [`COMPONENT-API.md`](COMPONENT-API.md) — Props structure, elementProps, data types, file splitting, containers, array props
|
|
12
|
+
- [`FUNCTION-HANDLERS.md`](FUNCTION-HANDLERS.md) — Standard SDK event handler props, DOM wiring, custom callbacks
|
|
12
13
|
- [`ANIMATED-COMPONENTS.md`](ANIMATED-COMPONENTS.md) — Play/pause control and autoplay for animated/playable components
|
|
13
14
|
- [`REACT-PATTERNS.md`](REACT-PATTERNS.md) — SSR-safe patterns, CSS rules, common mistakes
|
|
14
15
|
|
|
@@ -110,7 +111,7 @@ Unless explicitly specified as a component capability/API in the specification,
|
|
|
110
111
|
**External handlers (only when specified):**
|
|
111
112
|
|
|
112
113
|
- Only expose handlers that are explicitly listed as component capabilities
|
|
113
|
-
-
|
|
114
|
+
- Use the standard SDK prop names and DOM wiring rules from [`FUNCTION-HANDLERS.md`](FUNCTION-HANDLERS.md)
|
|
114
115
|
|
|
115
116
|
**Child component handlers:**
|
|
116
117
|
|
|
@@ -170,4 +171,3 @@ See [`PROPS-VS-CSS.md`](PROPS-VS-CSS.md) and [`COMPONENT-API.md`](COMPONENT-API.
|
|
|
170
171
|
**Phase 2: Component File** — Apply all §1.1 mandatory features and §1.2 implementation standards; toggle custom-state classes from data ([`DESIGN-STATES.md`](DESIGN-STATES.md))
|
|
171
172
|
|
|
172
173
|
**Phase 3: Styles** — Apply Part 2 SCSS rules, including state styles ([`DESIGN-STATES.md`](DESIGN-STATES.md)). If the request uses the words **branded**, **themed**, or **brand-aware**, also apply [`BRANDED-COMPONENTS.md`](BRANDED-COMPONENTS.md) before writing CSS.
|
|
173
|
-
|
|
@@ -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 };
|