@wix/agent-skills 1.18.2 → 1.19.1
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 +42 -40
- package/skills/wix-app/references/BACKEND_API.md +151 -152
- package/skills/wix-app/references/DASHBOARD_PAGE.md +6 -10
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +16 -7
- package/skills/wix-app/references/EXTENSION_REGISTRATION.md +12 -8
- package/skills/wix-app/references/WIX_PATTERNS_DOCS.md +53 -131
- package/skills/wix-app/references/dashboard-page/UX_SUCCESS_MODEL.md +1 -2
- package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +83 -73
- package/skills/wix-app/references/editor-react-component/COMPONENT-CONTRACT.md +3 -2
- package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +9 -0
- package/skills/wix-app/references/editor-react-component/PARTS.md +5 -2
- package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +1 -1
- package/skills/wix-app/scripts/pkg-root.cjs +73 -0
- package/skills/wix-app/scripts/scan-a11y-code.cjs +310 -40
- package/skills/wix-app/scripts/scan-a11y-eslint.cjs +118 -24
- package/skills/wix-app/scripts/scan-a11y-render.cjs +551 -0
- package/skills/wix-app/scripts/scan-a11y-review.cjs +373 -0
- package/skills/wix-base44-connector/SKILL.md +24 -22
- package/skills/wix-base44-connector/scripts/utils.cjs +39 -3
- package/skills/wix-docs/SKILL.md +47 -20
- package/skills/wix-headless/entry/bootstrap.mjs +106 -50
- package/skills/wix-headless/entry/skill.md +5 -3
- package/skills/wix-headless/references/managed/AUTHENTICATION.md +25 -5
- package/skills/wix-headless-fast/SKILL.md +25 -7
- package/skills/wix-headless-fast/entry/skill.md +23 -14
- package/skills/wix-headless-fast/install/fast-path.mjs +48 -5
- package/skills/wix-headless-fast/install/pin-agents-md.mjs +57 -0
- package/skills/wix-headless-fast/references/shared/CUSTOM_OPERATIONS.md +3 -3
- package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +204 -35
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +146 -36
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-store.ts +8 -2
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +95 -20
- package/skills/wix-headless-fast/references/storefront/seed/SEED.md +21 -2
- package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +104 -18
- package/skills/wix-manage/SKILL.md +14 -8
- package/skills/wix-manage/references/bookings/booking-service-policy-setup.md +59 -33
- package/skills/wix-manage/references/bookings/bookings-staff-setup.md +45 -22
- package/skills/wix-manage/references/bookings/multi-resource-service-creation.md +70 -12
- package/skills/wix-manage/references/calendar/configure-default-business-hours.md +53 -17
- package/skills/wix-manage/references/google-ads/billing-and-payment.md +3 -3
- package/skills/wix-manage/references/google-ads/create-performance-max-campaign.md +8 -8
- package/skills/wix-manage/references/google-ads/get-campaign-suggestions.md +4 -4
- package/skills/wix-manage/references/google-ads/google-ads-dashboard-navigation.md +1 -1
- package/skills/wix-manage/references/google-ads/install-and-create-account.md +9 -9
- package/skills/wix-manage/references/google-ads/query-campaign-analytics.md +7 -7
- package/skills/wix-manage/references/seo/manage-content-plan.md +181 -135
- package/skills/wix-manage/references/seo/manage-google-search-console.md +272 -0
- package/skills/wix-manage/references/sites/upload-static-site.md +53 -0
- package/skills/wix-manage/references/stores/change-store-currency.md +1 -1
- package/skills/wix-manage/references/stores/create-product-catalog-v3.md +17 -1
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed-with-install.md +12 -9
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +12 -9
- package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +1 -1
- package/skills/wix-vibe-headless/platforms/base44.md +11 -3
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +5 -1
- package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +5 -4
- package/skills/wix-vibe-headless/references/storefront/seed/seed-store.cjs +33 -5
- package/skills/wix-app/references/dashboard-page/COLLECTION_TOOLKIT.md +0 -80
- package/skills/wix-app/references/dashboard-page/ENTITY_PAGE_TOOLKIT.md +0 -140
- package/skills/wix-app/references/dashboard-page/PATTERNS_BUNDLE_READING.md +0 -112
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductCard.tsx +0 -100
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductDetailView.tsx +0 -130
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductGrid.tsx +0 -48
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/ShopView.tsx +0 -66
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/VariantPicker.tsx +0 -103
- package/skills/wix-headless-fast/references/storefront/app-astro/pages/products/[slug].astro +0 -56
- package/skills/wix-headless-fast/references/storefront/app-astro/pages/shop.astro +0 -23
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn, spawnSync } from 'node:child_process';
|
|
3
|
-
import
|
|
3
|
+
import { existsSync, mkdirSync, openSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
4
6
|
|
|
5
7
|
// ── tiny event protocol (one JSON object per line) ───────────────────────────
|
|
6
8
|
const emit = (event, extra = {}) =>
|
|
@@ -24,6 +26,12 @@ const WIX = [bin('npx'), '-y', '@wix/cli@latest']; // run the CLI via npx — no
|
|
|
24
26
|
// Respect an existing value so a known runner (claude, cursor, …) keeps its name.
|
|
25
27
|
const AGENT_ENV = { ...process.env, AI_AGENT: process.env.AI_AGENT || 'wix-headless-skill' };
|
|
26
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
|
+
|
|
27
35
|
// run a command, capture stdout+stderr (combined), return {status, out}
|
|
28
36
|
function capture(cmd, args, opts = {}) {
|
|
29
37
|
const r = spawnSync(cmd, args, { encoding: 'utf8', shell: isWin, env: AGENT_ENV, ...opts });
|
|
@@ -41,60 +49,108 @@ function checkCli() {
|
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
// ── 2. Reuse an existing session, or start device login ──────────────────────
|
|
44
|
-
|
|
45
|
-
const r = capture(WIX[0], [...WIX.slice(1), 'whoami']);
|
|
46
|
-
if (r.status !== 0) return false;
|
|
47
|
-
emit('logged_in');
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
52
|
+
const hasExistingSession = () => capture(WIX[0], [...WIX.slice(1), 'whoami']).status === 0;
|
|
50
53
|
|
|
51
|
-
function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// a guess. Keep only the tail so a chatty CLI can't blow up memory, and draining
|
|
59
|
-
// stderr also avoids the pipe filling up and stalling the child.
|
|
60
|
-
let diag = '';
|
|
61
|
-
const collect = (chunk) => {
|
|
62
|
-
diag = (diag + chunk).slice(-2000);
|
|
63
|
-
};
|
|
64
|
-
child.stderr?.on('data', collect);
|
|
65
|
-
rl.on('line', (line) => {
|
|
66
|
-
const t = line.trim();
|
|
67
|
-
if (!t) return;
|
|
68
|
-
// In agent mode the CLI emits {event:"awaiting_user"|"success"|"logged_in", ...}
|
|
69
|
-
// — pass those straight through so the agent can relay the device code.
|
|
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) => {
|
|
70
61
|
try {
|
|
71
|
-
|
|
72
|
-
if (ev && ev.event) {
|
|
73
|
-
process.stdout.write(t + '\n');
|
|
74
|
-
if (ev.event === 'success' || ev.event === 'logged_in') {
|
|
75
|
-
loggedIn = true;
|
|
76
|
-
resolve();
|
|
77
|
-
}
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
collect(line + '\n'); // JSON, but not an event — keep for diagnostics
|
|
62
|
+
return JSON.parse(l);
|
|
81
63
|
} catch {
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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.`,
|
|
95
140
|
});
|
|
141
|
+
process.exit(0);
|
|
96
142
|
}
|
|
97
143
|
|
|
98
144
|
// ── main ────────────────────────────────────────────────────────────────────
|
|
99
145
|
checkCli();
|
|
100
|
-
|
|
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());
|
|
@@ -36,9 +36,9 @@ If that errors (Node not installed) or prints a version below 20.11, install or
|
|
|
36
36
|
|
|
37
37
|
## Phase 1 — Run the bootstrap (deterministic)
|
|
38
38
|
|
|
39
|
-
Download the bootstrap script, then run it. It verifies the Wix CLI and handles login, emitting **one JSON event per line** on stdout. **Run it
|
|
39
|
+
Download the bootstrap script, then run it — an ordinary foreground command that exits on its own within seconds. It verifies the Wix CLI and handles login, emitting **one JSON event per line** on stdout. **Run it and relay its events to the user.**
|
|
40
40
|
|
|
41
|
-
The script is safe and inspectable: it only checks the Wix CLI via `npx` and drives `wix login` (a device-code flow) — no other network calls,
|
|
41
|
+
The script is safe and inspectable: it only checks the Wix CLI via `npx` and drives `wix login` (a device-code flow) — no other network calls, and the only files it writes are the login's own output and pid under the OS temp dir. Read it first if your sandbox flags externally-downloaded code — either at the URL below or after downloading; both are the same file.
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
44
|
# macOS/Linux:
|
|
@@ -56,10 +56,12 @@ The script emits one JSON object per line:
|
|
|
56
56
|
| Event | What to do |
|
|
57
57
|
|---|---|
|
|
58
58
|
| `cli_ok` | Wix CLI reachable — continue. |
|
|
59
|
-
| `awaiting_user` (`verificationUri`, `userCode`) |
|
|
59
|
+
| `awaiting_user` (`verificationUri`, `userCode`, `message`) | The script has exited and the next step is the user's. Send them `message` as-is; the login keeps running on its own. |
|
|
60
60
|
| `logged_in` / `success` | Login done — continue. |
|
|
61
61
|
| `cli_unreachable` / `login_failed` (with `detail`) | Stop and show the user the `detail`. **Do not** improvise a parallel setup by hand. |
|
|
62
62
|
|
|
63
|
+
On `awaiting_user`, run the script again once the user says they've logged in: it reports `logged_in` and you continue. Re-running while they're still in the browser is harmless — it returns the same code rather than issuing a new one.
|
|
64
|
+
|
|
63
65
|
## Phase 2 — Install the skill and hand off
|
|
64
66
|
|
|
65
67
|
Install the Wix Headless skills (`CI=1` forces plain non-interactive CLI output — keep it on every Wix CLI command):
|
|
@@ -13,12 +13,32 @@ Never emit them to stdout, stderr, logs, prompts, or tool results.
|
|
|
13
13
|
npx @wix/cli@latest whoami # exits 0 when logged in; non-zero when logged out
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
If it's non-zero, **log in yourself** — don't punt to the user and stop:
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
```bash
|
|
19
|
+
node ../../entry/bootstrap.mjs # path relative to this file
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
| Event | Do |
|
|
23
|
+
|---|---|
|
|
24
|
+
| `logged_in` | Session exists — continue. |
|
|
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
|
+
| `cli_unreachable` / `login_failed` (`detail`) | Show `detail` and stop. |
|
|
27
|
+
|
|
28
|
+
### If you'd rather not execute it
|
|
29
|
+
|
|
30
|
+
Read `../../entry/bootstrap.mjs` and do the same thing yourself. What matters:
|
|
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.
|
|
22
42
|
|
|
23
43
|
## 2 · Mint the token
|
|
24
44
|
|
|
@@ -11,6 +11,11 @@ layer, hooks, components, pages, and a seed script that are already correct —
|
|
|
11
11
|
job narrows to brand, layout, copy, and wiring. The decisions live in the code; don't
|
|
12
12
|
re-litigate them.
|
|
13
13
|
|
|
14
|
+
**Scope.** This is the bootstrap, tuned for Wix-managed Astro, and each vertical ships *one*
|
|
15
|
+
shape of its solution. Deploy it as-is when the brief doesn't contradict it. When the brief
|
|
16
|
+
asks for something that shape doesn't express — or once the site exists and the work turns to
|
|
17
|
+
managing or extending it — that's `wix-docs` and `wix-manage`, not a workaround here.
|
|
18
|
+
|
|
14
19
|
## Relationship to sibling skills
|
|
15
20
|
|
|
16
21
|
| Skill | Use when |
|
|
@@ -36,13 +41,14 @@ re-litigate them.
|
|
|
36
41
|
this — nothing to wire by hand.
|
|
37
42
|
- **Data as-is; presentation is yours.** The data layer, hooks, and cart chrome are wired
|
|
38
43
|
as-is — never rewrite their internals, re-route them through API routes, or re-derive a
|
|
39
|
-
request shape.
|
|
40
|
-
`wix-docs
|
|
41
|
-
|
|
42
|
-
privileged operation belongs in a validated server endpoint — see
|
|
43
|
-
`references/shared/CUSTOM_OPERATIONS.md`. The presentation
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
request shape. When the brief needs something they don't express, read the shipped file that
|
|
45
|
+
owns it and confirm the contract with `wix-docs`; never infer one from generated SDK types,
|
|
46
|
+
package files, or `node_modules`. A normal caller-permitted operation belongs in a new
|
|
47
|
+
data-layer function. A privileged operation belongs in a validated server endpoint — see
|
|
48
|
+
`references/shared/CUSTOM_OPERATIONS.md`. The presentation **doesn't ship**: the vertical's
|
|
49
|
+
INSTRUCTIONS names the surfaces you design and implement yourself on the shipped hooks,
|
|
50
|
+
with a skeleton carrying each surface's contract (for storefront: the shop and PDP pages
|
|
51
|
+
with their islands, and home).
|
|
46
52
|
- **Never mock, fail loudly, purchases via Wix.** Live data or an honest empty state; surfaced
|
|
47
53
|
errors, not swallowed ones; checkout/purchase always through the Wix redirect session.
|
|
48
54
|
- **Optional capabilities are deployed from the plan.** A vertical can opt into a shared
|
|
@@ -70,6 +76,17 @@ re-litigate them.
|
|
|
70
76
|
`--vertical` is required and picks which shipped code deploys AND which seed runs — use
|
|
71
77
|
the vertical you resolved from the Verticals table.
|
|
72
78
|
|
|
79
|
+
fast-path scaffolds with `--skip-git`: it composes its own steps and leaves version control to
|
|
80
|
+
you / the enclosing repo, so it does **not** create the scaffold's usual git repo + initial
|
|
81
|
+
commit (which would otherwise become a nested-repo gitlink if the project lands inside a repo).
|
|
82
|
+
|
|
83
|
+
Optional `--flatten`: by default the project is created in a **new subfolder** (named after the
|
|
84
|
+
business). Pass `--flatten` to create it **directly in the current directory** instead — a plain
|
|
85
|
+
move of the scaffold's files up into the CWD, done **before** the dependency install starts so
|
|
86
|
+
nothing is half-moved (and since git was skipped, there's no nested repo to reconcile). Use it
|
|
87
|
+
when bootstrapping into an existing repo that must stay a single flat tree; otherwise omit it
|
|
88
|
+
and keep the subfolder.
|
|
89
|
+
|
|
73
90
|
It emits one JSON event per line and returns in **~35s**: **scaffolds** the project,
|
|
74
91
|
**deploys** the shipped code (patching `package.json` with every dependency the code
|
|
75
92
|
imports, and placing the pre-resolved lockfile), then **starts two detached background
|
|
@@ -156,6 +173,7 @@ references/<vertical>/
|
|
|
156
173
|
app-astro/ # Astro overlay importing ONLY from the core:
|
|
157
174
|
pages/… # SSR fetch → DTO props → client:load islands; item pages carry
|
|
158
175
|
# wixMetadata + <SEO.Tags>; chrome islands are client:only
|
|
176
|
+
# (storefront ships no pages — its INSTRUCTIONS carries their skeletons)
|
|
159
177
|
layouts/… # (reuse SiteLayout when it fits)
|
|
160
178
|
seed/ # seed-<vertical>.mjs (REST, mints its own CLI token) + SEED.md
|
|
161
179
|
```
|
|
@@ -15,7 +15,7 @@ already reading the raw text, don't fetch it again.
|
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
17
|
curl -fsSL -O https://www.wix.com/skills/headless/entry/bootstrap.mjs && node bootstrap.mjs
|
|
18
|
-
CI=1 npx skills@latest add wix/skills
|
|
18
|
+
CI=1 npx skills@latest add wix/skills --skill wix-headless-fast --skill wix-docs --skill wix-manage --yes
|
|
19
19
|
# then open and follow: .agents/skills/wix-headless-fast/SKILL.md
|
|
20
20
|
```
|
|
21
21
|
|
|
@@ -50,15 +50,14 @@ version, install or upgrade Node first — do **not** work around it:
|
|
|
50
50
|
|
|
51
51
|
## Phase 1 — Run the bootstrap (deterministic, shared)
|
|
52
52
|
|
|
53
|
-
Download and run the shared bootstrap script
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
when a login is actually needed does it pause on `awaiting_user`; surface the URL + code and
|
|
57
|
-
let it keep running until the login completes.
|
|
53
|
+
Download and run the shared bootstrap script — an ordinary foreground command that exits on
|
|
54
|
+
its own within seconds. It verifies the Wix CLI and handles login, emitting **one JSON event
|
|
55
|
+
per line** on stdout. **Run it and relay its events.**
|
|
58
56
|
|
|
59
57
|
The script is safe and inspectable: it only checks the Wix CLI via `npx` and drives
|
|
60
|
-
`wix login` (a device-code flow) — no other network calls,
|
|
61
|
-
if your sandbox flags
|
|
58
|
+
`wix login` (a device-code flow) — no other network calls, and the only files it writes are
|
|
59
|
+
the login's own output and pid under the OS temp dir. Read it first if your sandbox flags
|
|
60
|
+
externally-downloaded code.
|
|
62
61
|
|
|
63
62
|
```bash
|
|
64
63
|
# macOS/Linux:
|
|
@@ -74,20 +73,30 @@ node bootstrap.mjs
|
|
|
74
73
|
| Event | What to do |
|
|
75
74
|
|---|---|
|
|
76
75
|
| `cli_ok` | Wix CLI reachable — continue. |
|
|
77
|
-
| `awaiting_user` (`verificationUri`, `userCode`) |
|
|
76
|
+
| `awaiting_user` (`verificationUri`, `userCode`, `message`) | The script has exited and the next step is the user's. Send them `message` as-is; the login keeps running on its own. |
|
|
78
77
|
| `logged_in` / `success` | Login done — continue. |
|
|
79
78
|
| `cli_unreachable` / `login_failed` (with `detail`) | Stop and show the user the `detail`. **Do not** improvise a parallel setup by hand. |
|
|
80
79
|
|
|
81
|
-
|
|
80
|
+
On `awaiting_user`, run the script again once the user says they've logged in: it reports
|
|
81
|
+
`logged_in` and you continue. Re-running while they're still in the browser is harmless — it
|
|
82
|
+
returns the same code rather than issuing a new one.
|
|
82
83
|
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
## Phase 2 — Install the skills and hand off
|
|
85
|
+
|
|
86
|
+
Install the skill and its two companions (`CI=1` forces plain non-interactive CLI output —
|
|
87
|
+
keep it on every Wix CLI command). Repeat `--skill` per skill; a comma-separated list is not
|
|
88
|
+
parsed:
|
|
85
89
|
|
|
86
90
|
```bash
|
|
87
|
-
CI=1 npx skills@latest add wix/skills
|
|
91
|
+
CI=1 npx skills@latest add wix/skills \
|
|
92
|
+
--skill wix-headless-fast --skill wix-docs --skill wix-manage --yes
|
|
88
93
|
```
|
|
89
94
|
|
|
90
|
-
|
|
95
|
+
- **`wix-headless-fast`** — the build itself.
|
|
96
|
+
- **`wix-docs`** — the API reference the playbooks defer to for any contract they don't cover.
|
|
97
|
+
- **`wix-manage`** — management recipes, for admin work on the site after it exists.
|
|
98
|
+
|
|
99
|
+
They land under `.agents/skills/`. Then **open
|
|
91
100
|
`.agents/skills/wix-headless-fast/SKILL.md` and follow it** — it owns the rest of the run:
|
|
92
101
|
resolve the stack, scaffold, deploy the shipped code, seed, build the brand layer, release.
|
|
93
102
|
(If the request needs a vertical the skill doesn't ship yet — see its SKILL.md § Verticals —
|
|
@@ -4,15 +4,18 @@
|
|
|
4
4
|
// or for connect/iterate runs (which must NOT scaffold and don't use this script).
|
|
5
5
|
//
|
|
6
6
|
// node <SKILL_ROOT>/install/fast-path.mjs --business-name "<Brand>" --plan plan.json \
|
|
7
|
-
// --vertical <storefront|bookings|…> [--stack astro] [--folder-name <npm-safe-name>]
|
|
7
|
+
// --vertical <storefront|bookings|…> [--stack astro] [--folder-name <npm-safe-name>] [--flatten]
|
|
8
|
+
//
|
|
9
|
+
// --flatten (opt-in): leave the project directly in the current directory instead of a subfolder,
|
|
10
|
+
// moving it before the background install starts. Default off — the normal output is a subfolder.
|
|
8
11
|
//
|
|
9
12
|
// It emits ONE JSON event per line and exits in ~35s with BOTH long steps — the dependency
|
|
10
13
|
// install AND the seed — running detached in the background (logs + completion markers
|
|
11
14
|
// reported in the final event), so the caller can build the brand layer while they finish.
|
|
12
15
|
// Steps: scaffold (Wix CLI; requires a logged-in session) → deploy shipped code + deps +
|
|
13
|
-
// lockfile → start `npm ci || npm install` detached → start the seed detached.
|
|
16
|
+
// lockfile → pin AGENTS.md → start `npm ci || npm install` detached → start the seed detached.
|
|
14
17
|
import { spawn, spawnSync } from "node:child_process";
|
|
15
|
-
import { existsSync, openSync, readFileSync, readdirSync } from "node:fs";
|
|
18
|
+
import { existsSync, openSync, readFileSync, readdirSync, renameSync, rmSync } from "node:fs";
|
|
16
19
|
import { dirname, join, resolve } from "node:path";
|
|
17
20
|
import { fileURLToPath } from "node:url";
|
|
18
21
|
|
|
@@ -34,6 +37,8 @@ const businessName = flag("business-name");
|
|
|
34
37
|
const planPath = flag("plan");
|
|
35
38
|
const vertical = flag("vertical");
|
|
36
39
|
const stack = flag("stack") ?? "astro";
|
|
40
|
+
// Opt-in: leave the project in the current directory (flat) instead of a subfolder.
|
|
41
|
+
const flatten = argv.includes("--flatten");
|
|
37
42
|
// --vertical is REQUIRED: a defaulted vertical deploys the wrong code and runs the wrong
|
|
38
43
|
// seed against the plan — fail loudly with the discovered choices instead.
|
|
39
44
|
const knownVerticals = readdirSync(join(SKILL_ROOT, "references"), { withFileTypes: true })
|
|
@@ -51,7 +56,10 @@ const plan = JSON.parse(readFileSync(planPath, "utf8"));
|
|
|
51
56
|
const folderName =
|
|
52
57
|
flag("folder-name") ??
|
|
53
58
|
businessName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
54
|
-
|
|
59
|
+
// `let`: after deploy we flatten the scaffold up into the repo root and repoint
|
|
60
|
+
// projectDir there, so the detached install/seed and every reported path use the
|
|
61
|
+
// final location.
|
|
62
|
+
let projectDir = resolve(process.cwd(), folderName);
|
|
55
63
|
|
|
56
64
|
// ---- 1 · scaffold -------------------------------------------------------------------------------
|
|
57
65
|
if (existsSync(join(process.cwd(), "wix.config.json"))) {
|
|
@@ -63,9 +71,14 @@ if (existsSync(join(projectDir, "wix.config.json"))) {
|
|
|
63
71
|
emit("scaffolding", { folder: folderName });
|
|
64
72
|
const scaffold = spawnSync(
|
|
65
73
|
"npm",
|
|
74
|
+
// --skip-git: this wrapper composes its own steps and leaves version control to
|
|
75
|
+
// the caller / the enclosing repo; the scaffold's own `git init` + "Initial
|
|
76
|
+
// commit" is noise here, and becomes a nested-repo (submodule gitlink) hazard
|
|
77
|
+
// if the project is later placed inside an existing repo. --skip-install for the
|
|
78
|
+
// same reason: deps install in a detached step below.
|
|
66
79
|
["create", "@wix/new@latest", "--", "headless",
|
|
67
80
|
"--folder-name", folderName, "--business-name", businessName,
|
|
68
|
-
"--site-template", "--skip-install", "--no-publish"],
|
|
81
|
+
"--site-template", "--skip-install", "--skip-git", "--no-publish"],
|
|
69
82
|
{ env: { ...process.env, CI: "1" }, encoding: "utf8", timeout: 300_000 },
|
|
70
83
|
);
|
|
71
84
|
if (scaffold.status !== 0 || !existsSync(join(projectDir, "wix.config.json"))) {
|
|
@@ -95,6 +108,36 @@ try { deployResult = JSON.parse(deploy.stdout); } catch { /* keep going with raw
|
|
|
95
108
|
if (deployResult.error) fail("deploy", deployResult.error);
|
|
96
109
|
emit("deployed", deployResult);
|
|
97
110
|
|
|
111
|
+
// ---- 2b · record what this project is, for later sessions ---------------------------------------
|
|
112
|
+
const pin = spawnSync(
|
|
113
|
+
"node",
|
|
114
|
+
[join(SKILL_ROOT, "install", "pin-agents-md.mjs"), "--vertical", vertical, "--stack", stack],
|
|
115
|
+
{ cwd: projectDir, encoding: "utf8", timeout: 10_000 },
|
|
116
|
+
);
|
|
117
|
+
try { emit("agents_md", JSON.parse(pin.stdout)); } catch { /* never block the build on the note */ }
|
|
118
|
+
|
|
119
|
+
// ---- 2c · optional --flatten: move the scaffold into the current directory ----------------------
|
|
120
|
+
// OFF by default — the normal output is a self-contained subfolder, unchanged. Opt in when the
|
|
121
|
+
// caller wants the project directly in the current directory (e.g. bootstrapping into an existing
|
|
122
|
+
// repo that must stay one flat tree). Done HERE, before the detached install below, on purpose:
|
|
123
|
+
// no node_modules exists yet, so the move is instant and cannot collide with a running install —
|
|
124
|
+
// the failure mode when a project is flattened by hand after the background install has started.
|
|
125
|
+
// A pure move: the scaffold was created with --skip-git (above), so there is no nested repo to
|
|
126
|
+
// reconcile — git is whatever the destination already is.
|
|
127
|
+
const targetDir = process.cwd();
|
|
128
|
+
if (flatten && projectDir !== targetDir) {
|
|
129
|
+
try {
|
|
130
|
+
for (const entry of readdirSync(projectDir)) {
|
|
131
|
+
renameSync(join(projectDir, entry), join(targetDir, entry));
|
|
132
|
+
}
|
|
133
|
+
rmSync(projectDir, { recursive: true, force: true });
|
|
134
|
+
projectDir = targetDir;
|
|
135
|
+
emit("flattened", { into: targetDir });
|
|
136
|
+
} catch (e) {
|
|
137
|
+
fail("flatten", e?.stack || e);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
98
141
|
// ---- 3 · start the dependency install, detached --------------------------------------------------
|
|
99
142
|
const installLog = join(projectDir, "npm-install.log");
|
|
100
143
|
const logFd = openSync(installLog, "a");
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Record what this project is in its agent instruction files, so a later session — which has
|
|
2
|
+
// none of the build conversation and may never open SKILL.md — still knows the Wix site is the
|
|
3
|
+
// source of truth and which skill answers which kind of question.
|
|
4
|
+
//
|
|
5
|
+
// node <SKILL_ROOT>/install/pin-agents-md.mjs --vertical <name> [--stack astro]
|
|
6
|
+
//
|
|
7
|
+
// Run from the project root. Kept out of deploy.mjs on purpose: "copy a vertical's files" and
|
|
8
|
+
// "record the project's ground rules" are separate concerns with separate reasons to change.
|
|
9
|
+
// APPENDS to each file — a template's or the user's own content is never overwritten — and is
|
|
10
|
+
// idempotent via the heading sentinel, so re-running fast-path.mjs writes at most once.
|
|
11
|
+
// Project facts that live in the repo (site id, app id) are NOT copied here; they would go
|
|
12
|
+
// stale against wix.config.json, which is the one place that owns them.
|
|
13
|
+
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
|
|
16
|
+
const argv = process.argv.slice(2);
|
|
17
|
+
const flag = (n, d) => { const i = argv.indexOf(`--${n}`); return i >= 0 ? argv[i + 1] : d; };
|
|
18
|
+
|
|
19
|
+
const vertical = flag("vertical");
|
|
20
|
+
const stack = flag("stack", "astro");
|
|
21
|
+
// "astro" means the Wix-managed flavour (ambient auth), which is worth naming. Any other stack
|
|
22
|
+
// is echoed as given rather than mapped, so adding one can't silently mislabel the project.
|
|
23
|
+
const frontend = stack === "astro" ? "Wix-managed Astro" : stack.charAt(0).toUpperCase() + stack.slice(1);
|
|
24
|
+
|
|
25
|
+
const SENTINEL = "## This app — a Wix Headless frontend";
|
|
26
|
+
const note = `
|
|
27
|
+
${SENTINEL}
|
|
28
|
+
|
|
29
|
+
A ${frontend} frontend for a live Wix site; \`wix.config.json\` identifies the site. Built on
|
|
30
|
+
the **${vertical}** Wix business solution — more can be added to this same app. **The Wix site
|
|
31
|
+
owns the content and commerce** — read and write it through the Wix APIs, never hardcode what
|
|
32
|
+
it holds.
|
|
33
|
+
|
|
34
|
+
The Wix skills are installed at \`.agents/skills/\` — read them from that exact path:
|
|
35
|
+
|
|
36
|
+
- **\`wix-headless-fast\`** — the code already deployed in \`src/\` and how to extend it. Every
|
|
37
|
+
business solution it ships sits under \`references/<solution>/\`: \`INSTRUCTIONS.md\` is that
|
|
38
|
+
solution's playbook, \`seed/\` creates its content. To add one to this app, run from the
|
|
39
|
+
project root:
|
|
40
|
+
\`node .agents/skills/wix-headless-fast/install/deploy.mjs <solution> --stack ${stack}\`
|
|
41
|
+
- **\`wix-docs\`** — the Wix API reference. Confirm any endpoint, request shape, field, or enum
|
|
42
|
+
here before writing code against it, frontend or backend. Never infer a contract from
|
|
43
|
+
generated SDK types or \`node_modules\`.
|
|
44
|
+
- **\`wix-manage\`** — recipes for managing the live site itself, as opposed to changing this
|
|
45
|
+
codebase. No recipe for the task? Fall back to \`wix-docs\`.
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
// Both files, independently — a project may have either, both, or neither, and agents differ
|
|
49
|
+
// in which one they read.
|
|
50
|
+
const pinned = ["AGENTS.md", "CLAUDE.md"].map((name) => {
|
|
51
|
+
const file = join(process.cwd(), name);
|
|
52
|
+
const existing = existsSync(file) ? readFileSync(file, "utf8") : "";
|
|
53
|
+
const wrote = !existing.includes(SENTINEL);
|
|
54
|
+
if (wrote) appendFileSync(file, (existing && !existing.endsWith("\n") ? "\n" : "") + note);
|
|
55
|
+
return { file: name, wrote, created: wrote && !existing };
|
|
56
|
+
});
|
|
57
|
+
console.log(JSON.stringify({ event: "agents_md", pinned }));
|
|
@@ -17,9 +17,9 @@ custom business write, or a third-party service.
|
|
|
17
17
|
|
|
18
18
|
## The research rule
|
|
19
19
|
|
|
20
|
-
Read the relevant shipped playbook first. For
|
|
21
|
-
official method or feature guide, then implement that contract. One documentation
|
|
22
|
-
one type check are enough before coding. Investigate further only after a concrete error.
|
|
20
|
+
Read the relevant shipped playbook first. For anything it doesn't cover, use `wix-docs` to
|
|
21
|
+
read the official method or feature guide, then implement that contract. One documentation
|
|
22
|
+
lookup and one type check are enough before coding. Investigate further only after a concrete error.
|
|
23
23
|
|
|
24
24
|
## User-created public content
|
|
25
25
|
|