@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.
Files changed (61) hide show
  1. package/README.md +1 -0
  2. package/package.json +2 -1
  3. package/skills/wix-app/SKILL.md +4 -1
  4. package/skills/wix-app/references/BUSINESS_MANAGER_THEME.md +108 -0
  5. package/skills/wix-app/references/BUSINESS_MANAGER_TOKENS.md +81 -0
  6. package/skills/wix-app/references/DASHBOARD_MODAL.md +41 -0
  7. package/skills/wix-app/references/DASHBOARD_PAGE.md +2 -0
  8. package/skills/wix-app/references/DASHBOARD_PLUGIN.md +19 -4
  9. package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +64 -50
  10. package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE.md +5 -4
  11. package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_ROUTER.md +8 -7
  12. package/skills/wix-app/references/dashboard-page/DYNAMIC_PARAMETERS.md +21 -38
  13. package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +4 -3
  14. package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +28 -25
  15. package/skills/wix-app/references/editor-react-component/BRANDED-COMPONENTS.md +8 -2
  16. package/skills/wix-app/references/editor-react-component/COMPONENT-CONTRACT.md +12 -14
  17. package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +4 -3
  18. package/skills/wix-app/references/editor-react-component/DESIGN-STATES.md +66 -9
  19. package/skills/wix-app/references/editor-react-component/EDITOR-EXTENSION-CONFIGURATION.md +25 -0
  20. package/skills/wix-app/references/editor-react-component/OVERFLOW.md +146 -0
  21. package/skills/wix-app/references/editor-react-component/PARTS.md +6 -0
  22. package/skills/wix-app/scripts/scan-a11y-eslint.cjs +25 -1
  23. package/skills/wix-headless/SKILL.md +0 -1
  24. package/skills/wix-headless/references/CAPABILITIES.md +3 -3
  25. package/skills/wix-headless/references/DISCOVERY.md +1 -1
  26. package/skills/wix-headless/references/SDK_HANDOFF.md +1 -1
  27. package/skills/wix-headless/references/SEED.md +1 -1
  28. package/skills/wix-headless/references/SETUP.md +1 -1
  29. package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-reservations.md +1 -1
  30. package/skills/wix-headless/references/inline-recipes/setup-restaurant-experiences.md +2 -2
  31. package/skills/wix-headless/references/managed/AUTHENTICATION.md +3 -14
  32. package/skills/wix-headless/scripts/bootstrap.mjs +156 -0
  33. package/skills/{wix-headless-fast → wix-headless-kit}/SKILL.md +83 -64
  34. package/skills/{wix-headless-fast → wix-headless-kit}/cold-start/bootstrap.mjs +2 -2
  35. package/skills/{wix-headless-fast → wix-headless-kit}/cold-start/cold-start.md +26 -35
  36. package/skills/wix-headless-kit/guides/capabilities.md +76 -0
  37. package/skills/wix-headless-kit/guides/feedback.md +98 -0
  38. package/skills/{wix-headless-fast → wix-headless-kit}/guides/migration.md +20 -9
  39. package/skills/{wix-headless-fast → wix-headless-kit}/install/AGENTS.md +5 -2
  40. package/skills/{wix-headless-fast → wix-headless-kit}/install/attach.mjs +5 -2
  41. package/skills/wix-headless-kit/install/bootstrap.mjs +156 -0
  42. package/skills/{wix-headless-fast → wix-headless-kit}/install/check.mjs +2 -2
  43. package/skills/wix-headless-kit/install/context.mjs +169 -0
  44. package/skills/{wix-headless-fast → wix-headless-kit}/install/deploy.mjs +17 -4
  45. package/skills/{wix-headless-fast → wix-headless-kit}/install/setup.mjs +89 -55
  46. package/skills/{wix-headless-fast → wix-headless-kit}/install/templates.mjs +1 -1
  47. package/skills/wix-manage/SKILL.md +2 -8
  48. package/skills/wix-manage/references/cms/cms-dashboard-navigation.md +2 -2
  49. package/skills/wix-manage/references/cms/cms-data-items-crud.md +263 -31
  50. package/skills/wix-manage/references/cms/cms-publishing-flow.md +15 -15
  51. package/skills/wix-manage/references/cms/cms-schema-management.md +58 -7
  52. package/skills/wix-manage/references/sites/query-sites.md +1 -8
  53. package/skills/wix-auth/SKILL.md +0 -12
  54. package/skills/wix-auth/references/device-flow.md +0 -140
  55. package/skills/wix-headless-fast/install/context.mjs +0 -126
  56. package/skills/wix-manage/references/cms/cms-data-operations-extended.md +0 -263
  57. package/skills/wix-manage/references/cms/cms-references-and-relationships.md +0 -162
  58. /package/skills/{wix-headless-fast → wix-headless-kit}/guides/existing-site.md +0 -0
  59. /package/skills/{wix-headless-fast → wix-headless-kit}/guides/reference-mode.md +0 -0
  60. /package/skills/{wix-headless-fast → wix-headless-kit}/install/agents-md.mjs +0 -0
  61. /package/skills/{wix-headless-fast → wix-headless-kit}/install/lock.mjs +0 -0
@@ -17,8 +17,8 @@ call is memory, and an empty or error reply to it says nothing about the site.
17
17
 
18
18
  ## Skills
19
19
 
20
- Installed at `.agents/skills/`. If missing, restore with:
21
- `CI=1 npx skills@latest add wix/skills --skill {{SKILL}} --skill wix-docs --skill wix-manage --yes`
20
+ Installed at `.agents/skills/` (by the Wix plugin, or by
21
+ `npx skills add wix/skills --skill {{SKILL}} --skill wix-docs --skill wix-manage`).
22
22
 
23
23
  - `{{SKILL}}` — the code in this app and how to extend it. The shipped code sits in
24
24
  `.agents/skills/{{SKILL}}/templates/`, fetched once from the skill's repository and kept with this
@@ -29,6 +29,9 @@ Installed at `.agents/skills/`. If missing, restore with:
29
29
  site holds for it, with the documentation URL of every call it made. A code change ends with a release; the live URL shows it, the dev server does not count.
30
30
  `node .agents/skills/{{SKILL}}/install/check.mjs` says whether the skill or its templates have a newer
31
31
  version and prints the update commands; an update touches the skill folder or `templates/`, never `src/`.
32
+ When Wix gets in the way of a change here (a confusing error, a doc gap, a workaround you had to
33
+ invent), offer to relay it to Wix per `.agents/skills/{{SKILL}}/guides/feedback.md`; send only after
34
+ an explicit yes.
32
35
  - `wix-docs` — how to discover the Wix APIs and their docs: search first, then read only the page
33
36
  you need. Every endpoint, body, field and enum you did not get from the deployed code or a recipe
34
37
  is confirmed there before you write it.
@@ -45,7 +45,7 @@ import { cpSync, existsSync, mkdirSync, openSync, readFileSync, writeFileSync }
45
45
  import { basename, dirname, join, resolve } from "node:path";
46
46
  import { fileURLToPath } from "node:url";
47
47
  import { writeAgentsMd } from "./agents-md.mjs";
48
- import { siteContext } from "./context.mjs";
48
+ import { frontendPresent, siteContext } from "./context.mjs";
49
49
  import { listVerticals, templatesDir } from "./templates.mjs";
50
50
 
51
51
  const SKILL_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
@@ -76,7 +76,10 @@ const subfolder = argv.includes("--subfolder");
76
76
  const cwd = process.cwd();
77
77
  const has = (p) => existsSync(join(cwd, p));
78
78
  const cwdConfig = has("wix.config.json") ? JSON.parse(readFileSync(join(cwd, "wix.config.json"), "utf8")) : null;
79
- const hasProject = has("package.json") || has("index.html");
79
+ // A frontend here: a package.json, an index.html at the root, or an index.html in the folder the
80
+ // config's site.outputDirectory names (a static site laid out for release) — install/context.mjs.
81
+ const fp = frontendPresent(cwd);
82
+ const hasProject = fp.packageJson || fp.rootIndex || fp.outputIndex;
80
83
  // A migration preview (wix.config.json names a deploy-only site, .env.local names the site being
81
84
  // migrated) is already provisioned: its app, hosting and credentials came with the folder. Nothing
82
85
  // here applies — setup.mjs takes it (SKILL.md step 3).
@@ -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-kit-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-kit-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());
@@ -24,7 +24,7 @@ const { repo } = installSource();
24
24
  const out = { skill: {}, templates: {}, update: {} };
25
25
 
26
26
  // ---- the repository: one clone of its history without file contents (small), read for both layers
27
- const tmp = mkdtempSync(join(tmpdir(), "wix-headless-fast-check-"));
27
+ const tmp = mkdtempSync(join(tmpdir(), "wix-headless-kit-check-"));
28
28
  const clone = git(["clone", "--quiet", "--filter=blob:none", "--no-checkout", "--single-branch", repo, tmp]);
29
29
  if (clone.status !== 0) {
30
30
  rmSync(tmp, { recursive: true, force: true });
@@ -80,7 +80,7 @@ rmSync(tmp, { recursive: true, force: true });
80
80
  // ---- what would act on each ------------------------------------------------------------------
81
81
  const rel = relative(process.cwd(), SKILL_ROOT) || ".";
82
82
  out.update = {
83
- skill: `CI=1 npx skills@latest update ${skillName} --yes (or: gh skill update ${skillName})`,
83
+ skill: `update ${skillName} the way it was installed (the plugin's update, or the skills CLI's)`,
84
84
  templates: `node ${rel}/install/templates.mjs --refresh (then commit the changed files under ${rel}/templates)`,
85
85
  note: "neither touches src/; a shipped fix reaches the project by comparing its copy with the template",
86
86
  };
@@ -0,0 +1,169 @@
1
+ // The site context of a project folder — the one place that says which site a call targets.
2
+ //
3
+ // node <SKILL_ROOT>/install/context.mjs [--refresh] [--no-pull] (prints one JSON object)
4
+ //
5
+ // Two identities live in a Wix project and are the same site almost always:
6
+ // deploy — `wix.config.json` (`siteId`, `appId`): where `wix release` uploads. The CLI's business.
7
+ // content — `.env.local` (`WIX_CLIENT_ID`, what `wix env pull` writes): the app the SDK client
8
+ // runs as, on every stack (the Astro integration reads WIX_CLIENT_ID from the env and
9
+ // never the config; the other stacks get it copied into src/wix/config.ts by deploy.mjs).
10
+ // They differ on a MIGRATION PREVIEW: a project whose config points at a fresh site created only to
11
+ // host the deployment, while `.env.local` carries the credentials of the site being migrated (the
12
+ // parent) and says so. Then every admin, discovery and seed call targets the parent, the SDK client
13
+ // is the parent's, and only the release goes to the child. Completing the migration is a CLI step
14
+ // that does not exist yet; this module only reads the state.
15
+ //
16
+ // `env pull` runs here when `.env.local` is missing (or --refresh): it is the source of the content
17
+ // identity, and the Astro build refuses to run without it. Non-interactive (CI=1); a failure is
18
+ // reported in `pullError` and the config's ids stand in, so a caller can still work offline.
19
+ import { spawnSync } from "node:child_process";
20
+ import { existsSync, readFileSync } from "node:fs";
21
+ import { join } from "node:path";
22
+
23
+ // The variables the editor-to-headless migration writes into the child project's `prod`
24
+ // environment (headless-bo, editorMigrations/environmentMetadata.ts), which `wix env pull` copies
25
+ // into `.env.local` unchanged. The names are the platform's and are spelled once, here.
26
+ export const ENV = {
27
+ clientId: "WIX_CLIENT_ID",
28
+ parentSiteId: "EDITOR_MIGRATION_PARENT_SITE_ID",
29
+ /** ACTIVE while the migration is under way; COMPLETED once the parent serves the child's frontend. */
30
+ status: "EDITOR_MIGRATION_STATUS",
31
+ childSiteId: "EDITOR_MIGRATION_CHILD_SITE_ID",
32
+ childAppId: "EDITOR_MIGRATION_CHILD_APP_ID",
33
+ };
34
+
35
+ /** KEY=value lines of a dotenv file, quotes stripped; null when the file is absent. */
36
+ export function readEnvFile(file) {
37
+ if (!existsSync(file)) return null;
38
+ const out = {};
39
+ for (const raw of readFileSync(file, "utf8").split(/\r?\n/)) {
40
+ const line = raw.trim();
41
+ if (!line || line.startsWith("#")) continue;
42
+ const eq = line.indexOf("=");
43
+ if (eq === -1) continue;
44
+ const key = line.slice(0, eq).trim().replace(/^export\s+/, "");
45
+ let value = line.slice(eq + 1).trim();
46
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
47
+ value = value.slice(1, -1).replace(/\\"/g, '"');
48
+ }
49
+ out[key] = value;
50
+ }
51
+ return out;
52
+ }
53
+
54
+ export function readWixConfig(cwd) {
55
+ const file = join(cwd, "wix.config.json");
56
+ if (!existsSync(file)) return null;
57
+ try { return JSON.parse(readFileSync(file, "utf8")); } catch { return null; }
58
+ }
59
+
60
+ const ENV_PULL = ["-y", "@wix/cli@latest", "env", "pull"];
61
+ const runPull = (dir) => spawnSync("npx", ENV_PULL, { cwd: dir, env: { ...process.env, CI: "1" }, encoding: "utf8", timeout: 180_000 });
62
+
63
+ /**
64
+ * `wix env pull` into `cwd/.env.local`, in place. Every project shape gets the command since Wix CLI
65
+ * 1.1.253 (2026-09-29): before it, a config with `site.outputDirectory` (the static stack, a site
66
+ * published through the drop flow) landed in a limited command set without `env`, and this pulled
67
+ * through a temp copy of the config. Non-interactive; a failure comes back as `error`.
68
+ */
69
+ export function pullEnv(cwd) {
70
+ const envFile = join(cwd, ".env.local");
71
+ const r = runPull(cwd);
72
+ if (r.status === 0 && existsSync(envFile)) return { ok: true, via: "in place" };
73
+ return { ok: false, error: (r.stderr || r.stdout || "env pull produced no .env.local — is the Wix CLI logged in? (npx @wix/cli@latest whoami)").trim().slice(-400) };
74
+ }
75
+
76
+ /**
77
+ * Whether a frontend exists in this folder: a `package.json` (a bundled project), an `index.html` at
78
+ * the root (plain pages), or an `index.html` inside the folder the config's `site.outputDirectory`
79
+ * names (a static site laid out for release, pages under site/). Nothing else counts — not this
80
+ * skill's code, not the skills folder, not AGENTS.md: those say who ran here, not what is here.
81
+ */
82
+ export function frontendPresent(cwd = process.cwd()) {
83
+ const has = (p) => existsSync(join(cwd, p));
84
+ const outDir = (readWixConfig(cwd)?.site?.outputDirectory ?? "").replace(/^\.\//, "").replace(/\/$/, "");
85
+ return {
86
+ packageJson: has("package.json"),
87
+ rootIndex: has("index.html"),
88
+ outputIndex: !!outDir && outDir !== "." && has(join(outDir, "index.html")),
89
+ outputDirectory: outDir || null,
90
+ };
91
+ }
92
+
93
+ /**
94
+ * What the folder IS, from five file facts — the one classification every script and SKILL.md
95
+ * step 3 share: wix.config.json, its site.outputDirectory, the migration variables in .env.local,
96
+ * package.json, index.html. `migrationActive` comes from siteContext (it needs `.env.local`).
97
+ *
98
+ * empty nothing that reads as a project → setup CREATE: the run makes the site, seeds the plan
99
+ * project a frontend, no wix.config.json → setup ADOPT: `init` gives it a new, empty site, seeds the plan
100
+ * config-only a config, no frontend → attach.mjs on the config's site; nothing seeded
101
+ * migration a config whose .env.local declares an ACTIVE editor migration, with or without the blank
102
+ * Astro starter the download carries → setup MIGRATE; nothing seeded. Decided before the
103
+ * frontend test: the starter's package.json must not read as a project to iterate on
104
+ * wix-project a config AND a frontend (package.json, or index.html in the output folder) → iterate:
105
+ * never scaffold, init or reseed; deploy.mjs adds a solution, edits, release
106
+ * published-static a config, index.html at the ROOT, no package.json, no laid-out output folder (a site
107
+ * published through the drop flow and downloaded) → setup makes site/ the upload and
108
+ * deploys the REST layer; the config's site, no init, nothing seeded
109
+ */
110
+ export function folderShape(cwd = process.cwd(), { migrationActive = false } = {}) {
111
+ const config = existsSync(join(cwd, "wix.config.json"));
112
+ const f = frontendPresent(cwd);
113
+ const next = {
114
+ empty: "setup.mjs --vertical <v> --business-name <brand> [--plan]: creates the site here and seeds the plan (CREATE)",
115
+ project: "setup.mjs --vertical <v> --stack <stack> [--plan]: init links the folder to a new, empty site, seeds the plan and deploys (ADOPT)",
116
+ "config-only": "attach.mjs: the site exists and has no frontend yet; read what it holds (the vertical's read-site.mjs) — nothing is seeded; the vertical's seed module with a plan only when the brief supplies or describes content",
117
+ migration: "setup.mjs (MIGRATE): the shipped code into the starter the download carries (or the composed template around a bare config); the site being migrated owns its content — guides/migration.md",
118
+ "wix-project": "iterate: never scaffold, init or reseed. deploy.mjs <vertical…> --stack <stack> adds a solution, then ONE npm install; file edits for a change; release. Read the site (read-site.mjs) before any seed module runs",
119
+ "published-static": "setup.mjs --vertical <v>: the config's site, no init; site/ becomes the upload and the REST layer lands in site/js/wix/; move the pages, styles and assets into site/. Nothing is seeded: read the site, then run the vertical's seed module with a plan when the brief gives content; release keeps the URL",
120
+ };
121
+ let shape;
122
+ if (!config) shape = f.packageJson || f.rootIndex ? "project" : "empty";
123
+ else if (migrationActive) shape = "migration";
124
+ else if (f.packageJson || f.outputIndex) shape = "wix-project";
125
+ else if (f.rootIndex) shape = "published-static";
126
+ else shape = "config-only";
127
+ return { shape, next: next[shape], facts: { config, ...f } };
128
+ }
129
+
130
+ /**
131
+ * `{ folder: { shape, next, tells }, deploy: { siteId, appId }, content: { siteId, clientId },
132
+ * migration: { active, parentSiteId }, env: { file, present, pulled }, pullError?, warnings: [] }`.
133
+ * `pull`: "auto" (default) pulls when `.env.local` is missing; true always; false never.
134
+ */
135
+ export function siteContext({ cwd = process.cwd(), pull = "auto" } = {}) {
136
+ const config = readWixConfig(cwd) ?? {};
137
+ const deploy = { siteId: config.siteId ?? config.projectId ?? null, appId: config.appId ?? null };
138
+ const envFile = join(cwd, ".env.local");
139
+ let pulled = false, pullError;
140
+ if (deploy.siteId && (pull === true || (pull === "auto" && !existsSync(envFile)))) {
141
+ const r = pullEnv(cwd);
142
+ if (r.ok) pulled = r.via;
143
+ else pullError = r.error;
144
+ }
145
+ const env = readEnvFile(envFile) ?? {};
146
+ const parentSiteId = env[ENV.parentSiteId] || null;
147
+ const status = env[ENV.status];
148
+ // ACTIVE (or a parent id with no status yet) is a migration under way; COMPLETED means the parent
149
+ // already serves this frontend and the project is an ordinary one again.
150
+ const active = !!parentSiteId && (status === undefined || status.trim().toUpperCase() === "ACTIVE");
151
+ const migration = { active, parentSiteId: active ? parentSiteId : null, ...(status ? { status: status.trim().toUpperCase() } : {}) };
152
+ const clientId = env[ENV.clientId] || deploy.appId;
153
+ const content = { siteId: active ? parentSiteId : deploy.siteId, clientId };
154
+ const warnings = [];
155
+ if (env[ENV.clientId] && deploy.appId && env[ENV.clientId] !== deploy.appId && !active && migration.status !== "COMPLETED") {
156
+ warnings.push(`.env.local ${ENV.clientId} differs from wix.config.json appId and no migration is declared — the SDK client runs as the env's app, the release goes to the config's site`);
157
+ }
158
+ if (active && migration.parentSiteId === deploy.siteId) {
159
+ warnings.push("the migration's parent site is the deploy site itself — nothing is being migrated");
160
+ }
161
+ return { folder: folderShape(cwd, { migrationActive: active }), deploy, content, migration, env: { file: envFile, present: Object.keys(env).length > 0, pulled }, ...(pullError ? { pullError } : {}), warnings };
162
+ }
163
+
164
+ // ---- CLI ----------------------------------------------------------------------------------------
165
+ if (process.argv[1] && /context\.mjs$/.test(process.argv[1])) {
166
+ const argv = process.argv.slice(2);
167
+ const pull = argv.includes("--no-pull") ? false : argv.includes("--refresh") ? true : "auto";
168
+ console.log(JSON.stringify(siteContext({ pull }), null, 2));
169
+ }
@@ -111,6 +111,18 @@ const VERTICAL_DEPS = {
111
111
  "@wix/essentials": "^1.0.6",
112
112
  },
113
113
  },
114
+ rentals: {
115
+ // A rental is a Bookings service with rentals-specific values: the same SDK modules as bookings.
116
+ core: {
117
+ "@wix/bookings": "^1.0.1650",
118
+ "@wix/auto_sdk_ecom_cart-v-2": "^1.0.192",
119
+ "@wix/redirects": "^1.0.125",
120
+ },
121
+ astro: {
122
+ "@wix/seo": "^1.0.79",
123
+ "@wix/essentials": "^1.0.6",
124
+ },
125
+ },
114
126
  blog: {
115
127
  core: {
116
128
  "@wix/blog": "^1.0.645",
@@ -247,7 +259,7 @@ if (siteSearch) {
247
259
  }
248
260
  // The generated file is data only; the shipped core resolves defaults (types, routes, labels) from it.
249
261
  const siteSearchConfigSource = () =>
250
- `// Generated by wix-headless-fast install/deploy.mjs from plan.capabilities.siteSearch.\n` +
262
+ `// Generated by wix-headless-kit install/deploy.mjs from plan.capabilities.siteSearch.\n` +
251
263
  `// Edit the plan and redeploy. Defaults for anything omitted are in search-core.ts (resolveConfig).\n` +
252
264
  `export const siteSearchPlan = ${JSON.stringify(siteSearch, null, 2)};\n`;
253
265
  // ---- static stack: the REST layer, composed flat and stripped ------------------------------------
@@ -401,7 +413,7 @@ if (uploadPolicies.length) {
401
413
  // The generated file is data only. The shipped endpoint owns validation and elevation.
402
414
  writeFileSync(
403
415
  configFile,
404
- `// Generated by wix-headless-fast install/deploy.mjs from plan.capabilities.mediaUpload.\n` +
416
+ `// Generated by wix-headless-kit install/deploy.mjs from plan.capabilities.mediaUpload.\n` +
405
417
  `// Edit the plan and redeploy; do not add browser-controlled policy selection here.\n` +
406
418
  `export const mediaUploadPolicies = ${JSON.stringify(uploadPolicies, null, 2)} as const;\n`,
407
419
  );
@@ -506,8 +518,9 @@ if (!requested.length) {
506
518
  }
507
519
 
508
520
  // ---- client ids ---------------------------------------------------------------------------------
509
- // The shared data client is ambient on managed Astro. Members are different: their shipped
510
- // custom credential flow always needs an explicit public OAuth client, including on Astro.
521
+ // The shared data client is ambient on managed Astro, and so is the members flow there (the
522
+ // integration's built-in /api/auth routes). WIX_MEMBERS_CLIENT_ID is still filled for projects whose
523
+ // members code predates the shared seam; the shipped code reads WIX_CLIENT_ID only.
511
524
  // --client-id, else the CONTENT app: .env.local's WIX_CLIENT_ID (what `wix env pull` writes — on a
512
525
  // migration preview the parent site's app, the one the Astro integration itself runs as), else the
513
526
  // config's appId. Never the config first: on a preview that is the deploy-only child app.