@wix/agent-skills 1.23.0 → 1.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/package.json +2 -1
- package/skills/wix-app/SKILL.md +4 -1
- package/skills/wix-app/references/BUSINESS_MANAGER_THEME.md +108 -0
- package/skills/wix-app/references/BUSINESS_MANAGER_TOKENS.md +81 -0
- package/skills/wix-app/references/DASHBOARD_MODAL.md +41 -0
- package/skills/wix-app/references/DASHBOARD_PAGE.md +2 -0
- package/skills/wix-app/references/DASHBOARD_PLUGIN.md +19 -4
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +64 -50
- package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE.md +5 -4
- package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_ROUTER.md +8 -7
- package/skills/wix-app/references/dashboard-page/DYNAMIC_PARAMETERS.md +21 -38
- package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +4 -3
- package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +28 -25
- package/skills/wix-app/references/editor-react-component/BRANDED-COMPONENTS.md +8 -2
- package/skills/wix-app/references/editor-react-component/COMPONENT-CONTRACT.md +12 -14
- package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +4 -3
- package/skills/wix-app/references/editor-react-component/DESIGN-STATES.md +66 -9
- package/skills/wix-app/references/editor-react-component/EDITOR-EXTENSION-CONFIGURATION.md +25 -0
- package/skills/wix-app/references/editor-react-component/OVERFLOW.md +146 -0
- package/skills/wix-app/references/editor-react-component/PARTS.md +6 -0
- package/skills/wix-app/scripts/scan-a11y-eslint.cjs +25 -1
- package/skills/wix-headless/SKILL.md +0 -1
- package/skills/wix-headless/references/CAPABILITIES.md +3 -3
- package/skills/wix-headless/references/DISCOVERY.md +1 -1
- package/skills/wix-headless/references/SDK_HANDOFF.md +1 -1
- package/skills/wix-headless/references/SEED.md +1 -1
- package/skills/wix-headless/references/SETUP.md +1 -1
- package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-reservations.md +1 -1
- package/skills/wix-headless/references/inline-recipes/setup-restaurant-experiences.md +2 -2
- package/skills/wix-headless/references/managed/AUTHENTICATION.md +3 -14
- package/skills/wix-headless/scripts/bootstrap.mjs +156 -0
- package/skills/{wix-headless-fast → wix-headless-kit}/SKILL.md +83 -64
- package/skills/{wix-headless-fast → wix-headless-kit}/cold-start/bootstrap.mjs +2 -2
- package/skills/{wix-headless-fast → wix-headless-kit}/cold-start/cold-start.md +26 -35
- package/skills/wix-headless-kit/guides/capabilities.md +76 -0
- package/skills/wix-headless-kit/guides/feedback.md +98 -0
- package/skills/{wix-headless-fast → wix-headless-kit}/guides/migration.md +20 -9
- package/skills/{wix-headless-fast → wix-headless-kit}/install/AGENTS.md +5 -2
- package/skills/{wix-headless-fast → wix-headless-kit}/install/attach.mjs +5 -2
- package/skills/wix-headless-kit/install/bootstrap.mjs +156 -0
- package/skills/{wix-headless-fast → wix-headless-kit}/install/check.mjs +2 -2
- package/skills/wix-headless-kit/install/context.mjs +169 -0
- package/skills/{wix-headless-fast → wix-headless-kit}/install/deploy.mjs +17 -4
- package/skills/{wix-headless-fast → wix-headless-kit}/install/setup.mjs +89 -55
- package/skills/{wix-headless-fast → wix-headless-kit}/install/templates.mjs +1 -1
- package/skills/wix-manage/SKILL.md +2 -8
- package/skills/wix-manage/references/cms/cms-dashboard-navigation.md +2 -2
- package/skills/wix-manage/references/cms/cms-data-items-crud.md +263 -31
- package/skills/wix-manage/references/cms/cms-publishing-flow.md +15 -15
- package/skills/wix-manage/references/cms/cms-schema-management.md +58 -7
- package/skills/wix-manage/references/sites/query-sites.md +1 -8
- package/skills/wix-auth/SKILL.md +0 -12
- package/skills/wix-auth/references/device-flow.md +0 -140
- package/skills/wix-headless-fast/install/context.mjs +0 -126
- package/skills/wix-manage/references/cms/cms-data-operations-extended.md +0 -263
- package/skills/wix-manage/references/cms/cms-references-and-relationships.md +0 -162
- /package/skills/{wix-headless-fast → wix-headless-kit}/guides/existing-site.md +0 -0
- /package/skills/{wix-headless-fast → wix-headless-kit}/guides/reference-mode.md +0 -0
- /package/skills/{wix-headless-fast → wix-headless-kit}/install/agents-md.mjs +0 -0
- /package/skills/{wix-headless-fast → wix-headless-kit}/install/lock.mjs +0 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Overflow and Resizing
|
|
2
|
+
|
|
3
|
+
Apply when creating a component or changing its layout, content, or sizing.
|
|
4
|
+
Custom components own their internal layout; do not assume the editor exposes
|
|
5
|
+
native-container overflow settings for their DOM. The component must keep
|
|
6
|
+
content and controls usable within its assigned size.
|
|
7
|
+
|
|
8
|
+
## Choose What Happens When Content Does Not Fit
|
|
9
|
+
|
|
10
|
+
- **Content-height component:** reflow at narrower widths and let normal-flow
|
|
11
|
+
content establish height. Do not constrain it with `height: 100%`, a fixed
|
|
12
|
+
height, or absolute positioning of all content.
|
|
13
|
+
- **Bounded-height component:** propagate the height constraint through growing
|
|
14
|
+
wrappers (`min-height: 0` for flex/grid items). Fit the content or give the
|
|
15
|
+
intended content region `overflow: auto`; keep essential controls reachable.
|
|
16
|
+
Choose scrolling only when it suits the requested interaction.
|
|
17
|
+
- **Carousel or other intentional overflow:** contain the oversized track in
|
|
18
|
+
its own viewport and provide working navigation/scrolling to every item.
|
|
19
|
+
Treat a requested count such as "show four items at a time" as the
|
|
20
|
+
default/initial-width presentation unless the user explicitly requires that
|
|
21
|
+
column count at every width. Each carousel page should still contain that
|
|
22
|
+
requested group at narrow widths, but its internal layout must reflow so every
|
|
23
|
+
required item is usable. Stacking or wrapping the current page's items
|
|
24
|
+
preserves the group; squeezing them into tiny columns or clipping later items
|
|
25
|
+
does not. Choose the installation width, gaps, padding, and usable item
|
|
26
|
+
minimum together so the requested count actually fits at the initial size.
|
|
27
|
+
|
|
28
|
+
A container-driven group page can preserve the requested grouping without
|
|
29
|
+
changing the carousel's item-count logic:
|
|
30
|
+
|
|
31
|
+
```css
|
|
32
|
+
.trackViewport {
|
|
33
|
+
min-width: 0;
|
|
34
|
+
overflow: hidden;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.page {
|
|
38
|
+
box-sizing: border-box;
|
|
39
|
+
display: grid;
|
|
40
|
+
flex: 0 0 100%;
|
|
41
|
+
min-width: 0;
|
|
42
|
+
grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr));
|
|
43
|
+
gap: 16px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.item {
|
|
47
|
+
min-width: 0;
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
At a wide initial width, the page uses the requested presentation count. When
|
|
52
|
+
the component is narrower than a usable item minimum, the same group wraps to
|
|
53
|
+
fewer columns or a single column and its content-height component grows.
|
|
54
|
+
Equivalent container-query or wrapping layouts are valid. A fixed
|
|
55
|
+
`repeat(<count>, minmax(0, 1fr))` or percentage basis derived from the count is
|
|
56
|
+
not sufficient as the only layout for a horizontally resizable component.
|
|
57
|
+
|
|
58
|
+
Do not apply blanket `overflow: hidden`/`clip` to the root or page as a repair.
|
|
59
|
+
It can hide text, focus indicators, navigation, and overlays while leaving the
|
|
60
|
+
layout defect intact. Clip a dedicated media/track/decorative region only when
|
|
61
|
+
that is its intended behavior. Preserve deliberate shadows and overlays.
|
|
62
|
+
|
|
63
|
+
## Remove Intrinsic Sizing Traps
|
|
64
|
+
|
|
65
|
+
`width: 100%` and `box-sizing: border-box` on the root are necessary, but do not
|
|
66
|
+
constrain descendants. A bare `1fr` track has an automatic minimum; three such
|
|
67
|
+
tracks can exceed the container even when the grid itself is correctly sized.
|
|
68
|
+
|
|
69
|
+
- Use `min-width: 0` on flex/grid items that must shrink, including nested
|
|
70
|
+
content wrappers. For a fixed column count, use `minmax(0, 1fr)`; still adapt
|
|
71
|
+
the layout when columns would become too narrow to use.
|
|
72
|
+
- Prefer fewer columns or wrapping to squeezing text and controls. An intrinsic
|
|
73
|
+
collection can use the following pattern; the minimum never exceeds its
|
|
74
|
+
container's content width:
|
|
75
|
+
|
|
76
|
+
```css
|
|
77
|
+
.grid {
|
|
78
|
+
box-sizing: border-box;
|
|
79
|
+
display: grid;
|
|
80
|
+
min-width: 0;
|
|
81
|
+
grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
|
|
82
|
+
gap: 16px;
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`minmax(280px, 1fr)` alone still overflows a slot narrower than 280px.
|
|
87
|
+
This grid is suitable for a wrapping collection, not a substitute for a
|
|
88
|
+
carousel's item/navigation contract.
|
|
89
|
+
- For user-authored prose, allow wrapping and use `overflow-wrap: anywhere`
|
|
90
|
+
where long words/URLs could force intrinsic width. Use truncation only when
|
|
91
|
+
the product intends it and essential content remains available.
|
|
92
|
+
- Constrain images/media to the space available; preserve aspect ratio or use
|
|
93
|
+
an intentional fitted media frame. Check fixed-size media plus adjacent text,
|
|
94
|
+
padding, borders, and gaps together.
|
|
95
|
+
- Let control rows and pagination adapt without losing buttons, focus rings,
|
|
96
|
+
or usable target sizes. `min-width: 0` does not make fixed children or large
|
|
97
|
+
padding fit by itself.
|
|
98
|
+
|
|
99
|
+
Adapt to the component's width, including a narrow slot on a desktop viewport.
|
|
100
|
+
Do not derive the layout from `window.innerWidth` or viewport breakpoints. If
|
|
101
|
+
container queries are needed, establish the query container on an appropriate
|
|
102
|
+
ancestor; a query cannot restyle its own container based on that container's
|
|
103
|
+
size. Do not add size containment on an auto-height component that prevents
|
|
104
|
+
content from establishing its height.
|
|
105
|
+
|
|
106
|
+
## Review Before Reporting Completion
|
|
107
|
+
|
|
108
|
+
First inspect the sizing chain in source. Identify the root, growing wrappers,
|
|
109
|
+
tracks, cards, text, media, and controls; check each constrained axis. Do not
|
|
110
|
+
infer safety from a CSS keyword or a successful build.
|
|
111
|
+
|
|
112
|
+
When a runnable browser preview is available, exercise the actual component:
|
|
113
|
+
|
|
114
|
+
1. Vary the **component container** independently of the page viewport. Cover
|
|
115
|
+
its initial size, 320px, a narrower stress probe such as 240px, and a wider
|
|
116
|
+
slot. For a vertically resizable component, also test a short bounded height;
|
|
117
|
+
for content height, verify the root grows without overlapping the next block.
|
|
118
|
+
A horizontally resizable top-level component must keep required content
|
|
119
|
+
usable at 320px unless an actual supported editor minimum is part of its
|
|
120
|
+
contract. Treat 240px as a stress probe. Report any unsupported size and its
|
|
121
|
+
remaining usability limit.
|
|
122
|
+
2. Use long prose and an unbroken token, larger text, changed padding/borders,
|
|
123
|
+
and representative item counts. For carousels, test navigation and the
|
|
124
|
+
longest item; include RTL when direction changes layout or navigation.
|
|
125
|
+
3. Compare root/region `scrollWidth` with `clientWidth` (allow about 1px for
|
|
126
|
+
rounding), and inspect descendant bounds. For bounded height, also compare
|
|
127
|
+
`scrollHeight` and `clientHeight`. An intentional scroll region/track may
|
|
128
|
+
exceed its client size, but its content must remain reachable and contained.
|
|
129
|
+
Check the component itself: a page ancestor may clip overflow while the
|
|
130
|
+
document has no horizontal scrollbar. Conversely, shadows/overlays do not
|
|
131
|
+
automatically mean the content layout is broken.
|
|
132
|
+
4. Visually inspect and use the controls/keyboard. Passing geometry checks by
|
|
133
|
+
clipping required content is a failure. Verify intentional scroll regions
|
|
134
|
+
are usable with keyboard and touch, focus stays visible, and navigation can
|
|
135
|
+
reach every item.
|
|
136
|
+
|
|
137
|
+
Fix confirmed component-owned defects and repeat affected cases. If the first
|
|
138
|
+
incorrect constraint comes from the host layout, report that boundary instead
|
|
139
|
+
of masking it inside the component. For existing components, preserve unrelated
|
|
140
|
+
public props, editor overrides, and behavior.
|
|
141
|
+
|
|
142
|
+
Report sizes/states tested and any remaining overflow or inaccessible content.
|
|
143
|
+
If no browser preview or fixture is available, perform the source review and
|
|
144
|
+
state that rendered resize behavior is **unverified**; do not claim the resize
|
|
145
|
+
review passed. Build, typecheck, a11y scans, and manifest generation do not
|
|
146
|
+
establish that layout fits.
|
|
@@ -45,6 +45,12 @@ Apply this filter to every candidate:
|
|
|
45
45
|
share the same semantic editor surface. Distinguish position with data or a
|
|
46
46
|
module-only helper class, not separate part names.
|
|
47
47
|
|
|
48
|
+
The manifest generator can silently omit a named part behind conditional JSX.
|
|
49
|
+
Keep an editor-controlled part mounted in the render it inspects. Hide it with
|
|
50
|
+
CSS driven by a data attribute, or disable it when it should remain visible.
|
|
51
|
+
After generating the manifest, confirm that its `elements` include every
|
|
52
|
+
intended named part.
|
|
53
|
+
|
|
48
54
|
## Sanity Check
|
|
49
55
|
|
|
50
56
|
For each candidate, ask:
|
|
@@ -40,7 +40,9 @@ const RULE_CONFIG = {
|
|
|
40
40
|
'jsx-a11y/no-noninteractive-element-to-interactive-role': 'error',
|
|
41
41
|
// A tabpanel with no focusable content takes tabIndex=0 (ARIA Authoring Practices).
|
|
42
42
|
'jsx-a11y/no-noninteractive-tabindex': ['error', { roles: ['tabpanel'] }],
|
|
43
|
-
|
|
43
|
+
// Safari drops <ul> list semantics with list-style: none; role="list" restores
|
|
44
|
+
// them (WebKit bug 170179). Keep all other redundant roles flagged.
|
|
45
|
+
'jsx-a11y/no-redundant-roles': ['error', { ul: ['list'] }],
|
|
44
46
|
'jsx-a11y/no-static-element-interactions': 'error',
|
|
45
47
|
'jsx-a11y/prefer-tag-over-role': 'error',
|
|
46
48
|
'jsx-a11y/role-has-required-aria-props': 'error',
|
|
@@ -74,6 +76,22 @@ function forwardsOnlySdkHandlers(tag) {
|
|
|
74
76
|
);
|
|
75
77
|
}
|
|
76
78
|
|
|
79
|
+
const PASSIVE_AUTOPLAY_HANDLERS = new Set([
|
|
80
|
+
'onMouseEnter',
|
|
81
|
+
'onMouseLeave',
|
|
82
|
+
'onFocus',
|
|
83
|
+
'onBlur',
|
|
84
|
+
'onFocusCapture',
|
|
85
|
+
'onBlurCapture',
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
/** Carousel roots observe hover/focus to pause autoplay; these handlers do not activate the region. */
|
|
89
|
+
function pausesAutoplayOnly(tag) {
|
|
90
|
+
if (!isSdkRoot(tag) || !tag.includes('data-pause-button-visibility=')) return false;
|
|
91
|
+
const handlers = [...tag.matchAll(/\b(on[A-Z]\w*)=\{[^}]*\}/g)];
|
|
92
|
+
return handlers.length > 0 && handlers.every(([, name]) => PASSIVE_AUTOPLAY_HANDLERS.has(name));
|
|
93
|
+
}
|
|
94
|
+
|
|
77
95
|
/** Roles whose native element cannot express a styled component. */
|
|
78
96
|
const ROLES_WITHOUT_NATIVE_TAG = new Set(['img', 'presentation', 'none', 'group', 'status']);
|
|
79
97
|
|
|
@@ -93,6 +111,12 @@ function roleHasNoNativeTag(tag) {
|
|
|
93
111
|
* `rules` is skipped when the opening tag of the reported element matches `tag`.
|
|
94
112
|
*/
|
|
95
113
|
const EXEMPTIONS = [
|
|
114
|
+
{
|
|
115
|
+
// ANIMATED-COMPONENTS.md requires the noninteractive carousel root to
|
|
116
|
+
// observe hover and focus so autoplay stops while a visitor is using it.
|
|
117
|
+
rules: new Set(['jsx-a11y/no-noninteractive-element-interactions']),
|
|
118
|
+
tag: pausesAutoplayOnly,
|
|
119
|
+
},
|
|
96
120
|
{
|
|
97
121
|
// SDK handlers (`onClick`, `onMouseIn`, ...) are forwarded on the root
|
|
98
122
|
// element, which carries `id={id}`; the root itself is not the control.
|
|
@@ -67,7 +67,7 @@ The verticals the skill operates end-to-end today: **stores · blog · cms · fo
|
|
|
67
67
|
|
|
68
68
|
### bookings — appointments and classes
|
|
69
69
|
- **Intent:** book / appointment / schedule / class / session / reserve a slot — a **service or appointment with a provider** (a stylist, trainer, tutor, clinic, studio).
|
|
70
|
-
- **Not bookings — route to `restaurants` instead:** a **table at a restaurant** is a Table Reservations concern, not a bookable service; and a **special dining occasion guests reserve** — a
|
|
70
|
+
- **Not bookings — route to `restaurants` instead:** a **table at a restaurant** is a Table Reservations concern, not a bookable service; and a **special dining occasion guests reserve** — a chef's table, cheese pairing, or tasting-menu evening at a restaurant — is a **restaurants → Table Reservations *experience*** (see the restaurants entry), **not** a bookings service. Reach for `bookings` only when what's booked is a standalone service/class with a provider, never a seat at a restaurant.
|
|
71
71
|
- **Not bookings — route to `rentals` instead:** when the **customer chooses how long** they want something, and what's booked is a **thing rather than a person** — a room by the hour, a desk for three days, a van for the weekend. The tell is a duration *range* the customer picks within. A 60-minute haircut is `bookings`; a meeting room booked for 1–8 hours is `rentals`.
|
|
72
72
|
- **Required site features:** a list of services; a page per service; a way to pick a time and book it.
|
|
73
73
|
- **Implementation checklist:** show each service's name, duration, and price; show the staff or provider; show the available time slots; a book action; a confirmation after booking.
|
|
@@ -85,8 +85,8 @@ The verticals the skill operates end-to-end today: **stores · blog · cms · fo
|
|
|
85
85
|
- **Implementation checklist:** show each plan's name, price, and billing cycle; list what each plan includes (the perks); a clear "choose plan" action; highlight a recommended tier where it makes sense.
|
|
86
86
|
|
|
87
87
|
### restaurants — menus and food
|
|
88
|
-
- **Intent:** menu / restaurant / cafe / food / dish list / order food / dine-in / **reserve a table** / a **special dining occasion guests reserve** (
|
|
89
|
-
- **Required site features:** a menu organized into sections; each item with a name, description, and price. **Add-ons, each on demand (separate apps/features; see `SETUP.md`):** online ordering, table reservations, and — **when the brief names special dining occasions** — **experiences** (a reservation that *is* a curated occasion at the restaurant, e.g. a
|
|
88
|
+
- **Intent:** menu / restaurant / cafe / food / dish list / order food / dine-in / **reserve a table** / a **special dining occasion guests reserve** (chef's table, cheese pairing, tasting-menu evening).
|
|
89
|
+
- **Required site features:** a menu organized into sections; each item with a name, description, and price. **Add-ons, each on demand (separate apps/features; see `SETUP.md`):** online ordering, table reservations, and — **when the brief names special dining occasions** — **experiences** (a reservation that *is* a curated occasion at the restaurant, e.g. a chef's table), which ride on the Table Reservations app.
|
|
90
90
|
- **Experiences vs bookings — decide here:** an **experience** is a **reservation that is itself a special dining occasion at the restaurant** — it books seats at the restaurant's own location and carries its own name, per-guest price, schedule, and booking form. Use it for pairing sessions, tastings, and chef's tables. Use the separate **`bookings`** vertical only for a **service/appointment/class with a provider** that isn't a seat at the restaurant. For anything a restaurant hosts and seats, prefer an experience.
|
|
91
91
|
- **Implementation checklist:** show the menu grouped into sections in order; each item's name, description, price, and labels (vegan, spicy…) and modifiers / variants where they exist; an order action if online ordering is wired; a reserve-a-table action if reservations are wired; **if experiences are wired, list each experience with its name, description, per-guest price, and schedule, and a reserve-seats action.**
|
|
92
92
|
|
|
@@ -52,7 +52,7 @@ For each capability, build its `intent.<cap>` block — the inputs the seed step
|
|
|
52
52
|
| bookings | `{ serviceCount, servicesNamed: [] }` | `serviceCount: 2`, brand-derived service names |
|
|
53
53
|
| rentals | `{ unitType, serviceCount, resourceCount, servicesNamed: [] }` | `unitType: "HOUR"` (`"DAY"` if the brief rents by the day), `serviceCount: 2`, `resourceCount: 2`, brand-derived item names |
|
|
54
54
|
| pricing-plans | `{ planCount, tiersNamed: [] }` | `planCount: 2` (e.g. Basic / Pro), monthly billing |
|
|
55
|
-
| restaurants | `{ menuName, sections: [{ name, itemCount }], ordering?, reservations?, experiences?: [{ name }] }` | one menu, 2 sections, `itemCount: 3` each; add-ons only when the brief names them. **`experiences[]`** = special dining occasions guests reserve (
|
|
55
|
+
| restaurants | `{ menuName, sections: [{ name, itemCount }], ordering?, reservations?, experiences?: [{ name }] }` | one menu, 2 sections, `itemCount: 3` each; add-ons only when the brief names them. **`experiences[]`** = special dining occasions guests reserve (chef's table, tasting menu) — a restaurant *reservation that is an experience*, **not** the `bookings` vertical (see `CAPABILITIES.md`). |
|
|
56
56
|
| portfolio | `{ collections: [{ name }], projectCount }` | one brand-derived collection, `projectCount: 3` |
|
|
57
57
|
|
|
58
58
|
Counts are deliberately small (the seed shows the shape, not a full catalog).
|
|
@@ -48,7 +48,7 @@ Before implementing a capability, Read the local recipe linked in its row.
|
|
|
48
48
|
| restaurants — online ordering (add-on; only when the Orders app is in the run) | add `@wix/ecom` + `@wix/redirects` to the row above | `how-to-code-restaurant-orders.md` — ordering cart/checkout on top of the menu display. |
|
|
49
49
|
| portfolio | `@wix/portfolio` (+ `media` from `@wix/sdk` to resolve image strings; **no** `@wix/ecom`/`@wix/redirects` — display-only) | `how-to-code-portfolio.md` — Portfolio v1 display-only read. |
|
|
50
50
|
| restaurants — table reservations (add-on; only when the Table Reservations app is in the run) | `@wix/table-reservations` (a **distinct** package from `@wix/restaurants`; **no ecom/redirects** — a reservation is a hold, not a purchase) | `how-to-code-restaurant-reservations.md` — book-a-table hold→reserve. |
|
|
51
|
-
| restaurants — experiences (add-on; special dining occasions guests reserve —
|
|
51
|
+
| restaurants — experiences (add-on; special dining occasions guests reserve — chef's table, tasting menu) | `@wix/table-reservations` (same package; the **`experiences`** namespace + `timeSlots`/`reservations`) | `how-to-code-restaurant-reservations.md` — "Booking an experience" (query experiences → scheduled slots → reserve with `experienceId`). |
|
|
52
52
|
|
|
53
53
|
> **Cross-cutting packages** (`CAPABILITIES.md` § "Cross-cutting capabilities"): these aren't per-vertical rows but get added to the package list when the run used them. If **coupons** were seeded, add `@wix/marketing`. The eCommerce checkout packages (`@wix/ecom`, `@wix/redirects`) are already folded into the stores row above — include them once when any purchase-flow vertical is present.
|
|
54
54
|
|
|
@@ -39,7 +39,7 @@ Each entry states only the entities, where their count/content comes from, the p
|
|
|
39
39
|
- **bookings** — *Bookable services.* Recipe: **`setup-bookings.md`**. `intent.bookings.serviceCount` services (name + short description fitting `brand`, a simple duration and price); keep the schedule minimal. **Keep:** `serviceIds[]` as **seed-time state** (pricing-plans' coverage step keys on them, below); nothing for the handoff — services are discovered **live** (`queryServices`).
|
|
40
40
|
- **rentals** — *Rentable resources.* Recipe: **`setup-rentals.md`**. A resource **type** per kind of thing rented (rooms, vans, cameras), then `intent.rentals.resourceCount` **resources** inside it (Room A / Room B — parallel capacity comes from more resources, not higher capacity), then `intent.rentals.serviceCount` **rental services**, each carrying the Wix Rentals `appId`, `serviceResources` naming its resource type, a `primaryResourceType`, and a `durationRange` (`HOUR` or `DAY`) with the min/max the request implies — defaulting to **60/480 minutes** hourly and **1/5 days** daily when the brief doesn't say — and a **per-unit** price. **No category** — unlike bookings, rental services don't use one; don't create or assign one. **Order is load-bearing: resource type → resources → services** — a service whose resource type holds no resources has permanently empty availability. Seed resources **24/7** (no working-hours schedule) unless the request names opening hours; that keeps a multi-day rental to a single booking. **Keep:** `resourceTypeIds[]` as seed-time state (services key on them); nothing for the handoff — rentals are discovered **live** (`queryServicesByFilters` filtered by the rentals `appId`).
|
|
41
41
|
- **pricing-plans** — *Membership tiers.* Recipe: **`setup-pricing-plans.md`**. `intent.pricing-plans.planCount` recurring plans (name, price, a monthly billing cycle) fitting `brand`. **When bookings is also in this run and the intent is a membership that covers services** (a studio/gym/class pass), also **attach the covered bookings services to the plan** — seed the bookings services first, since coverage keys on their service ids (see the recipe). **Keep:** the bookings coverage only transiently, to wire it during seeding; nothing for the handoff — plans are discovered **live** (`queryPlans`) and plan→service coverage is read **live** at checkout, never from a frozen map (see `how-to-code-pricing-plans.md`).
|
|
42
|
-
- **restaurants** — *A menu.* Recipe: **`setup-restaurants.md`** (add-ons have their own recipes, below). Create the `itemCount` items per section first (name, description, price fitting `brand`), then the `intent.restaurants.sections` referencing those item ids, then the menu referencing the section ids — **items → sections → menu** (build bottom-up: a section is created with its `itemIds`, a menu with its `sectionIds`, so each child must exist before its parent — see `setup-restaurants.md` STEP 1–3). Text-only. **Keep (seed-time only):** `menuId`, `sectionIds[]`, `itemIds[]` to build the tree; nothing goes to the handoff — the frontend reads menus **live** (`listMenus`/`listSections`/`listItems`). **Online ordering (add-on, on demand)** — only when the request calls for ordering (not just a displayed menu): install the Orders app (per `SETUP.md` §2) and follow **`setup-restaurant-orders.md`**; **seed the menu FIRST, then ordering, in the same unit** (ordering auto-provisions per menu either way — see the concurrency note above — but keeping them one unit gives the ordering verify a menu to confirm against). **Configure the business-location address (`setup-restaurant-orders.md` STEP 0) — required for ordering to work end-to-end; without it Wix limits ordering to "testing only" and checkout breaks.** If the brief gives no address, set a placeholder and flag the owner in the handoff. Completing a *paid* order additionally needs premium + a payment method (dashboard/premium — surface, don't fail). **Keep:** nothing for the handoff — read **live** (`listOperations`/`listFulfillmentMethods`); track `operationId`/`fulfillmentMethodIds[]` transiently. **Table reservations (add-on, on demand, INDEPENDENT of ordering/menu)** — only when the request calls for reservations: install the Table Reservations app (per `SETUP.md` §2) and follow **`setup-restaurant-reservations.md`**; **no menu dependency** (reservations bind to a location, not a menu — do NOT apply the menu-first rule) and **nothing to bulk-seed** (visitors create reservations at runtime); **configure the business-location address (`setup-restaurant-reservations.md` STEP 0)** — the reservation location is the site's business location, so set a real address (placeholder + owner flag if the brief names none; shared with ordering if both are present); enabling online reservations is **premium-gated** (record the precondition, don't fail). **Keep:** nothing for the handoff — read **live** (`listReservationLocations`); track `reservationLocationId` transiently. **Experiences (add-on within Table Reservations, on demand)** — only when the request names **special dining occasions guests reserve** (
|
|
42
|
+
- **restaurants** — *A menu.* Recipe: **`setup-restaurants.md`** (add-ons have their own recipes, below). Create the `itemCount` items per section first (name, description, price fitting `brand`), then the `intent.restaurants.sections` referencing those item ids, then the menu referencing the section ids — **items → sections → menu** (build bottom-up: a section is created with its `itemIds`, a menu with its `sectionIds`, so each child must exist before its parent — see `setup-restaurants.md` STEP 1–3). Text-only. **Keep (seed-time only):** `menuId`, `sectionIds[]`, `itemIds[]` to build the tree; nothing goes to the handoff — the frontend reads menus **live** (`listMenus`/`listSections`/`listItems`). **Online ordering (add-on, on demand)** — only when the request calls for ordering (not just a displayed menu): install the Orders app (per `SETUP.md` §2) and follow **`setup-restaurant-orders.md`**; **seed the menu FIRST, then ordering, in the same unit** (ordering auto-provisions per menu either way — see the concurrency note above — but keeping them one unit gives the ordering verify a menu to confirm against). **Configure the business-location address (`setup-restaurant-orders.md` STEP 0) — required for ordering to work end-to-end; without it Wix limits ordering to "testing only" and checkout breaks.** If the brief gives no address, set a placeholder and flag the owner in the handoff. Completing a *paid* order additionally needs premium + a payment method (dashboard/premium — surface, don't fail). **Keep:** nothing for the handoff — read **live** (`listOperations`/`listFulfillmentMethods`); track `operationId`/`fulfillmentMethodIds[]` transiently. **Table reservations (add-on, on demand, INDEPENDENT of ordering/menu)** — only when the request calls for reservations: install the Table Reservations app (per `SETUP.md` §2) and follow **`setup-restaurant-reservations.md`**; **no menu dependency** (reservations bind to a location, not a menu — do NOT apply the menu-first rule) and **nothing to bulk-seed** (visitors create reservations at runtime); **configure the business-location address (`setup-restaurant-reservations.md` STEP 0)** — the reservation location is the site's business location, so set a real address (placeholder + owner flag if the brief names none; shared with ordering if both are present); enabling online reservations is **premium-gated** (record the precondition, don't fail). **Keep:** nothing for the handoff — read **live** (`listReservationLocations`); track `reservationLocationId` transiently. **Experiences (add-on within Table Reservations, on demand)** — only when the request names **special dining occasions guests reserve** (chef's table, tasting menu, tasting-menu evening — an *experience*, not a `bookings` service; see `CAPABILITIES.md`): they need the **Table Reservations app** (no extra install) and a configured **business-location address** (`setup-restaurant-reservations.md` STEP 0, same as reservations) and, unlike plain reservations, they **are** seeded — create one Experience per named occasion on the default reservation location, following **`setup-restaurant-experiences.md`**. Booking an experience is **premium-gated** like online reservations (record the precondition, don't fail). **Keep:** nothing for the handoff — experiences are read **live** (`queryExperiences`); track `experienceId`(s) transiently.
|
|
43
43
|
- **portfolio** — *A project showcase.* Recipe: **`setup-portfolio.md`**. Create the `intent.portfolio.collections` (title + short description fitting `brand`), then `intent.portfolio.projectCount` projects assigned to them — **collections before projects** (a project's `collectionIds` must hold real collection ids; see the recipe). Text-only by default (omit `coverImage`; images only when `imagery` is on — §1, §4). **Keep (seed-time only):** `collectionIds{<name>}` to assign projects into their collections; nothing goes to the handoff — the frontend lists collections and projects **live** (`collections.queryCollections` / `projects.queryProjects`), so owner-added content appears with no code change.
|
|
44
44
|
|
|
45
45
|
> **Cross-cutting (on demand).** `coupons` is not a standalone capability (`CAPABILITIES.md` § "Cross-cutting capabilities") — there's no app to install and it's not in `verticals[]`. If intent calls for discounts **and** a parent vertical (stores / bookings / events / pricing-plans) is in this run, create coupons scoped to that parent and add the coupon to the `seeded` map (`seeded.coupons = { couponIds[] }`). **Coupons has no inline recipe** — read the create shape from the docs (`DOC_DISCOVERY.md` is the fallback): every coupon needs a scope with a namespace and its parent vertical's app installed (<https://dev.wix.com/docs/api-reference/business-solutions/coupons/about-wix-coupons.md>), and the create-coupon spec needs name/code/start + a scope namespace + exactly one coupon-type field (<https://dev.wix.com/docs/api-reference/business-solutions/coupons/coupons/create-a-coupon.md>). eCommerce needs no seeding — it's the runtime checkout layer that rides along.
|
|
@@ -25,7 +25,7 @@ For each capability in `verticals[]`, install its app by `appDefId` (these are t
|
|
|
25
25
|
- **bookings** → `13d21c63-b5ec-5912-8397-c3a5ddb27a97`
|
|
26
26
|
- **rentals** → `ff5d6eb1-65e4-4f9a-8b14-64d34c12cc2e` (Wix Rentals). Rentals provisions the Bookings infrastructure it runs on, so a Rentals-only install is enough — its availability comes back without adding Wix Bookings separately. Rentals has **no APIs of its own**: it is Services V2 with rentals-specific field values (`setup-rentals.md`), and its frontend package is `@wix/bookings` — there is no `@wix/rentals`. Rentals uses no categories; to also offer plain fixed-duration services, install Wix Bookings (`13d21c63-b5ec-5912-8397-c3a5ddb27a97`) as well, seed those with `setup-bookings.md`, and filter every catalog read by `appId` so the two sets stay separate.
|
|
27
27
|
- **pricing-plans** → `1522827f-c56c-a5c9-2ac9-00f9e6ae12d3`
|
|
28
|
-
- **restaurants** → `b278a256-2757-4f19-9313-c05c783bec92` (Wix Restaurants **Menus** — the seedable core). Online ordering and table reservations are **separate optional apps**, install only if intent calls for them: Orders (New) → `9a5d83fd-8570-482e-81ab-cfa88942ee60`, Table Reservations → `f9c07de2-5341-40c6-b096-8eb39de391fb`. **Experiences** (special dining occasions guests reserve —
|
|
28
|
+
- **restaurants** → `b278a256-2757-4f19-9313-c05c783bec92` (Wix Restaurants **Menus** — the seedable core). Online ordering and table reservations are **separate optional apps**, install only if intent calls for them: Orders (New) → `9a5d83fd-8570-482e-81ab-cfa88942ee60`, Table Reservations → `f9c07de2-5341-40c6-b096-8eb39de391fb`. **Experiences** (special dining occasions guests reserve — chef's tables, tasting menus; see `CAPABILITIES.md`) are a **feature of the Table Reservations app, not a separate install** — installing Table Reservations is all that's needed to create and book them (`setup-restaurant-experiences.md`).
|
|
29
29
|
- **portfolio** → `d90652a2-f5a1-4c7c-84c4-d4cdcc41f130` (Wix Portfolio; the install ships a default sample collection + projects — `setup-portfolio.md` STEP 0 cleans them)
|
|
30
30
|
- **cms** → **no install** (Wix Data is core) — skip
|
|
31
31
|
|
package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-reservations.md
CHANGED
|
@@ -166,7 +166,7 @@ Doc: <https://dev.wix.com/docs/api-reference/business-solutions/restaurants/rese
|
|
|
166
166
|
|
|
167
167
|
### Booking an experience (only when experiences are in the run)
|
|
168
168
|
|
|
169
|
-
An **experience** is a reservation that *is* a curated dining occasion (
|
|
169
|
+
An **experience** is a reservation that *is* a curated dining occasion (chef's table, tasting menu). It uses the **same** booking mechanism as a table, with two differences: you **list experiences** to show and choose from, and you offer bookable times **projected from the experience's own schedule** (there is *no* experience-scoped slot API — see Gotcha C) rather than from `getScheduledTimeSlots`, then stamp the chosen `experienceId` onto the reservation. Read the shapes from the docs (`.md?apiView=SDK`); the earned drift gotchas below are what the docs won't tell you.
|
|
170
170
|
|
|
171
171
|
Flow (mirrors the docs' sample flow — <https://dev.wix.com/docs/api-reference/business-solutions/restaurants/reservations/experiences/sample-flows.md>):
|
|
172
172
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: "Setup Restaurant Experiences"
|
|
3
|
-
description: Seeds Wix Table Reservations **Experiences** — a reservation that IS a special dining occasion at the restaurant (
|
|
3
|
+
description: Seeds Wix Table Reservations **Experiences** — a reservation that IS a special dining occasion at the restaurant (chef's table, cheese pairing, tasting-menu evening), as opposed to a plain table booking or a Bookings service. Points to the live docs for the full create payload and carries only the earned gotchas the docs don't state (field-shape drift, payment policy, premium gating, when-to-use). The occasions/prices/schedule come from the request.
|
|
4
4
|
---
|
|
5
5
|
**RECIPE**: Business Recipe – Experiences Setup for Wix Table Reservations (Experiences API)
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ description: Seeds Wix Table Reservations **Experiences** — a reservation that
|
|
|
8
8
|
|
|
9
9
|
## When to use experiences (and when NOT to)
|
|
10
10
|
|
|
11
|
-
An **experience** is a **reservation that is itself a curated dining occasion at the restaurant** — it books seats at the restaurant's own reservation location but overrides the location's defaults with its own name, description, per-guest payment policy, party-size, schedule, and booking form. Reach for it when the brief names a **
|
|
11
|
+
An **experience** is a **reservation that is itself a curated dining occasion at the restaurant** — it books seats at the restaurant's own reservation location but overrides the location's defaults with its own name, description, per-guest payment policy, party-size, schedule, and booking form. Reach for it when the brief names a **chef's table, cheese pairing, or tasting-menu evening**.
|
|
12
12
|
|
|
13
13
|
- **Experience vs plain reservation:** a plain reservation just books a table; an experience books a *named, scheduled, often paid occasion*. Same booking mechanism, different intent.
|
|
14
14
|
- **Experience vs `bookings`:** the separate `bookings` vertical is for a **service/appointment/class with a provider** (spa, tutor, studio). If a restaurant *hosts and seats* the occasion, it's an experience — not a booking. (See `CAPABILITIES.md` — "Experiences vs bookings".)
|
|
@@ -16,7 +16,7 @@ npx @wix/cli@latest whoami # exits 0 when logged in; non-zero when logged out
|
|
|
16
16
|
If it's non-zero, **log in yourself** — don't punt to the user and stop. A foreground command that exits in seconds, one JSON event per line:
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
node ../../
|
|
19
|
+
node ../../scripts/bootstrap.mjs # path relative to this file
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
| Event | Do |
|
|
@@ -25,20 +25,9 @@ node ../../entry/bootstrap.mjs # path relative to this file
|
|
|
25
25
|
| `awaiting_user` (`verificationUri`, `userCode`, `message`) | Send `message` verbatim, then stop. Re-run the script when the user says they're done → `logged_in`. Re-running early returns the **same** code, not a new one. |
|
|
26
26
|
| `cli_unreachable` / `login_failed` (`detail`) | Show `detail` and stop. |
|
|
27
27
|
|
|
28
|
-
###
|
|
28
|
+
### What the script does
|
|
29
29
|
|
|
30
|
-
Read `../../
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
# AI_AGENT must be set, or the CLI renders an interactive Ink TUI and emits no JSON.
|
|
34
|
-
# Detach, redirecting to a FILE — a pipe dies with your process and the CLI gets EPIPE.
|
|
35
|
-
AI_AGENT=my-agent nohup npx -y @wix/cli@latest login > /tmp/wix-login.log 2>&1 &
|
|
36
|
-
|
|
37
|
-
grep -m1 awaiting_user /tmp/wix-login.log
|
|
38
|
-
# {"event":"awaiting_user","expiresInSeconds":600,"userCode":"…","verificationUri":"…"}
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
Send the user that URL + code, then stop — nothing moves until they act. The detached login lands the session on its own; resume with `whoami` (exit 0). Codes last ~10 min; after that, start a new login and surface the new code.
|
|
30
|
+
Read `../../scripts/bootstrap.mjs` if you want to see it: it sets `AI_AGENT` so the CLI emits JSON events instead of an interactive TUI, starts `wix login` detached with its output in a file under the OS temp dir (a pipe would die with the parent), and prints the device code. It is the one login path; there is no manual variant. Codes last ~10 min; after that, run it again and surface the new code.
|
|
42
31
|
|
|
43
32
|
## 2 · Mint the token
|
|
44
33
|
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
3
|
+
import { existsSync, mkdirSync, openSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
// ── tiny event protocol (one JSON object per line) ───────────────────────────
|
|
8
|
+
const emit = (event, extra = {}) =>
|
|
9
|
+
process.stdout.write(JSON.stringify({ event, ...extra }) + '\n');
|
|
10
|
+
const fail = (event, extra = {}) => {
|
|
11
|
+
emit(event, { ok: false, ...extra });
|
|
12
|
+
process.exit(1);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// ── platform-safe binary names (npm/npx are .cmd on Windows) ─────────────────
|
|
16
|
+
const isWin = process.platform === 'win32';
|
|
17
|
+
const bin = (name) => (isWin ? `${name}.cmd` : name);
|
|
18
|
+
const WIX = [bin('npx'), '-y', '@wix/cli@latest']; // run the CLI via npx — no global install/mutation
|
|
19
|
+
|
|
20
|
+
// Force the CLI into non-interactive "agent" mode. Without an agent signal in
|
|
21
|
+
// the env, `wix login` renders an interactive Ink TUI (device code + keypress)
|
|
22
|
+
// that needs a raw TTY and crashes in an agent sandbox — and it never emits the
|
|
23
|
+
// JSON login events this script forwards. The CLI uses @vercel/detect-agent,
|
|
24
|
+
// whose first check is the AI_AGENT env var, so setting it guarantees agent mode
|
|
25
|
+
// (and the awaiting_user/success/logged_in events) for every child we spawn.
|
|
26
|
+
// Respect an existing value so a known runner (claude, cursor, …) keeps its name.
|
|
27
|
+
const AGENT_ENV = { ...process.env, AI_AGENT: process.env.AI_AGENT || 'wix-headless-skill' };
|
|
28
|
+
|
|
29
|
+
// Where the detached login parks its output between runs. It has to outlive this
|
|
30
|
+
// process, so it can't be a pipe and can't live in the project.
|
|
31
|
+
const STATE_DIR = join(tmpdir(), 'wix-headless-login');
|
|
32
|
+
const LOG = join(STATE_DIR, 'login.log');
|
|
33
|
+
const PIDFILE = join(STATE_DIR, 'login.pid');
|
|
34
|
+
|
|
35
|
+
// run a command, capture stdout+stderr (combined), return {status, out}
|
|
36
|
+
function capture(cmd, args, opts = {}) {
|
|
37
|
+
const r = spawnSync(cmd, args, { encoding: 'utf8', shell: isWin, env: AGENT_ENV, ...opts });
|
|
38
|
+
return { status: r.status ?? 1, out: `${r.stdout || ''}${r.stderr || ''}`, error: r.error };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── 1. CLI reachable (via npx — no install) ──────────────────────────────────
|
|
42
|
+
function checkCli() {
|
|
43
|
+
const r = capture(WIX[0], [...WIX.slice(1), '--version']);
|
|
44
|
+
if (r.status !== 0) fail('cli_unreachable', { detail: r.out.trim().slice(0, 400) });
|
|
45
|
+
// npx interleaves "npm notice …" lines with the version, so don't just take the
|
|
46
|
+
// last line — pick the first semver-looking token, falling back to the last line.
|
|
47
|
+
const version = (r.out.match(/\d+\.\d+\.\d+[^\s]*/) || [])[0] || r.out.trim().split('\n').pop();
|
|
48
|
+
emit('cli_ok', { version });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ── 2. Reuse an existing session, or start device login ──────────────────────
|
|
52
|
+
const hasExistingSession = () => capture(WIX[0], [...WIX.slice(1), 'whoami']).status === 0;
|
|
53
|
+
|
|
54
|
+
function loginEvents() {
|
|
55
|
+
if (!existsSync(LOG)) return [];
|
|
56
|
+
return readFileSync(LOG, 'utf8')
|
|
57
|
+
.split('\n')
|
|
58
|
+
.map((l) => l.trim())
|
|
59
|
+
.filter(Boolean)
|
|
60
|
+
.map((l) => {
|
|
61
|
+
try {
|
|
62
|
+
return JSON.parse(l);
|
|
63
|
+
} catch {
|
|
64
|
+
return null; // non-JSON CLI chatter
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
.filter((e) => e && e.event);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const alive = (pid) => {
|
|
71
|
+
try {
|
|
72
|
+
process.kill(pid, 0);
|
|
73
|
+
return true;
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// A login this machine started earlier may still be waiting on the browser. Reuse
|
|
80
|
+
// its code instead of minting a second one — a new login invalidates nothing, but
|
|
81
|
+
// it hands the user a different code than the one already on their screen.
|
|
82
|
+
function pendingLogin() {
|
|
83
|
+
if (!existsSync(PIDFILE)) return null;
|
|
84
|
+
let state;
|
|
85
|
+
try {
|
|
86
|
+
state = JSON.parse(readFileSync(PIDFILE, 'utf8'));
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
if (!state.pid || !alive(state.pid)) return null;
|
|
91
|
+
const ev = loginEvents().find((e) => e.event === 'awaiting_user');
|
|
92
|
+
if (!ev) return null;
|
|
93
|
+
// Don't hand back a code that's about to expire mid-typing.
|
|
94
|
+
const ageSeconds = Math.round((Date.now() - state.startedAt) / 1000);
|
|
95
|
+
if (ageSeconds > (ev.expiresInSeconds ?? 600) - 60) return null;
|
|
96
|
+
return ev;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Detach so the login keeps polling after this process exits. stdio goes to a
|
|
100
|
+
// file, not a pipe: a pipe dies with the parent, and the CLI would get EPIPE.
|
|
101
|
+
function startLogin() {
|
|
102
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
103
|
+
writeFileSync(LOG, '');
|
|
104
|
+
const out = openSync(LOG, 'a');
|
|
105
|
+
const child = spawn(WIX[0], [...WIX.slice(1), 'login'], {
|
|
106
|
+
detached: true,
|
|
107
|
+
stdio: ['ignore', out, out],
|
|
108
|
+
env: AGENT_ENV,
|
|
109
|
+
shell: isWin,
|
|
110
|
+
});
|
|
111
|
+
child.on('error', (e) => fail('login_failed', { detail: String(e) }));
|
|
112
|
+
writeFileSync(PIDFILE, JSON.stringify({ pid: child.pid, startedAt: Date.now() }));
|
|
113
|
+
child.unref();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function waitForCode(timeoutMs = 60000) {
|
|
117
|
+
const deadline = Date.now() + timeoutMs;
|
|
118
|
+
while (Date.now() < deadline) {
|
|
119
|
+
const events = loginEvents();
|
|
120
|
+
const ev = events.find((e) => e.event === 'awaiting_user');
|
|
121
|
+
if (ev) return ev;
|
|
122
|
+
const bad = events.find((e) => e.event === 'login_failed');
|
|
123
|
+
if (bad) fail('login_failed', bad);
|
|
124
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
125
|
+
}
|
|
126
|
+
const detail = existsSync(LOG) ? readFileSync(LOG, 'utf8').trim().slice(-1500) : '';
|
|
127
|
+
fail('login_failed', {
|
|
128
|
+
detail: detail || `wix login produced no device code within ${timeoutMs / 1000}s.`,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// The next move is the user's, so hand the code back and exit rather than holding
|
|
133
|
+
// the caller open for a browser round-trip. `message` is the sentence to relay.
|
|
134
|
+
function surrenderTo(ev) {
|
|
135
|
+
emit('awaiting_user', {
|
|
136
|
+
...ev,
|
|
137
|
+
message:
|
|
138
|
+
`To connect your Wix account, open ${ev.verificationUri} and enter the code ` +
|
|
139
|
+
`${ev.userCode}. Tell me once you're done and I'll continue.`,
|
|
140
|
+
});
|
|
141
|
+
process.exit(0);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ── main ────────────────────────────────────────────────────────────────────
|
|
145
|
+
checkCli();
|
|
146
|
+
|
|
147
|
+
if (hasExistingSession()) {
|
|
148
|
+
emit('logged_in');
|
|
149
|
+
process.exit(0);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const pending = pendingLogin();
|
|
153
|
+
if (pending) surrenderTo(pending);
|
|
154
|
+
|
|
155
|
+
startLogin();
|
|
156
|
+
surrenderTo(await waitForCode());
|