@wix/agent-skills 1.31.0 → 1.31.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-app/SKILL.md +7 -8
  3. package/skills/wix-app/references/DASHBOARD_PAGE.md +1 -1
  4. package/skills/wix-app/references/STORES_VERSIONING.md +1 -1
  5. package/skills/wix-app/references/WIX_PATTERNS_DOCS.md +11 -50
  6. package/skills/wix-app/references/dashboard-page/DATA_SOURCES.md +14 -10
  7. package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE.md +5 -8
  8. package/skills/wix-app/references/dashboard-page/TABLE_STATE.md +5 -4
  9. package/skills/wix-headless-kit/SKILL.md +31 -14
  10. package/skills/wix-headless-kit/guides/api-run.md +116 -31
  11. package/skills/wix-headless-kit/guides/existing-site.md +9 -2
  12. package/skills/wix-headless-kit/guides/reference-mode.md +9 -1
  13. package/skills/wix-headless-kit/install/AGENTS.md +10 -7
  14. package/skills/wix-headless-kit/install/attach.mjs +35 -30
  15. package/skills/wix-headless-kit/install/background.mjs +32 -0
  16. package/skills/wix-headless-kit/install/bootstrap.mjs +28 -11
  17. package/skills/wix-headless-kit/install/check.mjs +2 -2
  18. package/skills/wix-headless-kit/install/context.mjs +62 -8
  19. package/skills/wix-headless-kit/install/deploy.mjs +36 -11
  20. package/skills/wix-headless-kit/install/env-ignore.mjs +16 -0
  21. package/skills/wix-headless-kit/install/install.mjs +95 -0
  22. package/skills/wix-headless-kit/install/pins.json +1 -1
  23. package/skills/wix-headless-kit/install/proc.mjs +46 -0
  24. package/skills/wix-headless-kit/install/setup.mjs +18 -27
  25. package/skills/wix-headless-kit/install/templates.mjs +4 -3
  26. package/skills/wix-headless-templates/blog/app/components/blog/RichContent.tsx +3 -2
  27. package/skills/wix-headless-templates/blog/project/package-lock.json +1287 -1299
  28. package/skills/wix-headless-templates/blog/project/package.json +1 -1
  29. package/skills/wix-headless-templates/blog/project/src/components/blog/RichContent.tsx +3 -2
  30. package/skills/wix-headless-templates/blog/rest/authors.ts +5 -5
  31. package/skills/wix-headless-templates/blog/rest/comments.ts +4 -4
  32. package/skills/wix-headless-templates/blog/rest/post-likes.ts +3 -3
  33. package/skills/wix-headless-templates/blog/rest/posts.ts +4 -4
  34. package/skills/wix-headless-templates/blog/rest/taxonomy.ts +5 -5
  35. package/skills/wix-headless-templates/blog/seed/SEED.md +1 -1
  36. package/skills/wix-headless-templates/blog/seed/seed-blog.mjs +4 -3
  37. package/skills/wix-headless-templates/bookings/rest/booking.ts +4 -4
  38. package/skills/wix-headless-templates/bookings/rest/services.ts +4 -4
  39. package/skills/wix-headless-templates/bookings/seed/SEED.md +1 -1
  40. package/skills/wix-headless-templates/bookings/seed/seed-bookings.mjs +4 -2
  41. package/skills/wix-headless-templates/cms/rest/collections.ts +3 -3
  42. package/skills/wix-headless-templates/cms/rest/items.ts +4 -4
  43. package/skills/wix-headless-templates/cms/seed/seed-cms.mjs +2 -1
  44. package/skills/wix-headless-templates/donations/rest/campaigns.ts +4 -4
  45. package/skills/wix-headless-templates/donations/rest/donate.ts +3 -3
  46. package/skills/wix-headless-templates/donations/seed/SEED.md +1 -1
  47. package/skills/wix-headless-templates/donations/seed/seed-donations.mjs +5 -3
  48. package/skills/wix-headless-templates/events/rest/events.ts +4 -4
  49. package/skills/wix-headless-templates/events/rest/orders.ts +4 -4
  50. package/skills/wix-headless-templates/events/rest/registration.ts +4 -4
  51. package/skills/wix-headless-templates/events/seed/SEED.md +1 -1
  52. package/skills/wix-headless-templates/events/seed/seed-events.mjs +5 -3
  53. package/skills/wix-headless-templates/faq/rest/faq.ts +4 -4
  54. package/skills/wix-headless-templates/faq/seed/seed-faq.mjs +2 -1
  55. package/skills/wix-headless-templates/forms/rest/forms.ts +4 -4
  56. package/skills/wix-headless-templates/forms/rest/submissions.ts +4 -4
  57. package/skills/wix-headless-templates/forms/seed/seed-forms.mjs +2 -1
  58. package/skills/wix-headless-templates/members/rest/auth.ts +3 -3
  59. package/skills/wix-headless-templates/members/rest/members.ts +4 -4
  60. package/skills/wix-headless-templates/members/seed/seed-members.mjs +2 -1
  61. package/skills/wix-headless-templates/portfolio/rest/portfolio.ts +4 -4
  62. package/skills/wix-headless-templates/portfolio/seed/SEED.md +4 -2
  63. package/skills/wix-headless-templates/portfolio/seed/seed-portfolio.mjs +8 -7
  64. package/skills/wix-headless-templates/pricing-plans/rest/plan-access.ts +3 -3
  65. package/skills/wix-headless-templates/pricing-plans/rest/plans.ts +4 -4
  66. package/skills/wix-headless-templates/pricing-plans/rest/purchase.ts +3 -3
  67. package/skills/wix-headless-templates/pricing-plans/seed/seed-pricing-plans.mjs +2 -1
  68. package/skills/wix-headless-templates/rentals/rest/rentals.ts +4 -4
  69. package/skills/wix-headless-templates/rentals/seed/SEED.md +1 -1
  70. package/skills/wix-headless-templates/rentals/seed/seed-rentals.mjs +4 -2
  71. package/skills/wix-headless-templates/restaurants/rest/menu.ts +4 -4
  72. package/skills/wix-headless-templates/restaurants/rest/ordering.ts +5 -5
  73. package/skills/wix-headless-templates/restaurants/rest/reservations.ts +4 -4
  74. package/skills/wix-headless-templates/restaurants/seed/SEED.md +1 -1
  75. package/skills/wix-headless-templates/restaurants/seed/seed-restaurants.mjs +6 -4
  76. package/skills/wix-headless-templates/shared/capabilities/site-search/rest/search.ts +4 -4
  77. package/skills/wix-headless-templates/shared/capabilities/site-search/seed/install.mjs +6 -4
  78. package/skills/wix-headless-templates/shared/seed/images.mjs +41 -5
  79. package/skills/wix-headless-templates/shared/seed/main.mjs +15 -0
  80. package/skills/wix-headless-templates/shared/seed/wix-cli.mjs +43 -11
  81. package/skills/wix-headless-templates/storefront/INSTRUCTIONS.md +1 -1
  82. package/skills/wix-headless-templates/storefront/rest/cart.ts +5 -5
  83. package/skills/wix-headless-templates/storefront/rest/catalog.ts +5 -5
  84. package/skills/wix-headless-templates/storefront/seed/SEED.md +44 -14
  85. package/skills/wix-headless-templates/storefront/seed/seed-store.mjs +23 -15
  86. package/skills/wix-manage/SKILL.md +28 -18
  87. package/skills/wix-manage/references/app-installation/install-wix-apps.md +5 -1
  88. package/skills/wix-manage/references/app-installation/list-installed-apps.md +19 -9
  89. package/skills/wix-manage/references/automations/activation-status.md +52 -0
  90. package/skills/wix-manage/references/automations/api-catalog.md +81 -0
  91. package/skills/wix-manage/references/automations/build-and-manage-automations.md +34 -0
  92. package/skills/wix-manage/references/bookings/booking-service-policy-setup.md +3 -0
  93. package/skills/wix-manage/references/bookings/create-and-update-booking-services.md +277 -363
  94. package/skills/wix-manage/references/bookings/external-calendar-integration.md +3 -0
  95. package/skills/wix-manage/references/bookings/multi-resource-service-creation.md +3 -0
  96. package/skills/wix-manage/references/cms/cms-data-items-crud.md +11 -2
  97. package/skills/wix-manage/references/cms/cms-publishing-flow.md +63 -16
  98. package/skills/wix-manage/references/custom-apps/use-app-skills-and-app-tools.md +3 -1
  99. package/skills/wix-manage/references/forms/update-form.md +3 -0
  100. package/skills/wix-manage/references/pricing-plans/create-and-update-pricing-plans.md +3 -0
  101. package/skills/wix-manage/references/restaurants/wix-restaurants-setup.md +3 -0
  102. package/skills/wix-manage/references/sites/create-headless-site.md +7 -50
  103. package/skills/wix-manage/references/sites/manage-oauth-apps.md +4 -4
  104. package/skills/wix-manage/references/sites/read-site-context.md +4 -0
  105. package/skills/wix-manage/references/sites/send-feedback-to-wix.md +92 -0
  106. package/skills/wix-manage/references/sites/upload-static-site.md +298 -283
  107. package/skills/wix-manage/references/stores/add-store-pages-to-site.md +3 -0
  108. package/skills/wix-manage/references/stores/change-store-currency.md +3 -0
  109. package/skills/wix-manage/references/stores/create-product-catalog-v3.md +3 -0
  110. package/skills/wix-manage/references/stores/find-products-query-and-search-catalog-v3.md +3 -0
  111. package/skills/wix-manage/references/stores/update-product-with-options.md +192 -22
  112. package/skills/wix-manage/references/bookings/create-appointment-service.md +0 -243
  113. package/skills/wix-manage/references/bookings/create-booking-service-from-prompt.md +0 -31
  114. package/skills/wix-manage/references/bookings/create-class-service.md +0 -222
  115. package/skills/wix-manage/references/bookings/create-course-service.md +0 -251
@@ -27,6 +27,7 @@
27
27
  // Seeding is ADDITIVE — never deletes or overwrites existing forms.
28
28
  import { seedSiteId } from "../../shared/seed/site-context.mjs";
29
29
  import { wixToken } from "../../shared/seed/wix-cli.mjs";
30
+ import { isMain } from "../../shared/seed/main.mjs";
30
31
  import { readFileSync } from "node:fs";
31
32
  import { randomUUID } from "node:crypto";
32
33
 
@@ -556,7 +557,7 @@ export async function setupForms(ctx, plan) {
556
557
 
557
558
  // ---- CLI entry -------------------------------------------------------------------------------
558
559
 
559
- const invokedDirectly = process.argv[1] && import.meta.url.endsWith(process.argv[1].split("/").pop());
560
+ const invokedDirectly = isMain(import.meta.url);
560
561
  if (invokedDirectly) {
561
562
  const planPath = process.argv[2];
562
563
  if (!planPath) {
@@ -8,8 +8,8 @@
8
8
  // client id; nothing elevated. Porting: keep the paths and bodies; the exchange is three calls.
9
9
  // docs: https://dev.wix.com/docs/go-headless/authentication/members/custom-login-page/build-a-custom-login-page-rest.md
10
10
  // docs: https://dev.wix.com/docs/api-reference/business-management/headless/redirects/create-redirect-session.md
11
- import { WIX_CLIENT_ID } from "./config.js";
12
- import { WixApiError, clearTokens, loadTokens, mintTokens, setTokens, wixRequest, type Tokens } from "./client.js";
11
+ import { WIX_CLIENT_ID } from "../../shared/rest/config.js";
12
+ import { WixApiError, clearTokens, loadTokens, mintTokens, setTokens, wixRequest, type Tokens } from "../../shared/rest/client.js";
13
13
  import {
14
14
  NO_SESSION_ERROR,
15
15
  authOutcome,
@@ -20,7 +20,7 @@ import {
20
20
  type LoginResult,
21
21
  type LoginState,
22
22
  type RawAuth,
23
- } from "./auth-core.js";
23
+ } from "../app/wix/members/auth-core.js";
24
24
 
25
25
  export type { LoginResult, LoginState };
26
26
 
@@ -4,10 +4,10 @@
4
4
  // whatever token ./client.js holds — a member's after a rest/auth.ts login → the profile; a
5
5
  // visitor's → 403 → null. Porting: keep the path and the fieldsets, port the core once.
6
6
  // docs: https://dev.wix.com/docs/api-reference/crm/members-contacts/members/member-management/members/get-my-member.md
7
- import { wixRequest } from "./client.js";
8
- import { imgSrc } from "./media.js";
9
- import { MEMBER_FIELDSETS, memberId, toCurrentMember, type Raw } from "./members-core.js";
10
- import type { CurrentMember } from "./types.js";
7
+ import { wixRequest } from "../../shared/rest/client.js";
8
+ import { imgSrc } from "../../shared/rest/media.js";
9
+ import { MEMBER_FIELDSETS, memberId, toCurrentMember, type Raw } from "../app/wix/members/members-core.js";
10
+ import type { CurrentMember } from "../app/wix/members/types.js";
11
11
 
12
12
  /**
13
13
  * The current member, or null: for a visitor (the call is refused — "Missing site member id"), for
@@ -15,6 +15,7 @@
15
15
  // Members Area app's App Market id; the identity/profile split is described beside it.
16
16
  import { seedSiteId } from "../../shared/seed/site-context.mjs";
17
17
  import { wixToken } from "../../shared/seed/wix-cli.mjs";
18
+ import { isMain } from "../../shared/seed/main.mjs";
18
19
  import { readFileSync } from "node:fs";
19
20
 
20
21
  const API = "https://www.wixapis.com";
@@ -77,7 +78,7 @@ export async function setupMembers(ctx, { installMembersArea = true } = {}) {
77
78
 
78
79
  // ---- CLI entry ----------------------------------------------------------------------------------
79
80
 
80
- const invokedDirectly = process.argv[1] && import.meta.url.endsWith(process.argv[1].split("/").pop());
81
+ const invokedDirectly = isMain(import.meta.url);
81
82
  if (invokedDirectly) {
82
83
  const planPath = process.argv[2];
83
84
  const plan = planPath ? JSON.parse(readFileSync(planPath, "utf8")) : {};
@@ -8,8 +8,8 @@
8
8
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/portfolio/projects/list-projects.md
9
9
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/portfolio/projects/query-projects.md
10
10
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/portfolio/project-items/list-project-items.md
11
- import { wixRequest } from "./client.js";
12
- import { imgSrc } from "./media.js";
11
+ import { wixRequest } from "../../shared/rest/client.js";
12
+ import { imgSrc } from "../../shared/rest/media.js";
13
13
  import {
14
14
  galleryItems,
15
15
  visibleCollection,
@@ -18,8 +18,8 @@ import {
18
18
  visibleProjects,
19
19
  type Raw,
20
20
  type VideoSrc,
21
- } from "./portfolio-core.js";
22
- import type { CollectionSummary, GalleryItem, ProjectDetail, ProjectSummary } from "./types.js";
21
+ } from "../app/wix/portfolio/portfolio-core.js";
22
+ import type { CollectionSummary, GalleryItem, ProjectDetail, ProjectSummary } from "../app/wix/portfolio/types.js";
23
23
 
24
24
  // REST never returns the bare wix:video:// string — videoInfo arrives as { resolutions, posters },
25
25
  // which the core reads directly. A string here has no resolver on this transport → dropped.
@@ -53,8 +53,10 @@ rows (Role, Year, Client…).
53
53
  - Every image field's default is a prompt (`coverImagePrompt` / `items[].imagePrompt` —
54
54
  AI-generated, ~1 Wix AI credit per image, account-billed): brand-contextual — subject,
55
55
  aesthetic/mood, palette, lighting — always ending "no text, no watermarks". At least one image in the set shows the real subject of the business — the actual product/space/service, not abstract decoration. For an asset the user actually supplied
56
- use a path (`coverImagePath` / `items[].imagePath` — a file on this machine, uploaded to
57
- Wix Media) or a url (`coverImageUrl` / `items[].imageUrl` — their own hosted URL; verify it
56
+ use a Media Manager file (`coverImageMediaId` / `items[].imageMediaId` — a file already in the
57
+ site's Media Manager, such as one the user uploaded there: used as it is, no copy; a
58
+ `static.wixstatic.com/media/<file id>` URL of a site file counts as one), a path
59
+ (`coverImagePath` / `items[].imagePath` — a file on this machine, uploaded to Wix Media) or a url (`coverImageUrl` / `items[].imageUrl` — their own hosted URL; verify it
58
60
  with `curl -sI` → 200) — never a stock-photo or guessed URL. All images —
59
61
  covers and gallery items alike — resolve in one parallel wave and never block the seed; a
60
62
  failed image skips just that item/cover. The **cover** is the
@@ -9,12 +9,12 @@
9
9
  // project's gallery items, and imports+attaches cover images. Prints a JSON result to stdout.
10
10
  //
11
11
  // Plan shape (see SEED.md):
12
- // { "collections": [{ "title", "description"?, "hidden"?, "coverImageUrl"? | "coverImagePrompt"? }],
12
+ // { "collections": [{ "title", "description"?, "hidden"?, "coverImageMediaId"? | "coverImageUrl"? | "coverImagePrompt"? }],
13
13
  // "projects": [{ "title", "description"?, "hidden"?,
14
14
  // "collection"? (title), // resolved to that collection's id
15
15
  // "details"?: [{ "label", "text" }],
16
- // "coverImageUrl"? | "coverImagePrompt"?,
17
- // "items"?: [{ "sortOrder", "title"?, "imageUrl" | "imagePrompt" }] }] }
16
+ // "coverImageMediaId"? | "coverImageUrl"? | "coverImagePrompt"?,
17
+ // "items"?: [{ "sortOrder", "title"?, "imageMediaId" | "imageUrl" | "imagePrompt" }] }] }
18
18
  //
19
19
  // Seeding is ADDITIVE — never deletes or overwrites existing content. A fresh Portfolio
20
20
  // install ships its own sample content ("My Portfolio" + sample projects); removing it is the
@@ -24,6 +24,7 @@ import { readFileSync } from "node:fs";
24
24
  import { resolveItemImages } from "../../shared/seed/images.mjs";
25
25
  import { seedSiteId } from "../../shared/seed/site-context.mjs";
26
26
  import { wixToken } from "../../shared/seed/wix-cli.mjs";
27
+ import { isMain } from "../../shared/seed/main.mjs";
27
28
 
28
29
  const API = "https://www.wixapis.com";
29
30
  const PORTFOLIO_APP_ID = "d90652a2-f5a1-4c7c-84c4-d4cdcc41f130";
@@ -186,13 +187,13 @@ export async function setupPortfolio(ctx, { collections = [], projects = [] } =
186
187
  projects.forEach((p, pi) => {
187
188
  for (const it of p.items ?? []) {
188
189
  galleryRefs.push({ pi, it, spec: specs.length });
189
- specs.push({ path: it.imagePath, url: it.imageUrl, prompt: it.imagePrompt, displayName: `${it.title || "item"}.png` });
190
+ specs.push({ mediaId: it.imageMediaId, path: it.imagePath, url: it.imageUrl, prompt: it.imagePrompt, displayName: `${it.title || "item"}.png` });
190
191
  }
191
192
  });
192
193
  const projCoverAt = specs.length;
193
- projects.forEach((p) => specs.push({ path: p.coverImagePath, url: p.coverImageUrl, prompt: p.coverImagePrompt, displayName: `${p.title || "project"}-cover.png` }));
194
+ projects.forEach((p) => specs.push({ mediaId: p.coverImageMediaId, path: p.coverImagePath, url: p.coverImageUrl, prompt: p.coverImagePrompt, displayName: `${p.title || "project"}-cover.png` }));
194
195
  const colCoverAt = specs.length;
195
- collections.forEach((c) => specs.push({ path: c.coverImagePath, url: c.coverImageUrl, prompt: c.coverImagePrompt, displayName: `${c.title || "collection"}-cover.png` }));
196
+ collections.forEach((c) => specs.push({ mediaId: c.coverImageMediaId, path: c.coverImagePath, url: c.coverImageUrl, prompt: c.coverImagePrompt, displayName: `${c.title || "collection"}-cover.png` }));
196
197
  const files = await resolveItemImages(ctx, specs);
197
198
  const dims = { height: 1024, width: 1024 };
198
199
 
@@ -235,7 +236,7 @@ export async function setupPortfolio(ctx, { collections = [], projects = [] } =
235
236
 
236
237
  // ---- CLI entry ----------------------------------------------------------------------------------
237
238
 
238
- const invokedDirectly = process.argv[1] && import.meta.url.endsWith(process.argv[1].split("/").pop());
239
+ const invokedDirectly = isMain(import.meta.url);
239
240
  if (invokedDirectly) {
240
241
  const planPath = process.argv[2];
241
242
  if (!planPath) {
@@ -5,9 +5,9 @@
5
5
  // visitor, so callers check the session first (the store does) and never call for a visitor.
6
6
  // Porting: keep the path and the query string; the rule is one line in the core.
7
7
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/pricing-plans/orders/member-list-orders.md
8
- import { wixRequest } from "./client.js";
9
- import { hasPlanAccess, memberOrdersQuery, type MemberOrdersQuery } from "./plan-access-core.js";
10
- import type { Raw } from "./plans-core.js";
8
+ import { wixRequest } from "../../shared/rest/client.js";
9
+ import { hasPlanAccess, memberOrdersQuery, type MemberOrdersQuery } from "../app/wix/pricing-plans/plan-access-core.js";
10
+ import type { Raw } from "../app/wix/pricing-plans/plans-core.js";
11
11
 
12
12
  export type { MemberOrdersQuery };
13
13
 
@@ -5,10 +5,10 @@
5
5
  // A plan without a numeric price is malformed and never returned (plans-core `planAmount`).
6
6
  // Porting: keep the path, keep the body, port the core once.
7
7
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/pricing-plans/plans-v3/query-plans.md
8
- import { wixRequest } from "./client.js";
9
- import { imgSrc } from "./media.js";
10
- import { plansQuery, toDetail, toSummaries, type FormatOptions, type Raw } from "./plans-core.js";
11
- import type { PlanDetail, PlanSummary } from "./types.js";
8
+ import { wixRequest } from "../../shared/rest/client.js";
9
+ import { imgSrc } from "../../shared/rest/media.js";
10
+ import { plansQuery, toDetail, toSummaries, type FormatOptions, type Raw } from "../app/wix/pricing-plans/plans-core.js";
11
+ import type { PlanDetail, PlanSummary } from "../app/wix/pricing-plans/types.js";
12
12
 
13
13
  export type { FormatOptions };
14
14
 
@@ -4,9 +4,9 @@
4
4
  // order form, and payment itself, so a visitor token starts it. Never create the order yourself,
5
5
  // never hand-build a checkout URL.
6
6
  // docs: https://dev.wix.com/docs/api-reference/business-management/headless/redirects/create-redirect-session.md
7
- import { wixRequest } from "./client.js";
8
- import { purchaseBody, redirectUrl, type PurchaseOptions } from "./purchase-core.js";
9
- import type { Raw } from "./plans-core.js";
7
+ import { wixRequest } from "../../shared/rest/client.js";
8
+ import { purchaseBody, redirectUrl, type PurchaseOptions } from "../app/wix/pricing-plans/purchase-core.js";
9
+ import type { Raw } from "../app/wix/pricing-plans/plans-core.js";
10
10
 
11
11
  export type { PurchaseOptions };
12
12
 
@@ -23,6 +23,7 @@
23
23
  import { setSiteCurrency } from "../../shared/seed/site.mjs";
24
24
  import { seedSiteId } from "../../shared/seed/site-context.mjs";
25
25
  import { wixToken } from "../../shared/seed/wix-cli.mjs";
26
+ import { isMain } from "../../shared/seed/main.mjs";
26
27
  import { readFileSync } from "node:fs";
27
28
  import { randomUUID } from "node:crypto";
28
29
 
@@ -283,7 +284,7 @@ export async function setupPricingPlans(ctx, { plans = [], currency } = {}) {
283
284
 
284
285
  // ---- CLI entry ----------------------------------------------------------------------------------
285
286
 
286
- const invokedDirectly = process.argv[1] && import.meta.url.endsWith(process.argv[1].split("/").pop());
287
+ const invokedDirectly = isMain(import.meta.url);
287
288
  if (invokedDirectly) {
288
289
  const planPath = process.argv[2];
289
290
  if (!planPath) {
@@ -10,8 +10,8 @@
10
10
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/bookings/bookings/bookings-writer-v2/create-booking.md
11
11
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/create-cart.md
12
12
  // docs: https://dev.wix.com/docs/api-reference/business-management/headless/redirects/create-redirect-session.md
13
- import { wixRequest } from "./client.js";
14
- import { imgSrc } from "./media.js";
13
+ import { wixRequest } from "../../shared/rest/client.js";
14
+ import { imgSrc } from "../../shared/rest/media.js";
15
15
  import {
16
16
  FALLBACK_FIELDS,
17
17
  RENTALS_APP_ID,
@@ -42,8 +42,8 @@ import {
42
42
  toSummary,
43
43
  type Raw,
44
44
  type StartWindow,
45
- } from "./rentals-core.js";
46
- import type { EndOption, RentalDetail, RentalFormField, RentalQuote, RentalResult, RentalSummary, StartOption } from "./types.js";
45
+ } from "../app/wix/rentals/rentals-core.js";
46
+ import type { EndOption, RentalDetail, RentalFormField, RentalQuote, RentalResult, RentalSummary, StartOption } from "../app/wix/rentals/types.js";
47
47
 
48
48
  export { RENTALS_APP_ID, RENTALS_FORM_ID, RENTALS_PAGE_SIZE, dailyEndOptions, defaultTimeZone };
49
49
 
@@ -19,7 +19,7 @@ exercise the UI: one hourly and one daily rental when the business allows it, a
19
19
  approval-gated one where it fits, and an image per rental — the default is an `imagePrompt`
20
20
  (AI-generated, ~1 Wix AI credit per image, account-billed): brand-contextual — subject,
21
21
  aesthetic/mood, palette, lighting — always ending "no text, no watermarks". At least one image shows
22
- the real thing being rented. For an asset the user actually supplied use `imagePath` (a file on this
22
+ the real thing being rented. For an asset the user actually supplied use `imageMediaId` (a file already in the site's Media Manager, such as one the user uploaded there: used as it is, no copy; a `static.wixstatic.com/media/<file id>` URL of a site file counts as one), `imagePath` (a file on this
23
23
  machine — uploaded to Wix Media) or `imageUrl` (their own hosted URL; verify it with `curl -sI` →
24
24
  200) — never a stock-photo or guessed URL. Images resolve in parallel and never block the seed.
25
25
 
@@ -17,7 +17,7 @@
17
17
  // "resourceTypes": [{ "name": "Kayaks", "resources": ["Kayak 1", "Kayak 2"] }],
18
18
  // "rentals": [{ "name", "description", "tagLine"?, "unit": "HOUR"|"DAY", "rate": 25, "min"?, "max"?,
19
19
  // "resourceType": "Kayaks", "resources"?: ["Kayak 1"], "free"?, "requireManualApproval"?,
20
- // "imageUrl"? | "imagePath"? | "imagePrompt"? }] }
20
+ // "imageMediaId"? | "imageUrl"? | "imagePath"? | "imagePrompt"? }] }
21
21
  //
22
22
  // Seeding is ADDITIVE — never deletes or overwrites existing content. Unexpected shapes →
23
23
  // read the live API reference; every call below carries a docs: line with its reference page.
@@ -26,6 +26,7 @@ import { readFileSync } from "node:fs";
26
26
  import { resolveItemImages } from "../../shared/seed/images.mjs";
27
27
  import { seedSiteId } from "../../shared/seed/site-context.mjs";
28
28
  import { wixToken } from "../../shared/seed/wix-cli.mjs";
29
+ import { isMain } from "../../shared/seed/main.mjs";
29
30
 
30
31
  const API = "https://www.wixapis.com";
31
32
  /** The Wix Rentals app: the service's immutable appId, the catalog filter, the cart's appId. */
@@ -268,6 +269,7 @@ export async function setupRentals(ctx, { resourceTypes = [], rentals = [], curr
268
269
  // Pass 2 — images: resolve (import by url / generate by prompt) in one parallel wave, then attach.
269
270
  // Failures leave the rental text-only; the seed's exit never depends on images.
270
271
  const files = await resolveItemImages(ctx, created.map((c, i) => ({
272
+ mediaId: rentals[i]?.imageMediaId,
271
273
  url: rentals[i]?.imageUrl,
272
274
  path: rentals[i]?.imagePath,
273
275
  prompt: rentals[i]?.imagePrompt,
@@ -300,7 +302,7 @@ export async function setupRentals(ctx, { resourceTypes = [], rentals = [], curr
300
302
 
301
303
  // ---- CLI entry ----------------------------------------------------------------------------------
302
304
 
303
- const invokedDirectly = process.argv[1] && import.meta.url.endsWith(process.argv[1].split("/").pop());
305
+ const invokedDirectly = isMain(import.meta.url);
304
306
  if (invokedDirectly) {
305
307
  const planPath = process.argv[2];
306
308
  if (!planPath) {
@@ -7,8 +7,8 @@
7
7
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/menus/menus/list-menus.md
8
8
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/menus/items/items/list-items.md
9
9
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/ecommerce-settings/get-ecommerce-settings.md
10
- import { wixRequest } from "./client.js";
11
- import { imgSrc } from "./media.js";
10
+ import { wixRequest } from "../../shared/rest/client.js";
11
+ import { imgSrc } from "../../shared/rest/media.js";
12
12
  import {
13
13
  anyLabels,
14
14
  assembleMenus,
@@ -19,8 +19,8 @@ import {
19
19
  nextCursor,
20
20
  toSiteMoney,
21
21
  type Raw,
22
- } from "./menu-core.js";
23
- import type { MenuData, SiteMoney } from "./types.js";
22
+ } from "../app/wix/restaurants/menu-core.js";
23
+ import type { MenuData, SiteMoney } from "../app/wix/restaurants/types.js";
24
24
 
25
25
  const get = (path: string, query?: Record<string, string | readonly string[]>) => wixRequest<Raw>(path, { method: "GET", query });
26
26
 
@@ -12,10 +12,10 @@
12
12
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/online-orders/fulfillment-methods/list-fulfillment-methods.md
13
13
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/add-line-items-to-current-cart.md
14
14
  // docs: https://dev.wix.com/docs/api-reference/business-management/headless/redirects/create-redirect-session.md
15
- import { WixApiError, wixRequest } from "./client.js";
16
- import { imgSrc } from "./media.js";
15
+ import { WixApiError, wixRequest } from "../../shared/rest/client.js";
16
+ import { imgSrc } from "../../shared/rest/media.js";
17
17
  import { fetchSiteMoney } from "./menu.js";
18
- import { initialSelection, nextCursor, validateSelection, type Raw } from "./menu-core.js";
18
+ import { initialSelection, nextCursor, validateSelection, type Raw } from "../app/wix/restaurants/menu-core.js";
19
19
  import {
20
20
  RESTAURANTS_ORDERS_APP_ID,
21
21
  assertOrderAdded,
@@ -29,8 +29,8 @@ import {
29
29
  toFulfillmentMethods,
30
30
  toMenuOrderingMap,
31
31
  toOrderCart,
32
- } from "./ordering-core.js";
33
- import type { FulfillmentMethodInfo, MenuItem, MenuOrderingInfo, OrderCart, OrderSelection, OrderingStatus } from "./types.js";
32
+ } from "../app/wix/restaurants/ordering-core.js";
33
+ import type { FulfillmentMethodInfo, MenuItem, MenuOrderingInfo, OrderCart, OrderSelection, OrderingStatus } from "../app/wix/restaurants/types.js";
34
34
 
35
35
  export { RESTAURANTS_ORDERS_APP_ID };
36
36
 
@@ -9,9 +9,9 @@
9
9
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/reservations/reservations/create-held-reservation.md
10
10
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/reservations/reservations/reserve-reservation.md
11
11
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/reservations/reservations/create-reservation.md
12
- import { wixRequest } from "./client.js";
13
- import { availableSlots, reservationBody, reserveeBody, toConfirmation, toHold, toLocations } from "./reservations-core.js";
14
- import type { Raw } from "./menu-core.js";
12
+ import { wixRequest } from "../../shared/rest/client.js";
13
+ import { availableSlots, reservationBody, reserveeBody, toConfirmation, toHold, toLocations } from "../app/wix/restaurants/reservations-core.js";
14
+ import type { Raw } from "../app/wix/restaurants/menu-core.js";
15
15
  import type {
16
16
  ReservationConfirmation,
17
17
  ReservationFormConfig,
@@ -19,7 +19,7 @@ import type {
19
19
  ReservationLocationInfo,
20
20
  ReservationReservee,
21
21
  ReservationSlot,
22
- } from "./types.js";
22
+ } from "../app/wix/restaurants/types.js";
23
23
 
24
24
  const RESERVATIONS = "/table-reservations/reservations/v1";
25
25
 
@@ -16,7 +16,7 @@ of 2–3 items each** (the seed shows the shape; the owner adds the rest in the
16
16
  every item with an image (a menu without photos looks broken) — the default is an
17
17
  `imagePrompt` (AI-generated, ~1 Wix AI credit per image, account-billed): brand-contextual —
18
18
  subject, aesthetic/mood, palette, lighting — always ending "no text, no watermarks". At least one image in the set shows the real subject of the business — the actual product/space/service, not abstract decoration. For an asset the user actually
19
- supplied use `imagePath` (a file on this machine — uploaded to Wix Media) or `imageUrl`
19
+ supplied use `imageMediaId` (a file already in the site's Media Manager, such as one the user uploaded there: used as it is, no copy; a `static.wixstatic.com/media/<file id>` URL of a site file counts as one), `imagePath` (a file on this machine — uploaded to Wix Media) or `imageUrl`
20
20
  (their own hosted URL; verify it with `curl -sI` → 200) — never a stock-photo or guessed URL. Images resolve in parallel and never
21
21
  block the seed, a failed image leaves that item text-only — and both add-ons on for a
22
22
  restaurant that takes orders and reservations. Give at least one dish a choice (a `variants`
@@ -14,7 +14,7 @@
14
14
  // "items": [{ "name", "description"?, "price"? | "variants"?: [{ "name", "price" }],
15
15
  // "modifierGroups"?: [{ "name", "required"?, "min"?, "max"?,
16
16
  // "modifiers": [{ "name", "price"?, "preSelected"?, "inStock"? }] }],
17
- // "acceptSpecialRequests"?, "imageUrl"? | "imagePath"? | "imagePrompt"? }] }] }],
17
+ // "acceptSpecialRequests"?, "imageMediaId"? | "imageUrl"? | "imagePath"? | "imagePrompt"? }] }] }],
18
18
  // "ordering"?: true | { "address"? }, // menu-first add-on; address is STEP 0
19
19
  // "reservations"?: true | { "partySize"? { "min","max" }, "address"? } }
20
20
  //
@@ -28,6 +28,7 @@ import { readFileSync } from "node:fs";
28
28
  import { resolveItemImages } from "../../shared/seed/images.mjs";
29
29
  import { seedSiteId } from "../../shared/seed/site-context.mjs";
30
30
  import { wixToken } from "../../shared/seed/wix-cli.mjs";
31
+ import { isMain } from "../../shared/seed/main.mjs";
31
32
 
32
33
  const API = "https://www.wixapis.com";
33
34
  const MENUS_APP_ID = "b278a256-2757-4f19-9313-c05c783bec92";
@@ -459,12 +460,13 @@ export async function setupRestaurants(ctx, plan) {
459
460
  const flat = m.sections.flatMap((s) => s.items || []);
460
461
  flat.forEach((it, i) => {
461
462
  const created = createdMenus[mi]?.items?.[i];
462
- if ((it.imageUrl || it.imagePath || it.imagePrompt) && created?.id) {
463
- imageItems.push({ ...created, imageUrl: it.imageUrl, imagePath: it.imagePath, imagePrompt: it.imagePrompt, name: it.name });
463
+ if ((it.imageMediaId || it.imageUrl || it.imagePath || it.imagePrompt) && created?.id) {
464
+ imageItems.push({ ...created, imageMediaId: it.imageMediaId, imageUrl: it.imageUrl, imagePath: it.imagePath, imagePrompt: it.imagePrompt, name: it.name });
464
465
  }
465
466
  });
466
467
  });
467
468
  const files = await resolveItemImages(ctx, imageItems.map((it) => ({
469
+ mediaId: it.imageMediaId,
468
470
  url: it.imageUrl,
469
471
  path: it.imagePath,
470
472
  prompt: it.imagePrompt,
@@ -559,7 +561,7 @@ export async function setupRestaurants(ctx, plan) {
559
561
 
560
562
  // ---- CLI entry ----------------------------------------------------------------------------------
561
563
 
562
- const invokedDirectly = process.argv[1] && import.meta.url.endsWith(process.argv[1].split("/").pop());
564
+ const invokedDirectly = isMain(import.meta.url);
563
565
  if (invokedDirectly) {
564
566
  const planPath = process.argv[2];
565
567
  if (!planPath) {
@@ -4,8 +4,8 @@
4
4
  // a literal body per function. Every call here answers a visitor token (observed live). Porting:
5
5
  // keep the paths, keep the bodies, port the core once.
6
6
  // docs: https://dev.wix.com/docs/api-reference/business-management/site-search/wix-site-search/search.md
7
- import { wixRequest } from "./client.js";
8
- import { imgSrc } from "./media.js";
7
+ import { wixRequest } from "../../../rest/client.js";
8
+ import { imgSrc } from "../../../rest/media.js";
9
9
  import { siteSearchPlan } from "./site-search-config.generated.js";
10
10
  import {
11
11
  GROUP_LIMIT,
@@ -23,8 +23,8 @@ import {
23
23
  toSearchAll,
24
24
  toSuggestGroups,
25
25
  type Raw,
26
- } from "./search-core.js";
27
- import type { SearchAllResult, SearchOptions, SearchPage, SiteSearchConfig, SuggestGroup } from "./types.js";
26
+ } from "../app/wix/site-search/search-core.js";
27
+ import type { SearchAllResult, SearchOptions, SearchPage, SiteSearchConfig, SuggestGroup } from "../app/wix/site-search/types.js";
28
28
 
29
29
  /** The plan's siteSearch entry resolved — types in order, routes, labels. */
30
30
  export const config: SiteSearchConfig = resolveConfig(siteSearchPlan);
@@ -11,10 +11,10 @@
11
11
  // (searchAll("<a seeded title word>")) and expect a non-empty group before wiring the UI. Zero
12
12
  // documents after a minute means the app is not installed or the content is not visible to the
13
13
  // index (hidden products, drafts), not that the query is wrong.
14
- import { execFileSync } from "node:child_process";
15
14
  import { seedSiteId } from "../../../seed/site-context.mjs";
16
15
  import { existsSync, readFileSync } from "node:fs";
17
16
  import { installSiteSearch } from "../../../seed/site.mjs";
17
+ import { wixToken } from "../../../seed/wix-cli.mjs";
18
18
 
19
19
  function flag(name) {
20
20
  const i = process.argv.indexOf(`--${name}`);
@@ -29,9 +29,11 @@ if (!siteId) {
29
29
  console.log(JSON.stringify({ error: "no site: pass --site <siteId> or run in a folder with wix.config.json" }));
30
30
  process.exit(1);
31
31
  }
32
- const token = execFileSync("npx", ["-y", "@wix/cli@1.1.258", "token", "--site", siteId], { encoding: "utf8" }).trim();
33
- if (!token) {
34
- console.log(JSON.stringify({ error: "the Wix CLI returned no token — run `npx @wix/cli@1.1.258 login` first" }));
32
+ let token;
33
+ try {
34
+ token = wixToken(siteId);
35
+ } catch (e) {
36
+ console.log(JSON.stringify({ error: String(e.message).slice(0, 400) }));
35
37
  process.exit(1);
36
38
  }
37
39
  try {
@@ -124,6 +124,37 @@ async function findExisting(ctx, displayName) {
124
124
  }
125
125
  }
126
126
 
127
+ /**
128
+ * The Media Manager file a spec names, when it names one: `mediaId`, or the file id at the end of a
129
+ * `static.wixstatic.com/media/<fileId>` URL. Returns { id, url, existing: true } for a file on this
130
+ * site, waiting out a short PENDING (an upload still processing), or null when the URL's file is
131
+ * not on this site (it is then imported like any external URL). An explicit `mediaId` that is not
132
+ * on the site throws, so the product is reported text-only with the reason.
133
+ */
134
+ // docs: https://dev.wix.com/docs/api-reference/assets/media/media-manager/files/get-file-descriptor.md
135
+ const WIX_MEDIA_URL = /^https?:\/\/static\.wixstatic\.com\/media\/([^/?#]+)/;
136
+ async function existingMediaFile(ctx, s) {
137
+ const id = s.mediaId ?? s.url?.match(WIX_MEDIA_URL)?.[1];
138
+ if (!id) return null;
139
+ for (let attempt = 0; attempt < 10; attempt++) {
140
+ const res = await fetch(`${API}/site-media/v1/files/get-file-by-id?fileId=${encodeURIComponent(id)}`, {
141
+ headers: { Authorization: `Bearer ${ctx.token}`, "wix-site-id": ctx.siteId },
142
+ signal: AbortSignal.timeout(20_000),
143
+ });
144
+ if (res.status === 404) {
145
+ if (s.mediaId) throw new Error(`no Media Manager file ${id} on this site`);
146
+ return null;
147
+ }
148
+ const json = await res.json().catch(() => ({}));
149
+ if (!res.ok) throw new Error(`GET get-file-by-id -> ${res.status}: ${JSON.stringify(json).slice(0, 200)}`);
150
+ const f = json.file;
151
+ if (f?.operationStatus === "FAILED") throw new Error(`Media Manager file ${id} failed processing`);
152
+ if (f?.url && f.operationStatus !== "PENDING") return { id: f.id, url: f.url, existing: true };
153
+ await new Promise((r) => setTimeout(r, 3000));
154
+ }
155
+ throw new Error(`Media Manager file ${id} still processing`);
156
+ }
157
+
127
158
  /** Short stable fingerprint of an image's source (url, prompt + size, or file bytes). */
128
159
  function sourceHash(s) {
129
160
  const h = createHash("sha1");
@@ -149,8 +180,10 @@ export async function importImage(ctx, url, displayName = "image.png") {
149
180
 
150
181
  /**
151
182
  * THE seed entry point. Resolves a batch of image specs to Wix Media files in ONE parallel
152
- * wave. Each spec: { path } (LOCAL file — the user's own asset, uploaded) OR { url }
153
- * (verified external URL — imported) OR { prompt } (generated, ~1 credit) — plus optional
183
+ * wave. Each spec: { mediaId } (a file already in this site's Media Manager — used as it is,
184
+ * as is a static.wixstatic.com/media URL of one) OR { path } (LOCAL file — the user's own asset,
185
+ * uploaded) OR { url } (verified external URL — imported) OR { prompt } (generated, ~1 credit) —
186
+ * plus optional
154
187
  * displayName, width, height. Returns an array aligned with the input: { file, error } per
155
188
  * spec — `file` is { id, url } on success and null otherwise; `error` is the reason it is null
156
189
  * (the thrown message, "empty spec", or "timed out"). Never throws. Every failure is also
@@ -166,8 +199,11 @@ export async function resolveItemImagesDetailed(ctx, specs, { perImageBudgetMs =
166
199
  });
167
200
  const results = await Promise.allSettled(
168
201
  (specs ?? []).map(async (s) => {
169
- if (!s || (!s.path && !s.url && !s.prompt)) return null;
202
+ if (!s || (!s.path && !s.url && !s.prompt && !s.mediaId)) return null;
170
203
  const resolve = (async () => {
204
+ // A file already in this site's Media Manager is used as it is: no import, no copy.
205
+ const inMedia = await existingMediaFile(ctx, s);
206
+ if (inMedia) return inMedia;
171
207
  const name = stableName(s);
172
208
  // uploadImage swaps in the file's own extension; look for what it will actually be named
173
209
  const uploadedName = s.path ? name.replace(/\.[a-z0-9]+$/i, "") + extname(s.path).toLowerCase() : name;
@@ -184,10 +220,10 @@ export async function resolveItemImagesDetailed(ctx, specs, { perImageBudgetMs =
184
220
  );
185
221
  return results.map((r, i) => {
186
222
  const spec = specs?.[i] ?? {};
187
- const label = spec.displayName ?? spec.path ?? spec.url ?? (spec.prompt ? "(prompt)" : "(empty)");
223
+ const label = spec.displayName ?? spec.mediaId ?? spec.path ?? spec.url ?? (spec.prompt ? "(prompt)" : "(empty)");
188
224
  if (r.status === "fulfilled") {
189
225
  if (r.value) return { file: r.value, error: null };
190
- const error = !spec.path && !spec.url && !spec.prompt ? "empty spec" : `timed out after ${perImageBudgetMs} ms`;
226
+ const error = !spec.path && !spec.url && !spec.prompt && !spec.mediaId ? "empty spec" : `timed out after ${perImageBudgetMs} ms`;
191
227
  if (error !== "empty spec") console.error(`image ${label}: ${error}`);
192
228
  return { file: null, error };
193
229
  }
@@ -0,0 +1,15 @@
1
+ // Whether a seed module is the script node was started with (`node seed-<vertical>.mjs plan.json`)
2
+ // rather than imported. Compares real paths, so Windows backslashes, relative invocations and
3
+ // symlinked folders all match (node reports the module's real path in import.meta.url).
4
+ import { realpathSync } from "node:fs";
5
+ import { resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ export function isMain(moduleUrl) {
9
+ if (!process.argv[1]) return false;
10
+ try {
11
+ return realpathSync(resolve(process.argv[1])) === realpathSync(fileURLToPath(moduleUrl));
12
+ } catch {
13
+ return false;
14
+ }
15
+ }