@wix/himalaya-cli 0.1036.0 → 0.1037.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 (46) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/portfolio/src/index.d.ts +46 -9
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-portfolio.mjs +14 -2
  3. package/dist/cli.mjs +476 -109
  4. package/dist/preview-assets.generated.bin +0 -0
  5. package/fixtures/wix-canned.json +51 -2
  6. package/guides/sdk-reference.json +16 -10
  7. package/guides/wix-business-context.md +13 -1
  8. package/guides/wix-site-to-himi-app.md +10 -1
  9. package/package.json +2 -2
  10. package/templates/beauty-salon/tier5-src/himi-booking-bookings/index.ts +14 -8
  11. package/templates/beauty-salon/tier5-src/himi-branded-lite-home/index.ts +12 -1
  12. package/templates/beauty-salon-owner/tier5-src/_shared/owner-module-data.ts +2 -2
  13. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-appointments/index.ts +4 -1
  14. package/templates/branded-lite/tier5-src/himi-booking-bookings/index.ts +14 -8
  15. package/templates/branded-lite/tier5-src/himi-branded-lite-home/index.ts +12 -1
  16. package/templates/branded-lite/tier5-src/himi-branded-lite-portfolio/index.ts +2 -2
  17. package/templates/branded-lite/tier5-src/himi-branded-lite-portfolio-project/index.ts +3 -3
  18. package/templates/community-club/tier5-src/himi-branded-lite-home/index.ts +12 -1
  19. package/templates/community-club-owner/tier5-src/_shared/owner-module-data.ts +2 -2
  20. package/templates/events-meetups/tier5-src/himi-branded-lite-home/index.ts +12 -1
  21. package/templates/events-meetups-owner/tier5-src/_shared/owner-module-data.ts +2 -2
  22. package/templates/gym-fitness/tier5-src/himi-booking-bookings/index.ts +14 -8
  23. package/templates/gym-fitness/tier5-src/himi-branded-lite-home/index.ts +12 -1
  24. package/templates/gym-fitness-owner/tier5-src/_shared/owner-module-data.ts +2 -2
  25. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-appointments/index.ts +4 -1
  26. package/templates/nonprofit-cause/tier5-src/himi-branded-lite-home/index.ts +12 -1
  27. package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-module-data.ts +2 -2
  28. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-appointments/index.ts +4 -1
  29. package/templates/owner-lite/tier5-src/_shared/owner-module-data.ts +2 -2
  30. package/templates/owner-lite/tier5-src/himi-owner-lite-appointments/index.ts +4 -1
  31. package/templates/podcast-creator/tier5-src/himi-branded-lite-home/index.ts +12 -1
  32. package/templates/podcast-creator-owner/tier5-src/_shared/owner-module-data.ts +2 -2
  33. package/templates/portfolio-creative/tier5-src/himi-branded-lite-home/index.ts +12 -1
  34. package/templates/portfolio-creative/tier5-src/himi-branded-lite-portfolio/index.ts +2 -2
  35. package/templates/portfolio-creative/tier5-src/himi-branded-lite-portfolio-project/index.ts +3 -3
  36. package/templates/portfolio-creative-owner/tier5-src/_shared/owner-module-data.ts +2 -2
  37. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-appointments/index.ts +4 -1
  38. package/templates/restaurant/tier5-src/himi-branded-lite-home/index.ts +12 -1
  39. package/templates/restaurant-owner/tier5-src/_shared/owner-module-data.ts +2 -2
  40. package/templates/restaurant-owner/tier5-src/himi-owner-lite-appointments/index.ts +4 -1
  41. package/templates/retail-store/tier5-src/himi-branded-lite-home/index.ts +12 -1
  42. package/templates/retail-store-owner/tier5-src/_shared/owner-module-data.ts +2 -2
  43. package/templates/yoga-studio/tier5-src/himi-booking-bookings/index.ts +14 -8
  44. package/templates/yoga-studio/tier5-src/himi-branded-lite-home/index.ts +12 -1
  45. package/templates/yoga-studio-owner/tier5-src/_shared/owner-module-data.ts +2 -2
  46. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-appointments/index.ts +4 -1
package/dist/cli.mjs CHANGED
@@ -3056,14 +3056,14 @@ function confinementMatcher(contentRoot, extraRoots = []) {
3056
3056
  linked = [];
3057
3057
  const scanned = /* @__PURE__ */ new Set();
3058
3058
  const queue = [...roots];
3059
- const CAP4 = 4096;
3059
+ const CAP5 = 4096;
3060
3060
  const take = (abs) => {
3061
3061
  const target = realpathOrSelf(abs);
3062
- if (scanned.has(target) || linked.length >= CAP4) return;
3062
+ if (scanned.has(target) || linked.length >= CAP5) return;
3063
3063
  linked.push(target);
3064
3064
  queue.push(target);
3065
3065
  };
3066
- while (queue.length && linked.length < CAP4) {
3066
+ while (queue.length && linked.length < CAP5) {
3067
3067
  const r = queue.shift();
3068
3068
  for (const nm of [r, join12(r, "node_modules")]) {
3069
3069
  if (scanned.has(nm)) continue;
@@ -8206,8 +8206,8 @@ function findSkipDerive(bundleJs) {
8206
8206
  if (touchedBase.length === 0) continue;
8207
8207
  const missingDerived = [...outputUnion].filter((o) => !keySet.has(o));
8208
8208
  if (missingDerived.length === 0) continue;
8209
- const snippet2 = objBody.replace(/\s+/g, " ").trim();
8210
- findings.push({ touchedBase, missingDerived, snippet: `{ ${snippet2.slice(0, 70)}${snippet2.length > 70 ? " \u2026" : ""} }` });
8209
+ const snippet3 = objBody.replace(/\s+/g, " ").trim();
8210
+ findings.push({ touchedBase, missingDerived, snippet: `{ ${snippet3.slice(0, 70)}${snippet3.length > 70 ? " \u2026" : ""} }` });
8211
8211
  }
8212
8212
  return findings;
8213
8213
  } catch {
@@ -8812,8 +8812,8 @@ function validateScreenDescriptor(screenId, descriptor, opts = {}) {
8812
8812
  if (a11yMode !== "off") {
8813
8813
  const severity = a11yMode === "warn" && !strict ? "warning" : "error";
8814
8814
  const gaps = accessibleNameGaps(descriptor);
8815
- const CAP4 = 20;
8816
- for (const gap of gaps.slice(0, CAP4)) {
8815
+ const CAP5 = 20;
8816
+ for (const gap of gaps.slice(0, CAP5)) {
8817
8817
  const row = Object.prototype.hasOwnProperty.call(A11Y_CONTRACT, gap.type) ? A11Y_CONTRACT[gap.type] : void 0;
8818
8818
  const why = gap.reason === "interactive" ? `it carries an \`onTap\`/\`onLongPress\`/\`onDoubleTap\`, which promotes ANY node to a control that owes a name (RFC 0058 \xA73.4)` : `RFC 0058 \xA73.3 requires a name for \`${gap.type}\` (announced as \`${gap.role}\`)`;
8819
8819
  const fix = gap.reason === "contract" ? `Give it \`modifiers.accessibilityLabel\`, or copy on a descendant \u2014 or, if it really is presentational, say so with \`modifiers.accessibilityRole: "decorative"\` (node-local) or \`modifiers.accessibilityHidden: true\` (whole subtree).` : `Give it \`modifiers.accessibilityLabel\`, or put readable copy on a descendant. An icon-only control names itself with \`Icon.contentDescription\`. \`decorative\` does not silence a control${row?.decorative ? ` \u2014 a tappable \`${gap.type}\` is one` : ""}.`;
@@ -8824,11 +8824,11 @@ function validateScreenDescriptor(screenId, descriptor, opts = {}) {
8824
8824
  message: `${gap.type} renders no accessible name anywhere in its subtree, and ${why}. A screen reader announces this node as an unnamed \`${gap.role}\`. ${fix}`
8825
8825
  });
8826
8826
  }
8827
- if (gaps.length > CAP4) {
8827
+ if (gaps.length > CAP5) {
8828
8828
  issues.push({
8829
8829
  severity,
8830
8830
  pass: "a11y",
8831
- message: `+${gaps.length - CAP4} more node(s) on this screen render no accessible name (${gaps.length} in total). Fix the ${CAP4} above and re-run to see the rest.`
8831
+ message: `+${gaps.length - CAP5} more node(s) on this screen render no accessible name (${gaps.length} in total). Fix the ${CAP5} above and re-run to see the rest.`
8832
8832
  });
8833
8833
  }
8834
8834
  }
@@ -13417,12 +13417,12 @@ async function checkReport(appDir2, opts = {}) {
13417
13417
  function resolveFamilyName(input) {
13418
13418
  const catalog = loadCatalog();
13419
13419
  if (renderableFamilies(catalog).has(input)) return input;
13420
- const norm2 = input.toLowerCase().replace(/[^a-z0-9]/g, "");
13420
+ const norm3 = input.toLowerCase().replace(/[^a-z0-9]/g, "");
13421
13421
  for (const name of renderableFamilies(catalog)) {
13422
- if (name.toLowerCase().replace(/[^a-z0-9]/g, "") === norm2) return name;
13422
+ if (name.toLowerCase().replace(/[^a-z0-9]/g, "") === norm3) return name;
13423
13423
  }
13424
13424
  for (const [name, fam] of [...Object.entries(catalog.families), ...Object.entries(catalog.bundled)]) {
13425
- if ((fam.aliases ?? []).some((a) => a.toLowerCase().replace(/[^a-z0-9]/g, "") === norm2)) return name;
13425
+ if ((fam.aliases ?? []).some((a) => a.toLowerCase().replace(/[^a-z0-9]/g, "") === norm3)) return name;
13426
13426
  }
13427
13427
  return void 0;
13428
13428
  }
@@ -20137,6 +20137,7 @@ function shouldRevealError(args) {
20137
20137
  var EMAIL_RE, URL_HOST_RE, INTEGER_RE, NUMBER_RE;
20138
20138
  var init_validation = __esm({
20139
20139
  "../../core/ts/src/validation.ts"() {
20140
+ "use strict";
20140
20141
  EMAIL_RE = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
20141
20142
  URL_HOST_RE = /^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(:[0-9]+)?$/;
20142
20143
  INTEGER_RE = /^[+-]?[0-9]+$/;
@@ -23723,6 +23724,7 @@ function validateWorkerSubtree(raw, slotId, policy, actionCatalog) {
23723
23724
  var WORKER_SUBTREE_CEILINGS, ID_PART, ACTION_ID;
23724
23725
  var init_worker_subtree = __esm({
23725
23726
  "../../core/ts/src/worker-subtree.ts"() {
23727
+ "use strict";
23726
23728
  init_worker_subtree_safe_types_generated();
23727
23729
  WORKER_SUBTREE_CEILINGS = Object.freeze({
23728
23730
  maxEncodedBytes: 65536,
@@ -26851,7 +26853,7 @@ var init_renderNode = __esm({
26851
26853
  const hostRef = React5.useRef(null);
26852
26854
  const autoplayConsumed = React5.useRef(false);
26853
26855
  const item = resolvedItems[index];
26854
- const mediaKey = `${index}:${item?.id ?? item?.url ?? ""}`;
26856
+ const mediaKey2 = `${index}:${item?.id ?? item?.url ?? ""}`;
26855
26857
  React5.useEffect(() => {
26856
26858
  const outgoing = mediaRef.current;
26857
26859
  return () => {
@@ -26860,7 +26862,7 @@ var init_renderNode = __esm({
26860
26862
  } catch {
26861
26863
  }
26862
26864
  };
26863
- }, [mediaKey]);
26865
+ }, [mediaKey2]);
26864
26866
  const sessionId = String(props.sessionId || nodeId || "media");
26865
26867
  const provider = String(item?.provider || "direct").toLowerCase();
26866
26868
  const isYouTube = provider === "youtube";
@@ -27217,7 +27219,7 @@ var init_renderNode = __esm({
27217
27219
  provider,
27218
27220
  "."
27219
27221
  ] })
27220
- ] }) : isYouTube ? /* @__PURE__ */ jsx6("iframe", { title: String(item?.title ?? "YouTube video"), src: `https://www.youtube-nocookie.com/embed/${encodeURIComponent(youtubeId(item))}?playsinline=1&autoplay=${props.autoplay === "never" ? 0 : 1}&mute=${props.muted === true ? 1 : 0}&controls=${chrome === "none" ? 0 : 1}`, allow: "autoplay; encrypted-media; picture-in-picture; fullscreen", allowFullScreen: true, style: { border: 0, width: "100%", flex: 1, minHeight: 0 } }, mediaKey) : isAudio ? /* @__PURE__ */ jsxs3("div", { style: { position: "relative", flex: 1, minHeight: 0, display: "grid", gridTemplateColumns: props.presentation === "compact" || props.presentation === "mini" ? "72px 1fr" : "1fr", alignItems: "center", gap: 16, padding: hidden ? 0 : 16, boxSizing: "border-box" }, children: [
27222
+ ] }) : isYouTube ? /* @__PURE__ */ jsx6("iframe", { title: String(item?.title ?? "YouTube video"), src: `https://www.youtube-nocookie.com/embed/${encodeURIComponent(youtubeId(item))}?playsinline=1&autoplay=${props.autoplay === "never" ? 0 : 1}&mute=${props.muted === true ? 1 : 0}&controls=${chrome === "none" ? 0 : 1}`, allow: "autoplay; encrypted-media; picture-in-picture; fullscreen", allowFullScreen: true, style: { border: 0, width: "100%", flex: 1, minHeight: 0 } }, mediaKey2) : isAudio ? /* @__PURE__ */ jsxs3("div", { style: { position: "relative", flex: 1, minHeight: 0, display: "grid", gridTemplateColumns: props.presentation === "compact" || props.presentation === "mini" ? "72px 1fr" : "1fr", alignItems: "center", gap: 16, padding: hidden ? 0 : 16, boxSizing: "border-box" }, children: [
27221
27223
  poster && /* @__PURE__ */ jsx6("img", { src: poster, alt: "", style: chrome === "custom" ? { position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", opacity: 0.62 } : { width: props.presentation === "compact" || props.presentation === "mini" ? 72 : "min(55%, 260px)", aspectRatio: "1", objectFit: "cover", borderRadius: ctx.radius("md", 8), justifySelf: "center" } }),
27222
27224
  chrome === "custom" && /* @__PURE__ */ jsx6("div", { style: { position: "absolute", inset: 0, background: "linear-gradient(transparent,rgba(0,0,0,.75))" } }),
27223
27225
  !hidden && chrome !== "custom" && /* @__PURE__ */ jsxs3("div", { style: { textAlign: props.presentation === "compact" || props.presentation === "mini" ? "left" : "center", minWidth: 0 }, children: [
@@ -27226,10 +27228,10 @@ var init_renderNode = __esm({
27226
27228
  ] }),
27227
27229
  /* @__PURE__ */ jsx6("audio", { ref: (el) => {
27228
27230
  mediaRef.current = el;
27229
- }, ...commonMedia, children: renderMediaTracks() }, mediaKey)
27231
+ }, ...commonMedia, children: renderMediaTracks() }, mediaKey2)
27230
27232
  ] }) : /* @__PURE__ */ jsx6("video", { ref: (el) => {
27231
27233
  mediaRef.current = el;
27232
- }, poster: poster || void 0, ...commonMedia, style: { width: "100%", flex: 1, minHeight: 0, display: "block", objectFit: props.contentMode === "fill" ? "cover" : "contain", background: "#000" }, children: renderMediaTracks() }, mediaKey),
27234
+ }, poster: poster || void 0, ...commonMedia, style: { width: "100%", flex: 1, minHeight: 0, display: "block", objectFit: props.contentMode === "fill" ? "cover" : "contain", background: "#000" }, children: renderMediaTracks() }, mediaKey2),
27233
27235
  status === "idle" && slot("poster"),
27234
27236
  slot("overlay"),
27235
27237
  status === "loading" && slot("loading"),
@@ -41685,7 +41687,7 @@ function assertRedirectUri(value) {
41685
41687
  }
41686
41688
  return trimmed;
41687
41689
  }
41688
- var BOOK_SCHEMA_VERSION, KNOWN_VERTICALS;
41690
+ var BOOK_SCHEMA_VERSION, KNOWN_VERTICALS, REDIRECT_NEEDS_CLIENT;
41689
41691
  var init_types2 = __esm({
41690
41692
  "src/site-analyze/types.ts"() {
41691
41693
  BOOK_SCHEMA_VERSION = 2;
@@ -41719,6 +41721,7 @@ var init_types2 = __esm({
41719
41721
  "galleries",
41720
41722
  "inbox"
41721
41723
  ];
41724
+ REDIRECT_NEEDS_CLIENT = "--redirect-uri is the redirect REGISTERED on --client-id, so it needs --client-id too";
41722
41725
  }
41723
41726
  });
41724
41727
 
@@ -49340,8 +49343,7 @@ async function localizeConfigResponse(res, kind, header) {
49340
49343
  }
49341
49344
  function makeRunHandler(opts) {
49342
49345
  const mount = opts.chrome === "app" ? "/app" : "/preview";
49343
- const serve = makeServeHandler({
49344
- store: opts.store,
49346
+ const serveOpts = {
49345
49347
  defaultApp: opts.app,
49346
49348
  // A local plane is a pin: nothing mutates, and the owner rungs cannot work anyway — a service
49347
49349
  // worker never sees a `Cookie` header (it is a forbidden request header in both directions), so
@@ -49356,7 +49358,9 @@ function makeRunHandler(opts) {
49356
49358
  webwixBridge: Boolean(opts.serve?.webwixFetch),
49357
49359
  manifestCacheTtlMs: 0,
49358
49360
  ...opts.serve
49359
- });
49361
+ };
49362
+ const serve = makeServeHandler({ store: opts.store, ...serveOpts });
49363
+ const ownerRungOn = serveOpts.webwixOwnerRung !== false;
49360
49364
  return async (request) => {
49361
49365
  const url = new URL(request.url);
49362
49366
  const path2 = url.pathname;
@@ -49377,6 +49381,10 @@ function makeRunHandler(opts) {
49377
49381
  if (path2.startsWith("/v1/flags/")) {
49378
49382
  return json4(200, { app: opts.app, definitions: {} }, { "cache-control": "no-store" });
49379
49383
  }
49384
+ if (path2 === "/v1/owner/sites" && !ownerRungOn && (request.method === "GET" || request.method === "HEAD")) {
49385
+ const headers2 = { "cache-control": "no-store" };
49386
+ return request.method === "HEAD" ? new Response(null, { status: 200, headers: { "content-type": "application/json; charset=utf-8", ...headers2 } }) : json4(200, { sites: [] }, headers2);
49387
+ }
49380
49388
  if (path2 === "/v1/telemetry/ingest") {
49381
49389
  if (request.method !== "POST") return json4(405, { error: "method_not_allowed" });
49382
49390
  try {
@@ -49579,21 +49587,21 @@ var init_preview_assets_generated = __esm({
49579
49587
  "../../packages/serve/src/preview-assets.generated.ts"() {
49580
49588
  init_preview_assets_format();
49581
49589
  PREVIEW_ASSET_MANIFEST = [
49582
- { key: "assets/index-BWCUbjcx.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1051200, sha256: "bc39b1298d3c675988ae421ad8950e8ef46a46b6e8e3644aaea24e6ab1a1cd21" },
49583
- { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1051208, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
49584
- { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1055818, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
49585
- { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1544402, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
49586
- { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1635326, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
49587
- { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1726310, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
49588
- { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1817290, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
49589
- { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1908162, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
49590
- { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 1999142, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
49591
- { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2107778, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
49592
- { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2216862, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
49593
- { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2325910, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
49594
- { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2433610, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
49595
- { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2521718, length: 4947, sha256: "3d489aa5d99040dfa05a2175da5de975b1ebdd9f6d855c5609074cbc2c592d27" },
49596
- { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2526665, length: 17209, sha256: "dffc9718e2faa63ae2cc919e0a842f585ed565aaaa7c53778e90379442c731b6" }
49590
+ { key: "assets/index-3uQs-pBH.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1050432, sha256: "928dfeb0c4f367f39a34c92f32a7194f483918a869912cf33ad05bc8d6f8a483" },
49591
+ { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1050440, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
49592
+ { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1055050, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
49593
+ { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1543634, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
49594
+ { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1634558, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
49595
+ { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1725542, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
49596
+ { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1816522, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
49597
+ { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1907394, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
49598
+ { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 1998374, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
49599
+ { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2107010, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
49600
+ { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2216094, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
49601
+ { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2325142, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
49602
+ { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2432842, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
49603
+ { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2520950, length: 4947, sha256: "d5b6c88c0e67711667b4df9d04e74e4a77af7131bd8082754447cbfd69a173a8" },
49604
+ { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2525897, length: 17209, sha256: "dffc9718e2faa63ae2cc919e0a842f585ed565aaaa7c53778e90379442c731b6" }
49597
49605
  ];
49598
49606
  }
49599
49607
  });
@@ -52043,14 +52051,17 @@ var init_visitor_auth = __esm({
52043
52051
  });
52044
52052
 
52045
52053
  // src/site-analyze/copy-clean.ts
52054
+ function isTemplatePlaceholder(text5) {
52055
+ return PLACEHOLDER_RE.test(text5);
52056
+ }
52046
52057
  function cleanText(raw, cap) {
52047
52058
  const s = String(raw ?? "").replace(/[\u0000-\u001F\u007F]/g, " ").replace(/\s+/g, " ").trim();
52048
52059
  return s.length > cap ? `${s.slice(0, cap - 1).trimEnd()}\u2026` : s;
52049
52060
  }
52050
52061
  function stripPii(text5) {
52051
52062
  return text5.replace(EMAIL_RE2, "").replace(PHONE_SHAPE_RE, (m) => {
52052
- const digits = (m.match(ANY_DIGIT_RE) ?? []).length;
52053
- return digits >= PHONE_MIN_DIGITS && PHONE_DIGIT_RUN_RE.test(m) ? "" : m;
52063
+ const digits2 = (m.match(ANY_DIGIT_RE) ?? []).length;
52064
+ return digits2 >= PHONE_MIN_DIGITS && PHONE_DIGIT_RUN_RE.test(m) ? "" : m;
52054
52065
  }).replace(/\s+/g, " ").trim();
52055
52066
  }
52056
52067
  function isBoilerplate(text5) {
@@ -52086,7 +52097,7 @@ var init_copy_clean = __esm({
52086
52097
  PHONE_DIGIT_RUN_RE = new RegExp(`[${DIGIT}]{3}`);
52087
52098
  ANY_DIGIT_RE = new RegExp(`[${DIGIT}]`, "g");
52088
52099
  BOILERPLATE_RE = /powered by wix|this site was designed with|wix\.com|all rights reserved|©|\(c\)\s*\d{4}|privacy policy|terms (of use|and conditions)|accessibility statement|cookie (policy|settings|preferences)|accept all|manage preferences|do not sell/i;
52089
- PLACEHOLDER_RE = /i'?m a (paragraph|title|description)|click here to add|add your own text|double click to edit|this is a great (place|space)|tell your visitors|lorem ipsum|service title|item title|your paragraph text/i;
52100
+ PLACEHOLDER_RE = /i'?m a (paragraph|title|description)|click here to add|add your own text|double click to edit|this is a great (place|space)|tell your visitors|lorem ipsum|service title|item title|your paragraph text|tell clients about what you['’]?re offering|to add a description,? click|click manage services|booking settings\s*>\s*booking polic|in your dashboard,? go to|go to booking settings/i;
52090
52101
  ABOUT_HEADING_RE_SOURCE = "^(?:about\\b|who we are|who are we|our story|our mission|\u05DE\u05D9 \u05D0\u05E0\u05D7\u05E0\u05D5)";
52091
52102
  }
52092
52103
  });
@@ -52158,6 +52169,49 @@ function planDetail(p) {
52158
52169
  const bits = [cycle, perks.slice(0, 4).join("; ")].filter(Boolean);
52159
52170
  return bits.length ? bits.join(" \xB7 ") : void 0;
52160
52171
  }
52172
+ function moneyText(m) {
52173
+ if (!m || typeof m !== "object") return void 0;
52174
+ const o = m;
52175
+ const formatted = str4(o.formattedValue ?? o.formatted);
52176
+ if (formatted.trim()) return formatted.trim();
52177
+ const raw = o.value ?? o.amount;
52178
+ const value = typeof raw === "number" ? String(raw) : str4(raw);
52179
+ if (!value.trim()) return void 0;
52180
+ return [value.trim(), str4(o.currency).trim()].filter(Boolean).join(" ");
52181
+ }
52182
+ function servicePrice(s) {
52183
+ const payment = s.payment && typeof s.payment === "object" ? s.payment : void 0;
52184
+ if (!payment) return void 0;
52185
+ const rate = str4(payment.rateType).toUpperCase();
52186
+ const fixed = payment.fixed;
52187
+ const varied = payment.varied;
52188
+ const custom = payment.custom;
52189
+ if (rate === "NO_FEE") return void 0;
52190
+ if (rate === "VARIED" || !rate && varied) {
52191
+ const min = moneyText(varied?.minPrice);
52192
+ return moneyText(varied?.defaultPrice) ?? (min ? `from ${min}` : void 0);
52193
+ }
52194
+ if (rate === "CUSTOM" || !rate && custom) {
52195
+ return copyOrDrop(custom?.description, 80) || void 0;
52196
+ }
52197
+ const legacy = typeof payment.price === "number" || typeof payment.price === "string" ? moneyText({ value: payment.price, currency: payment.currency ?? s.currency }) : moneyText(payment.price);
52198
+ return moneyText(fixed?.price) ?? legacy;
52199
+ }
52200
+ function serviceDetail(s) {
52201
+ const payment = s.payment && typeof s.payment === "object" ? s.payment : void 0;
52202
+ if (!payment) return void 0;
52203
+ if (str4(payment.rateType).toUpperCase() === "NO_FEE") return "free";
52204
+ const options = payment.options && typeof payment.options === "object" ? payment.options : {};
52205
+ const hasPlanIds = asArr(payment.pricingPlanIds).length > 0;
52206
+ const modes = [
52207
+ options.online === true && "online",
52208
+ options.inPerson === true && "in person",
52209
+ (options.pricingPlan === true || hasPlanIds) && "with a pricing plan"
52210
+ ].filter((m) => !!m);
52211
+ if (!modes.length) return void 0;
52212
+ const how = modes.length === 1 ? `pay ${modes[0]} only` : `pay ${modes.slice(0, -1).join(", ")} or ${modes[modes.length - 1]}`;
52213
+ return options.deposit === true ? `${how} (deposit)` : how;
52214
+ }
52161
52215
  function formLabels(x, depth = 0, out = []) {
52162
52216
  if (x == null || depth > 5) return out;
52163
52217
  if (Array.isArray(x)) {
@@ -52178,7 +52232,7 @@ function formDetail(f) {
52178
52232
  return names.length ? names.join(", ") : void 0;
52179
52233
  }
52180
52234
  function side(call, def) {
52181
- if (!call) return Promise.resolve({ status: "error", count: 0, note: "no-auth", items: [] });
52235
+ if (!call) return Promise.resolve({ status: "skipped", count: 0, note: "no-auth", items: [] });
52182
52236
  const attempts = [
52183
52237
  { method: def.method, path: def.path, body: def.body, itemsOf: def.itemsOf },
52184
52238
  ...def.fallbacks ?? []
@@ -52208,14 +52262,20 @@ function side(call, def) {
52208
52262
  })();
52209
52263
  }
52210
52264
  function toItems(def, raw) {
52211
- return raw.slice(0, 12).map((it, i) => ({
52212
- id: str4((def.idOf ? def.idOf(it) : it.id) ?? `row-${i}`),
52213
- title: def.titleOf(it) || "(untitled)",
52214
- imageUrl: def.imageOf?.(it),
52215
- price: def.priceOf?.(it) || str4(it.price?.formatted) || str4(it.formattedPrice) || void 0,
52216
- detail: def.detailOf?.(it),
52217
- description: copyOrDrop(def.descriptionOf ? def.descriptionOf(it) : genericDescription(it), 240) || void 0
52218
- }));
52265
+ return raw.slice(0, 12).map((it, i) => {
52266
+ const rawDescription = def.descriptionOf ? def.descriptionOf(it) : genericDescription(it);
52267
+ const description = copyOrDrop(rawDescription, 240) || void 0;
52268
+ const template = rawDescription && !description && isTemplatePlaceholder(rawDescription) ? stripPii(cleanText(rawDescription, 90)) : void 0;
52269
+ return {
52270
+ id: str4((def.idOf ? def.idOf(it) : it.id) ?? `row-${i}`),
52271
+ title: def.titleOf(it) || "(untitled)",
52272
+ imageUrl: def.imageOf?.(it),
52273
+ price: def.priceOf?.(it) || str4(it.price?.formatted) || str4(it.formattedPrice) || void 0,
52274
+ detail: def.detailOf?.(it),
52275
+ description,
52276
+ ...template ? { templateText: template } : {}
52277
+ };
52278
+ });
52219
52279
  }
52220
52280
  async function runDualProbes(owner, visitor, definitions2 = ALL) {
52221
52281
  const rows = await Promise.all(definitions2.map(async (def) => {
@@ -52225,7 +52285,10 @@ async function runDualProbes(owner, visitor, definitions2 = ALL) {
52225
52285
  vertical: def.vertical,
52226
52286
  owner: { status: o.status, count: o.count, httpStatus: o.httpStatus, note: o.note },
52227
52287
  visitor: { status: vis.status, count: vis.count, httpStatus: vis.httpStatus, note: vis.note },
52228
- appInvisible: o.count > 0 && vis.count === 0 && vis.status !== "wrong-path",
52288
+ // Only a visitor request that was actually MADE can show the app cannot see a row. With no
52289
+ // client id (#3269) the visitor side is `skipped`, and every owner-visible vertical would
52290
+ // otherwise read APP-INVISIBLE off a request nobody sent (codebot review on #3283).
52291
+ appInvisible: o.count > 0 && vis.count === 0 && vis.status !== "wrong-path" && vis.status !== "skipped",
52229
52292
  imageless,
52230
52293
  items: toItems(def, o.items.length ? o.items : vis.items)
52231
52294
  };
@@ -52416,7 +52479,9 @@ var init_probes = __esm({
52416
52479
  body: { query: { paging: { limit: 50 } } },
52417
52480
  itemsOf: (d) => asArr(d.services),
52418
52481
  titleOf: (s) => str4(s.name),
52419
- imageOf: serviceImage
52482
+ imageOf: serviceImage,
52483
+ priceOf: servicePrice,
52484
+ detailOf: serviceDetail
52420
52485
  },
52421
52486
  {
52422
52487
  vertical: "events",
@@ -53855,6 +53920,10 @@ function taglineFrom(identity, pages, published) {
53855
53920
  const hero = copyOrDrop(home?.heroCopy, CAP2.tagline);
53856
53921
  return hero ? { text: hero, source: "hero" } : null;
53857
53922
  }
53923
+ function tabLabel(label2) {
53924
+ const trimmed = label2.replace(/\s+page$/i, "").trim();
53925
+ return trimmed && trimmed !== label2.trim() && !/^(front|landing|title|web)$/i.test(trimmed) ? trimmed : label2;
53926
+ }
53858
53927
  function suggestKeys(args) {
53859
53928
  const { nav, sections, tagline, installedVerticals } = args;
53860
53929
  const out = {};
@@ -53868,7 +53937,7 @@ function suggestKeys(args) {
53868
53937
  const allowed2 = (rule) => !rule.vertical || installed.has(rule.vertical);
53869
53938
  for (const label2 of nav) {
53870
53939
  for (const rule of KEY_RULES) {
53871
- if (rule.tab && allowed2(rule) && rule.re.test(label2)) set(rule.tab, label2);
53940
+ if (rule.tab && allowed2(rule) && rule.re.test(label2)) set(rule.tab, tabLabel(label2));
53872
53941
  }
53873
53942
  }
53874
53943
  for (const section2 of sections) {
@@ -54458,6 +54527,234 @@ var init_net_guard = __esm({
54458
54527
  }
54459
54528
  });
54460
54529
 
54530
+ // src/site-analyze/placeholders.ts
54531
+ function textFields(look) {
54532
+ if (look.unavailable) return [];
54533
+ const out = [];
54534
+ const add = (text5, where, narrative = false) => {
54535
+ if (text5 && text5.trim()) out.push({ text: text5, where, narrative });
54536
+ };
54537
+ for (const label2 of look.navLabels) add(label2, "nav");
54538
+ for (const p of look.pages) {
54539
+ for (const label2 of p.navLabels) add(label2, "nav");
54540
+ add(p.title, `${p.url} (title)`);
54541
+ add(p.heroCopy, p.url, true);
54542
+ for (const h of p.headings) add(h, p.url);
54543
+ for (const c of p.ctaLabels ?? []) add(c, p.url);
54544
+ for (const s of p.sections ?? []) {
54545
+ add(s.heading, p.url);
54546
+ add(s.body, p.url, true);
54547
+ }
54548
+ for (const f of p.faq ?? []) {
54549
+ add(f.q, `${p.url} (faq)`, true);
54550
+ add(f.a, `${p.url} (faq)`, true);
54551
+ }
54552
+ for (const t of p.testimonials ?? []) add(t, `${p.url} (testimonials)`, true);
54553
+ add(p.about, `${p.url} (about)`, true);
54554
+ add(p.meta?.ogTitle, `${p.url} (og:title)`);
54555
+ add(p.meta?.description, `${p.url} (meta)`, true);
54556
+ for (const t of p.templateText ?? []) add(t, p.url);
54557
+ }
54558
+ return out;
54559
+ }
54560
+ function pushWhere(map, key2, value, where) {
54561
+ const row = map.get(key2) ?? { value, where: [] };
54562
+ if (!row.where.includes(where) && row.where.length < CAP3.where) row.where.push(where);
54563
+ map.set(key2, row);
54564
+ }
54565
+ function templateContacts(identity, fields) {
54566
+ const hits = /* @__PURE__ */ new Map();
54567
+ const phone = identity.contact.phone ?? "";
54568
+ if (phone && TEMPLATE_PHONES.has(digits(phone))) pushWhere(hits, `p:${digits(phone)}`, phone.trim(), "identity.contact.phone");
54569
+ const email = identity.contact.email ?? "";
54570
+ if (email && TEMPLATE_EMAIL_RE.test(email)) pushWhere(hits, `e:${email.toLowerCase()}`, email.trim(), "identity.contact.email");
54571
+ const address = identity.contact.address ?? "";
54572
+ if (address && TEMPLATE_ADDRESS_RE.test(address)) pushWhere(hits, "a:terry", "500 Terry Francine Street", "identity.contact.address");
54573
+ for (const f of fields) {
54574
+ for (const m of f.text.matchAll(PHONE_IN_TEXT_RE)) {
54575
+ const d = digits(m[0]);
54576
+ if (TEMPLATE_PHONES.has(d)) pushWhere(hits, `p:${d}`, m[0].trim(), f.where);
54577
+ }
54578
+ const e = f.text.match(TEMPLATE_EMAIL_RE)?.[0];
54579
+ if (e) pushWhere(hits, `e:${e.toLowerCase()}`, e, f.where);
54580
+ if (TEMPLATE_ADDRESS_RE.test(f.text)) pushWhere(hits, "a:terry", "500 Terry Francine Street", f.where);
54581
+ }
54582
+ return [...hits.values()].slice(0, CAP3.perKind).map((h) => ({
54583
+ kind: "template-contact",
54584
+ value: h.value,
54585
+ where: h.where,
54586
+ note: "a Wix template's sample contact detail, not the business's \u2014 get the real one before any screen shows it"
54587
+ }));
54588
+ }
54589
+ function editorText(fields, probes) {
54590
+ const hits = /* @__PURE__ */ new Map();
54591
+ const all = [
54592
+ ...fields,
54593
+ ...probes.flatMap((p) => p.items.flatMap((it) => [
54594
+ { text: it.title, where: `catalog:${p.vertical}` },
54595
+ { text: it.description ?? "", where: `catalog:${p.vertical}` },
54596
+ // Already dropped from `description` by `copyOrDrop`; `toItems` kept the fact.
54597
+ { text: it.templateText ?? "", where: `catalog:${p.vertical}` }
54598
+ ]))
54599
+ ];
54600
+ for (const f of all) {
54601
+ if (!f.text || !isTemplatePlaceholder(f.text)) continue;
54602
+ const value = snippet(stripPii(f.text));
54603
+ pushWhere(hits, value.toLowerCase(), value, f.where);
54604
+ }
54605
+ return [...hits.values()].slice(0, CAP3.perKind).map((h) => ({
54606
+ kind: "editor-text",
54607
+ value: h.value,
54608
+ where: h.where,
54609
+ note: "unedited Wix template/editor text the published site still shows \u2014 dropped from book.copy; do not ship it, and tell the owner the site shows it"
54610
+ }));
54611
+ }
54612
+ function stripLead(name) {
54613
+ const [left = "", ...rest] = name.split(/\s+&\s+/);
54614
+ const words = left.split(/\s+/);
54615
+ let cut = 0;
54616
+ words.forEach((w, i) => {
54617
+ if (LEAD_STOPWORDS.has(w.toLowerCase())) cut = i + 1;
54618
+ });
54619
+ const kept = words.slice(cut).join(" ");
54620
+ return kept ? [kept, ...rest].join(" & ") : "";
54621
+ }
54622
+ function nameMismatches(identity, look, fields) {
54623
+ const name = (identity.businessName || identity.siteDisplayName || "").trim();
54624
+ const n = norm2(name);
54625
+ if (n.length < 3) return [];
54626
+ const pageNames = /* @__PURE__ */ new Set([
54627
+ ...(look.sitePages ?? []).map((p) => norm2(p.title)),
54628
+ ...look.navLabels.map(norm2)
54629
+ ]);
54630
+ const differs = (candidate) => {
54631
+ const c = norm2(candidate);
54632
+ return c.length >= 3 && c !== n && !c.includes(n) && !n.includes(c) && !pageNames.has(c);
54633
+ };
54634
+ const hits = /* @__PURE__ */ new Map();
54635
+ for (const f of fields) {
54636
+ if (/\((?:og:)?title\)$/.test(f.where)) {
54637
+ const parts = f.text.split(TITLE_SPLIT_RE).map((s) => s.trim()).filter(Boolean);
54638
+ const site = parts.length >= 2 ? parts[parts.length - 1] : "";
54639
+ if (site && differs(site)) pushWhere(hits, norm2(site), site, f.where);
54640
+ }
54641
+ for (const m of f.text.matchAll(COPYRIGHT_BY_RE)) {
54642
+ const who = m[1].trim();
54643
+ if (differs(who)) pushWhere(hits, norm2(who), who, f.where);
54644
+ }
54645
+ if (!f.narrative) continue;
54646
+ for (const m of f.text.matchAll(AMP_NAME_RE)) {
54647
+ const who = stripLead(m[1].trim());
54648
+ if (who.includes("&") && differs(who)) pushWhere(hits, norm2(who), who, f.where);
54649
+ }
54650
+ }
54651
+ return [...hits.values()].slice(0, CAP3.perKind).map((h) => ({
54652
+ kind: "name-mismatch",
54653
+ value: h.value,
54654
+ where: h.where,
54655
+ note: `the business is "${name}" but the site also calls itself "${h.value}" \u2014 likely the template's name left in the copy; confirm with the owner which is theirs before reusing any sentence that says it`
54656
+ }));
54657
+ }
54658
+ function mediaKey(url) {
54659
+ return wixMediaFileId(url) ?? url.split(/[?#]/)[0];
54660
+ }
54661
+ function duplicateMedia(look, probes) {
54662
+ const out = [];
54663
+ const scan = (urls, where) => {
54664
+ const counts = /* @__PURE__ */ new Map();
54665
+ for (const u of urls) {
54666
+ if (!u) continue;
54667
+ const k = mediaKey(u);
54668
+ counts.set(k, (counts.get(k) ?? 0) + 1);
54669
+ }
54670
+ for (const [k, count] of counts) {
54671
+ if (count < DUPLICATE_MEDIA_MIN || out.length >= CAP3.perKind) continue;
54672
+ out.push({
54673
+ kind: "duplicate-media",
54674
+ value: k.slice(0, 80),
54675
+ where: [where],
54676
+ note: `the same image is used ${count} times here \u2014 template filler, not ${count} distinct photos; do not present it as the business's gallery`
54677
+ });
54678
+ }
54679
+ };
54680
+ if (!look.unavailable) for (const p of look.pages) scan(p.imageUrls, p.url);
54681
+ for (const p of probes) scan(p.items.map((it) => it.imageUrl ?? ""), `catalog:${p.vertical}`);
54682
+ return out;
54683
+ }
54684
+ function detectPlaceholders(args) {
54685
+ const fields = textFields(args.look);
54686
+ return [
54687
+ ...templateContacts(args.identity, fields),
54688
+ ...editorText(fields, args.probes),
54689
+ ...nameMismatches(args.identity, args.look, fields),
54690
+ ...duplicateMedia(args.look, args.probes)
54691
+ ];
54692
+ }
54693
+ function placeholderWarnings(findings) {
54694
+ const byKind = /* @__PURE__ */ new Map();
54695
+ for (const f of findings) byKind.set(f.kind, [...byKind.get(f.kind) ?? [], f]);
54696
+ const quote = (rows) => rows.slice(0, 3).map((r) => `"${r.value}"`).join(", ");
54697
+ const out = [];
54698
+ const contact = byKind.get("template-contact");
54699
+ if (contact) out.push(`template placeholder contact details on the site: ${quote(contact)} \u2014 the Wix template's samples, not the business's; get the real ones before any screen shows them (book.placeholders)`);
54700
+ const editor = byKind.get("editor-text");
54701
+ if (editor) out.push(`${editor.length} unedited Wix editor/template string(s) on the published site, e.g. ${quote(editor.slice(0, 1))} \u2014 dropped from book.copy, never ship them (book.placeholders)`);
54702
+ const names = byKind.get("name-mismatch");
54703
+ if (names) out.push(`the site names the business inconsistently \u2014 it also says ${quote(names)}; likely the template's name left in the copy, so confirm with the owner (book.placeholders)`);
54704
+ const media = byKind.get("duplicate-media");
54705
+ if (media) out.push(`${media.length} place(s) repeat one image ${DUPLICATE_MEDIA_MIN}+ times (${media.slice(0, 3).map((m) => m.where[0]).join(", ")}) \u2014 template filler, not real photos (book.placeholders)`);
54706
+ return out;
54707
+ }
54708
+ var TEMPLATE_PHONES, TEMPLATE_EMAIL_RE, TEMPLATE_ADDRESS_RE, PHONE_IN_TEXT_RE, DUPLICATE_MEDIA_MIN, CAP3, digits, norm2, snippet, LEAD_STOPWORDS, AMP_NAME_RE, COPYRIGHT_BY_RE, TITLE_SPLIT_RE;
54709
+ var init_placeholders = __esm({
54710
+ "src/site-analyze/placeholders.ts"() {
54711
+ init_copy_clean();
54712
+ init_rest();
54713
+ TEMPLATE_PHONES = /* @__PURE__ */ new Set(["1234567890", "11234567890", "5555555555", "15555555555", "0000000000"]);
54714
+ TEMPLATE_EMAIL_RE = /[\w.+-]+@(?:my-?site|my-?domain|mydomain|your-?domain|your-?site|example|domain)\.(?:com|org|net|co)\b/i;
54715
+ TEMPLATE_ADDRESS_RE = /\b500 Terry Franc(?:ine|ois)\b/i;
54716
+ PHONE_IN_TEXT_RE = /\+?\d[\d\s().-]{6,}\d/g;
54717
+ DUPLICATE_MEDIA_MIN = 3;
54718
+ CAP3 = { perKind: 6, where: 6, snippet: 90 };
54719
+ digits = (s) => s.replace(/\D/g, "");
54720
+ norm2 = (s) => s.toLowerCase().normalize("NFKD").replace(/[^\p{L}\p{N}]+/gu, "");
54721
+ snippet = (s) => {
54722
+ const t = s.replace(/\s+/g, " ").trim();
54723
+ return t.length > CAP3.snippet ? `${t.slice(0, CAP3.snippet - 1).trimEnd()}\u2026` : t;
54724
+ };
54725
+ LEAD_STOPWORDS = /* @__PURE__ */ new Set([
54726
+ "at",
54727
+ "by",
54728
+ "with",
54729
+ "from",
54730
+ "choose",
54731
+ "call",
54732
+ "contact",
54733
+ "welcome",
54734
+ "about",
54735
+ "to",
54736
+ "for",
54737
+ "in",
54738
+ "on",
54739
+ "our",
54740
+ "why",
54741
+ "is",
54742
+ "are",
54743
+ "do",
54744
+ "does",
54745
+ "the",
54746
+ "and",
54747
+ "meet",
54748
+ "join",
54749
+ "trust",
54750
+ "hire"
54751
+ ]);
54752
+ AMP_NAME_RE = new RegExp("(\\p{Lu}[\\p{L}'\u2019]*(?:\\s+\\p{Lu}[\\p{L}'\u2019]*){0,2}\\s+&\\s+\\p{Lu}[\\p{L}'\u2019]*(?:\\s+\\p{Lu}[\\p{L}'\u2019]*){0,1})", "gu");
54753
+ COPYRIGHT_BY_RE = /©\s*\d{4}\s+by\s+([^.|\n©]{2,40})/giu;
54754
+ TITLE_SPLIT_RE = /\s+[|–—]\s+|\s+-\s+/;
54755
+ }
54756
+ });
54757
+
54461
54758
  // src/site-analyze/analyze.ts
54462
54759
  function siteFetchFor(ports) {
54463
54760
  return ports.siteFetch ?? guardedSiteFetch;
@@ -54468,14 +54765,15 @@ function localeFromSiteLanguage(languageCode) {
54468
54765
  return void 0;
54469
54766
  }
54470
54767
  async function analyzeSite(input, ports) {
54471
- const clientId = input.clientId.trim();
54768
+ const clientId = input.clientId?.trim() || void 0;
54472
54769
  const siteUrl = input.siteUrl.trim();
54473
- if (!clientId) throw new Error("clientId is required");
54474
54770
  if (!siteUrl) throw new Error("siteUrl is required");
54475
54771
  assertPublicSiteUrl(siteUrl);
54476
54772
  const redirectUri = input.redirectUri?.trim() ? assertRedirectUri(input.redirectUri) : void 0;
54477
- const visitorToken = await mintVisitorToken(ports.fetch, clientId);
54478
- const visitor = visitorTransportFrom(ports.fetch, visitorToken, input.msid ?? ports.ownerAuth?.msid);
54773
+ if (redirectUri && !clientId) {
54774
+ throw Object.assign(new Error(REDIRECT_NEEDS_CLIENT), { exit: 2 });
54775
+ }
54776
+ const visitor = clientId ? visitorTransportFrom(ports.fetch, await mintVisitorToken(ports.fetch, clientId), input.msid ?? ports.ownerAuth?.msid) : void 0;
54479
54777
  const owner = ports.ownerAuth ? ownerTransportFrom(ports.fetch, ports.ownerAuth) : void 0;
54480
54778
  const lookP = (ports.browser ? ports.browser.crawl({ siteUrl, budget: 12 }) : Promise.resolve({
54481
54779
  unavailable: ports.lookUnavailableReason ?? "no_browser_on_plane",
@@ -54613,12 +54911,14 @@ async function analyzeSite(input, ports) {
54613
54911
  });
54614
54912
  plan.provisioning.appearance = { homeLayout: traits.homeLayout.id };
54615
54913
  plan.provisioning.navigation = { navKind: traits.navKind.id };
54616
- plan.provisioning.wix = {
54617
- clientId,
54618
- siteUrl,
54619
- // Already validated and trimmed at the top of this function, with the other input guards.
54620
- ...redirectUri ? { redirectUri } : {}
54621
- };
54914
+ if (clientId) {
54915
+ plan.provisioning.wix = {
54916
+ clientId,
54917
+ siteUrl,
54918
+ // Already validated and trimmed at the top of this function, with the other input guards.
54919
+ ...redirectUri ? { redirectUri } : {}
54920
+ };
54921
+ }
54622
54922
  const locale = localeFromSiteLanguage(identity.locale.languageCode || identity.language?.site || "");
54623
54923
  if (locale) plan.provisioning.locale = locale;
54624
54924
  const velo = buildVeloChapter({ look, owner: ownerVelo });
@@ -54638,7 +54938,7 @@ async function analyzeSite(input, ports) {
54638
54938
  const book = {
54639
54939
  schemaVersion: BOOK_SCHEMA_VERSION,
54640
54940
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
54641
- input: { clientId, siteUrl, msid: input.msid, hasOwner: !!owner },
54941
+ input: { ...clientId ? { clientId } : {}, siteUrl, msid: input.msid, hasOwner: !!owner },
54642
54942
  identity,
54643
54943
  probes,
54644
54944
  media: mergeMedia(media, look, identity.logoUrl),
@@ -54653,7 +54953,10 @@ async function analyzeSite(input, ports) {
54653
54953
  velo,
54654
54954
  taxonomy,
54655
54955
  traits,
54656
- permissions
54956
+ permissions,
54957
+ // Template leftovers, recorded in the BOOK (the durable artifact the next agent reads) and
54958
+ // repeated as warnings below for whoever is watching this run (#3276).
54959
+ placeholders: detectPlaceholders({ identity, look, probes })
54657
54960
  };
54658
54961
  if (Object.keys(book.copy.suggestedCopyKeys).length) {
54659
54962
  plan.provisioning.copy = { ...book.copy.suggestedCopyKeys };
@@ -54680,6 +54983,12 @@ async function analyzeSite(input, ports) {
54680
54983
  `${cmsRead.truncated} further CMS collection(s) were not read \u2014 the listing stops at ${CMS_LIST_LIMIT}, so the applied app ships only the ones above.`
54681
54984
  );
54682
54985
  }
54986
+ warnings.push(...placeholderWarnings(book.placeholders));
54987
+ if (!clientId) {
54988
+ warnings.push(
54989
+ owner ? "no --client-id: the VISITOR side of every vertical probe was skipped, so the book cannot say what a shipped app will see, and there is no `wix` binding to --apply. Re-run with --client-id (`himi site client list --site <msid>`) for both." : "no --client-id: this was the URL-only pass (published pages, look, copy). The vertical probes were skipped, so the catalog has no counts, and there is no `wix` binding to --apply. Re-run with --client-id (`himi site client list --site <msid>`) for both."
54990
+ );
54991
+ }
54683
54992
  return {
54684
54993
  book,
54685
54994
  provisioningOverlay: plan.provisioning,
@@ -54797,6 +55106,7 @@ var init_analyze = __esm({
54797
55106
  init_permissions();
54798
55107
  init_published_html();
54799
55108
  init_net_guard();
55109
+ init_placeholders();
54800
55110
  SCREENSHOT_NAME = /^(home|shop|book|menu|blog|about|contact|events|gallery|plans|faq|library|members|other)-\d+\.png$/i;
54801
55111
  }
54802
55112
  });
@@ -54868,7 +55178,7 @@ function renderBookMarkdown(book) {
54868
55178
  lines.push(`# Business book`);
54869
55179
  lines.push("");
54870
55180
  lines.push(`schema ${book.schemaVersion} \xB7 generated ${book.generatedAt}`);
54871
- lines.push(`site: ${yn(book.input.siteUrl)} \xB7 msid: ${yn(book.input.msid)} \xB7 owner: ${book.input.hasOwner}`);
55181
+ lines.push(`site: ${yn(book.input.siteUrl)} \xB7 msid: ${yn(book.input.msid)} \xB7 owner: ${book.input.hasOwner}` + (book.input.clientId ? "" : book.input.hasOwner ? " \xB7 client: none \u2014 visitor-side probes skipped (owner side ran)" : " \xB7 client: none \u2014 URL-only pass, vertical probes skipped"));
54872
55182
  lines.push("");
54873
55183
  lines.push(`## Identity`);
54874
55184
  lines.push(`- name: ${yn(id.businessName)} (${yn(id.siteDisplayName)})`);
@@ -54877,13 +55187,29 @@ function renderBookMarkdown(book) {
54877
55187
  lines.push(`- language: site=${yn(id.language.site)} \xB7 business=${yn(id.language.business)}${id.language.site && id.language.business && id.language.site !== id.language.business ? " (mismatch)" : ""}`);
54878
55188
  lines.push(`- locale: ${yn(id.locale.languageCode)}/${yn(id.locale.country)} ${yn(id.locale.currency)} ${yn(id.locale.timeZone)}`);
54879
55189
  lines.push(`- logo: ${yn(id.logoUrl)}`);
54880
- lines.push(`- contact (PII, do not log): address=${id.contact.address ? "yes" : "no"} email=${id.contact.email ? "yes" : "no"} phone=${id.contact.phone ? "yes" : "no"}`);
55190
+ const templated = new Set(
55191
+ (book.placeholders ?? []).filter((f) => f.kind === "template-contact").flatMap((f) => f.where).filter((w) => w.startsWith("identity.contact."))
55192
+ );
55193
+ const contactFlag = (field) => !id.contact[field] ? "no" : templated.has(`identity.contact.${field}`) ? "TEMPLATE PLACEHOLDER (not the business's)" : "yes";
55194
+ lines.push(`- contact (PII, do not log): address=${contactFlag("address")} email=${contactFlag("email")} phone=${contactFlag("phone")}`);
54881
55195
  lines.push(`- source: ${id.source}`);
54882
55196
  if (id.source === "crawl") {
54883
55197
  lines.push(`- NAME IS A GUESS: no owner token, so the name above was read off the crawled page`);
54884
55198
  lines.push(` \`<title>\` rather than the owner-entered business name. Verify it before it ships.`);
54885
55199
  }
54886
55200
  lines.push("");
55201
+ lines.push(`## Placeholders`);
55202
+ const placeholders = book.placeholders ?? [];
55203
+ if (!placeholders.length) {
55204
+ lines.push(!book.placeholders ? `- not checked (book written before this chapter existed)` : look.unavailable ? `- page text NOT checked \u2014 the crawl did not run (${look.unavailable}); only identity and catalog fields were scanned, and they showed none` : `- none detected (template contact values, editor text, a second business name, repeated images)`);
55205
+ } else {
55206
+ lines.push(`The site still carries TEMPLATE content. None of it is the business's \u2014 keep it out of the`);
55207
+ lines.push(`app, and tell the owner their site shows it. Sample content is never the deliverable.`);
55208
+ for (const f of placeholders) {
55209
+ lines.push(`- ${f.kind}: "${f.value}" \u2014 ${f.note} (${f.where.join(", ")})`);
55210
+ }
55211
+ }
55212
+ lines.push("");
54887
55213
  lines.push(`## Copy`);
54888
55214
  lines.push(`The customer's OWN words. Reuse these before inventing any \u2014 screen titles, tab`);
54889
55215
  lines.push(`labels, section headings, CTA text, empty states. Write in \`${yn(copy.language)}\`.`);
@@ -58713,31 +59039,47 @@ function ctasOf(texts) {
58713
59039
  return out.slice(0, 8);
58714
59040
  }
58715
59041
  function copyFromEval(evaled) {
59042
+ const templateText = dedupe2(
59043
+ [
59044
+ ...(evaled.sections ?? []).flatMap((s) => [s?.heading, s?.body]),
59045
+ ...(evaled.faq ?? []).flatMap((f) => [f?.q, f?.a]),
59046
+ ...evaled.ctaLabels ?? [],
59047
+ ...evaled.testimonials ?? [],
59048
+ evaled.about,
59049
+ evaled.meta?.description,
59050
+ evaled.meta?.ogTitle,
59051
+ evaled.meta?.ogDescription
59052
+ ].map((raw) => {
59053
+ const text5 = cleanText(raw, 400);
59054
+ return text5 && isTemplatePlaceholder(text5) ? stripPii(cleanText(text5, 90)) : "";
59055
+ }),
59056
+ 8
59057
+ );
58716
59058
  const sections = [];
58717
59059
  for (const raw of evaled.sections ?? []) {
58718
- const heading = copyOrDrop(raw?.heading, CAP3.heading);
59060
+ const heading = copyOrDrop(raw?.heading, CAP4.heading);
58719
59061
  if (!heading) continue;
58720
- const body = copyOrDrop(raw?.body, CAP3.body);
59062
+ const body = copyOrDrop(raw?.body, CAP4.body);
58721
59063
  sections.push(body ? { heading, body } : { heading });
58722
- if (sections.length >= CAP3.sections) break;
59064
+ if (sections.length >= CAP4.sections) break;
58723
59065
  }
58724
59066
  const faq = [];
58725
59067
  for (const raw of evaled.faq ?? []) {
58726
- const q = copyOrDrop(raw?.q, CAP3.q);
58727
- const a = copyOrDrop(raw?.a, CAP3.a);
59068
+ const q = copyOrDrop(raw?.q, CAP4.q);
59069
+ const a = copyOrDrop(raw?.a, CAP4.a);
58728
59070
  if (q && a && q !== a) faq.push({ q, a });
58729
- if (faq.length >= CAP3.faq) break;
59071
+ if (faq.length >= CAP4.faq) break;
58730
59072
  }
58731
- const ctaLabels = dedupe2((evaled.ctaLabels ?? []).map((c) => copyOrDrop(c, CAP3.cta)), CAP3.ctas);
59073
+ const ctaLabels = dedupe2((evaled.ctaLabels ?? []).map((c) => copyOrDrop(c, CAP4.cta)), CAP4.ctas);
58732
59074
  const testimonials = dedupe2(
58733
- (evaled.testimonials ?? []).map((t) => copyOrDrop(t, CAP3.testimonial)),
58734
- CAP3.testimonials
59075
+ (evaled.testimonials ?? []).map((t) => copyOrDrop(t, CAP4.testimonial)),
59076
+ CAP4.testimonials
58735
59077
  );
58736
- const about = copyOrDrop(evaled.about, CAP3.about);
59078
+ const about = copyOrDrop(evaled.about, CAP4.about);
58737
59079
  const meta = {};
58738
- const description = copyOrDrop(evaled.meta?.description, CAP3.meta);
58739
- const ogTitle = copyOrDrop(evaled.meta?.ogTitle, CAP3.meta);
58740
- const ogDescription = copyOrDrop(evaled.meta?.ogDescription, CAP3.meta);
59080
+ const description = copyOrDrop(evaled.meta?.description, CAP4.meta);
59081
+ const ogTitle = copyOrDrop(evaled.meta?.ogTitle, CAP4.meta);
59082
+ const ogDescription = copyOrDrop(evaled.meta?.ogDescription, CAP4.meta);
58741
59083
  if (description) meta.description = description;
58742
59084
  if (ogTitle) meta.ogTitle = ogTitle;
58743
59085
  if (ogDescription) meta.ogDescription = ogDescription;
@@ -58747,7 +59089,8 @@ function copyFromEval(evaled) {
58747
59089
  ...faq.length ? { faq } : {},
58748
59090
  ...testimonials.length ? { testimonials } : {},
58749
59091
  ...about ? { about } : {},
58750
- ...Object.keys(meta).length ? { meta } : {}
59092
+ ...Object.keys(meta).length ? { meta } : {},
59093
+ ...templateText.length ? { templateText } : {}
58751
59094
  };
58752
59095
  }
58753
59096
  function forwardedRequestHeaders(all) {
@@ -59011,7 +59354,7 @@ async function createPlaywrightBrowser(load = () => import("playwright"), siteFe
59011
59354
  };
59012
59355
  return { port, screenshots, siteFetch, pin };
59013
59356
  }
59014
- var PLAYWRIGHT_HINT, PAGE_EVAL, CAP3, DOCUMENT_FETCH_TIMEOUT_MS, MAX_DOCUMENT_BYTES, SUBRESOURCE_PIN_TIMEOUT_MS, NON_NETWORK_SCHEMES, FORWARDED_REQUEST_HEADERS;
59357
+ var PLAYWRIGHT_HINT, PAGE_EVAL, CAP4, DOCUMENT_FETCH_TIMEOUT_MS, MAX_DOCUMENT_BYTES, SUBRESOURCE_PIN_TIMEOUT_MS, NON_NETWORK_SCHEMES, FORWARDED_REQUEST_HEADERS;
59015
59358
  var init_crawl_look = __esm({
59016
59359
  "src/site-analyze/crawl-look.ts"() {
59017
59360
  init_dist();
@@ -59206,7 +59549,7 @@ var init_crawl_look = __esm({
59206
59549
  } catch (e4) {}
59207
59550
  return { wst, nav, headings, hero, imgs, hrefs, buttons, html, htmlLang, velo, sections, ctaLabels, faq, testimonials, about, meta };
59208
59551
  })()`;
59209
- CAP3 = {
59552
+ CAP4 = {
59210
59553
  heading: 120,
59211
59554
  body: 300,
59212
59555
  cta: 80,
@@ -59604,7 +59947,7 @@ async function veloCall(fetchImpl, method, url, opts = {}) {
59604
59947
  }
59605
59948
  return { status: res.status, json: json5, text: text5 };
59606
59949
  }
59607
- function snippet(x, cap = 180) {
59950
+ function snippet2(x, cap = 180) {
59608
59951
  const s = typeof x === "string" ? x : JSON.stringify(x) ?? "";
59609
59952
  return s.replace(/\s+/g, " ").slice(0, cap);
59610
59953
  }
@@ -59627,7 +59970,7 @@ async function resolveGridAppId(fetchImpl, token, msid, siteUrl) {
59627
59970
  return { gridAppId: app.id, source: "lifecycle-provision", mutable: app.mutable !== false };
59628
59971
  }
59629
59972
  if (publishedId) return { gridAppId: publishedId, source: "published-html", mutable: false };
59630
- return { source: `provision ${provisioned.status}: ${snippet(provisioned.json ?? provisioned.text)}` };
59973
+ return { source: `provision ${provisioned.status}: ${snippet2(provisioned.json ?? provisioned.text)}` };
59631
59974
  }
59632
59975
  function sameVeloPath(listed, wanted) {
59633
59976
  const n = (p) => p.replace(/^\.?\//, "");
@@ -59724,7 +60067,7 @@ async function runSiteFunction(opts) {
59724
60067
  revision: snap.revision,
59725
60068
  codeAppId: snap.codeAppId,
59726
60069
  publish: { revision: httpOk(snap.rev), rc: httpOk(snap.rc), live: httpOk(snap.live), status: snap.live?.status },
59727
- error: ok ? void 0 : snippet(snap.live?.json ?? snap.rev.json)
60070
+ error: ok ? void 0 : snippet2(snap.live?.json ?? snap.rev.json)
59728
60071
  };
59729
60072
  }
59730
60073
  const { id, listed } = await withGrid(fetchImpl, opts.token, opts.msid, opts.siteUrl);
@@ -59769,7 +60112,7 @@ async function runSiteFunction(opts) {
59769
60112
  next
59770
60113
  );
59771
60114
  if (!httpOk(patched)) {
59772
- return { ...base, error: `UpdateFiles ${patched.status}: ${snippet(patched.json ?? patched.text)}` };
60115
+ return { ...base, error: `UpdateFiles ${patched.status}: ${snippet2(patched.json ?? patched.text)}` };
59773
60116
  }
59774
60117
  let report2 = {
59775
60118
  ...base,
@@ -59786,7 +60129,7 @@ async function runSiteFunction(opts) {
59786
60129
  revision: snap.revision,
59787
60130
  codeAppId: snap.codeAppId,
59788
60131
  publish: { revision: httpOk(snap.rev), rc: httpOk(snap.rc), live: httpOk(snap.live), status: snap.live?.status },
59789
- error: pubOk ? report2.error : snippet(snap.live?.json ?? snap.rev.json)
60132
+ error: pubOk ? report2.error : snippet2(snap.live?.json ?? snap.rev.json)
59790
60133
  };
59791
60134
  }
59792
60135
  if (opts.invoke) {
@@ -59823,8 +60166,8 @@ async function invokeAndReport(fetchImpl, opts, base, name, method) {
59823
60166
  return {
59824
60167
  ...base,
59825
60168
  ok,
59826
- invoke: { url: hit.url, status: hit.r.status, body: hit.r.json ?? snippet(hit.r.text) },
59827
- error: ok ? void 0 : `invoke ${hit.r.status}: ${snippet(hit.r.json ?? hit.r.text)}`
60169
+ invoke: { url: hit.url, status: hit.r.status, body: hit.r.json ?? snippet2(hit.r.text) },
60170
+ error: ok ? void 0 : `invoke ${hit.r.status}: ${snippet2(hit.r.json ?? hit.r.text)}`
59828
60171
  };
59829
60172
  }
59830
60173
  var HTTP_METHODS2, WRITE_LAYOUTS, VELO_PROVISION_URL, SITE_PUBLISH_URL, PUBLISH_RC_URL, SITE_FUNCTION_USAGE, NAME_RE, EXPORT_HEAD, WIX_HTTP_IMPORT_LINE;
@@ -59849,9 +60192,12 @@ var init_site_function = __esm({
59849
60192
  // src/site-analyze/cli.ts
59850
60193
  var cli_exports = {};
59851
60194
  __export(cli_exports, {
60195
+ APPLY_NEEDS_CLIENT: () => APPLY_NEEDS_CLIENT,
60196
+ REDIRECT_NEEDS_CLIENT: () => REDIRECT_NEEDS_CLIENT,
59852
60197
  SITE_FUNCTION_USAGE: () => SITE_FUNCTION_USAGE,
59853
60198
  assetFetchFor: () => assetFetchFor,
59854
60199
  noteWithWarnings: () => noteWithWarnings,
60200
+ ownerSiteFor: () => ownerSiteFor,
59855
60201
  runSiteAnalyze: () => runSiteAnalyze,
59856
60202
  runSiteFunctionCli: () => runSiteFunctionCli
59857
60203
  });
@@ -59860,7 +60206,17 @@ import { resolve as resolve46 } from "node:path";
59860
60206
  function assetFetchFor(_opts) {
59861
60207
  return pinnedFetch2;
59862
60208
  }
60209
+ function ownerSiteFor(opts, env = process.env) {
60210
+ const flag = opts.siteFlag?.trim();
60211
+ if (flag) return flag;
60212
+ if (!opts.clientId?.trim() || opts.explicitClient !== true) return void 0;
60213
+ return env.WIX_SITE_ID?.trim() || void 0;
60214
+ }
59863
60215
  async function runSiteAnalyze(opts) {
60216
+ if (!opts.clientId?.trim()) {
60217
+ if (opts.apply) throw Object.assign(new Error(APPLY_NEEDS_CLIENT), { exit: 2 });
60218
+ if (opts.redirectUri?.trim()) throw Object.assign(new Error(REDIRECT_NEEDS_CLIENT), { exit: 2 });
60219
+ }
59864
60220
  const fetchImpl = opts.fetchImpl ?? fetch;
59865
60221
  const assetFetch = assetFetchFor(opts);
59866
60222
  const siteFetch = guardedSiteFetch;
@@ -59868,9 +60224,11 @@ async function runSiteAnalyze(opts) {
59868
60224
  const outDir = resolve46(cwd, opts.outDir ?? ".himi/business-book");
59869
60225
  let ownerAuth;
59870
60226
  let msid;
60227
+ const ownerSite = ownerSiteFor(opts);
59871
60228
  try {
59872
- msid = resolveSiteId(opts.siteFlag);
59873
- const token = await siteToken(msid);
60229
+ if (!ownerSite) throw new Error("no owner site for this run");
60230
+ const token = await siteToken(ownerSite);
60231
+ msid = ownerSite;
59874
60232
  ownerAuth = { token, msid };
59875
60233
  } catch {
59876
60234
  msid = opts.siteFlag?.trim() || void 0;
@@ -59969,7 +60327,7 @@ async function runSiteFunctionCli(opts) {
59969
60327
  fetchImpl: opts.fetchImpl
59970
60328
  });
59971
60329
  }
59972
- var FUNCTION_OPS;
60330
+ var APPLY_NEEDS_CLIENT, FUNCTION_OPS;
59973
60331
  var init_cli = __esm({
59974
60332
  "src/site-analyze/cli.ts"() {
59975
60333
  init_analyze();
@@ -59980,6 +60338,8 @@ var init_cli = __esm({
59980
60338
  init_rest();
59981
60339
  init_wix_owner();
59982
60340
  init_site_function();
60341
+ init_types2();
60342
+ APPLY_NEEDS_CLIENT = "--apply needs --client-id: the applied app must be bound to the site's own Headless client, and a URL-only run has none. `himi site client list --site <msid>` shows the site's clients (`himi site client ensure --site <msid> --yes` mints one); WIX_CLIENT_ID is read as a fallback.";
59983
60343
  FUNCTION_OPS = ["list", "get", "put", "publish", "invoke"];
59984
60344
  }
59985
60345
  });
@@ -64833,11 +65193,14 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
64833
65193
  refuses a WEB client \u2014 so a WEB one carrying your
64834
65194
  redirect looks right and is not a candidate.
64835
65195
  himi site analyze --read-only --site <msid> [--client-id <id>] [--json]
64836
- himi site analyze --client-id <id> --url <https://\u2026> [--site <msid>] [--out <dir>] [--apply]
65196
+ himi site analyze --url <https://\u2026> [--client-id <id>] [--site <msid>] [--out <dir>] [--apply]
64837
65197
  [--shell ${SHELL_ENUM}]
64838
65198
  collect a Wix business book (APIs + Playwright look) and
64839
65199
  optionally bake identity/verticals/palette/homeLayout into
64840
- a brandable shell's content package. --shell takes the same
65200
+ a brandable shell's content package. --url alone is the
65201
+ visitor-only pass (published pages, look, copy); --client-id
65202
+ adds the vertical catalog counts and the app binding, and
65203
+ --apply requires it. --shell takes the same
64841
65204
  names 'himi dist --shell' does; default branded-lite. Owner token: WIX_TEST_TOKEN
64842
65205
  or 'wix token --site'. Look/screenshots need Chromium:
64843
65206
  npx playwright install chromium.
@@ -64948,7 +65311,7 @@ var COMMANDS = [
64948
65311
  { name: "login", summary: "obtain a scoped serve-control token from Wix identity and store it in ~/.himalaya/serve-targets.json (so push/promote need no hand-pasted token). Three modes: --browser (your own Wix account, no VPN/BO), --wix (HEADLESS \u2014 the machine's own Wix CLI credential; the mode a CI pod or agent container can satisfy), --dpx (Wix employee, reads ~/.dpx/session/token). RFC 0026 WS3.", required: "[--browser|--wix|--dpx] [--target <t>] [--issue-url <url>]" },
64949
65312
  { name: "logout", summary: "remove the stored control token for a target", required: "[--target <t>]" },
64950
65313
  { name: "notify", summary: "`setup` optionally configures Branded/Ping from local customer key files; `status --refresh` reads live backend readiness. Setup may be deferred while building. These use serve-control and require owner plus Wix account authentication. `send`, `tokens`, `doctor`: send a REAL push notification to registered devices (RFC 0036 WS5) \u2014 APNs .p8 over HTTP/2 for iOS, FCM v1 for Android. `send` targets one device (--token / --device <registry id>) or fans out to the whole app (--all), reporting a per-device result; a token the provider says is uninstalled (APNs 410 Unregistered / FCM UNREGISTERED) is auto-disabled, while a merely wrong one (BadDeviceToken \u2014 usually a sandbox/production mismatch) is recorded and left enabled. `tokens` lists the registry. `doctor` reports which credentials resolve and which sender each platform picks. Talks to the admin-server via --admin-url/HIMALAYA_ADMIN_URL, authenticated with --admin-session/HIMALAYA_ADMIN_SESSION. NOT `himi push`, which publishes a release.", required: "setup|status [--content <dir>] [--target <t>] [--refresh] [--yes] [--no-push-setup] | send|tokens|doctor [--app <a>] [--token <t> | --device <id> | --all] [--title <s>] [--body <s>] [--link <url>] [--platform ios|android] [--badge <n>] [--channel <id>] [--silent] [--dry-run] [--json]" },
64951
- { name: "site", summary: "PROVISION a Wix project, then analyze or extend it. `create` is the one door that makes a site instead of adopting one: POST /funnel/projects/v1/create (a WIX site, a HEADLESS backend, a BRANDED_APP, or a VIBE site), and for headless it also mints the OAuth app whose id IS the `WIX_CLIENT_ID` \u2014 with --redirect-uri already registered, because an unregistered redirect makes native signInDirect fail at redirect-session. `--verticals` REQUIRES `--template` and reports only what Wix confirmed: measured, installing onto a project created from the BLANK template returns 200 while the vertical's API answers 428 forever, because there is no site for it to attach to (the install asks for CREATE_SITE). A real template ships its verticals installed and populated, and installing further ones onto it then works. It MUTATES your Wix account and needs --yes; without it you get the payload and nothing is sent. `list` is the read-only site inventory (with each site's premium flag, which is what explains an installed-but-428 vertical). Auth is the account-scoped `wix token` (no --site), falling back to WIX_API_KEY + WIX_ACCOUNT_ID. `client` is the same OAuth step for a site that ALREADY exists \u2014 the one himi adopts rather than makes \u2014 so a clientId no longer means a manual trip to the dashboard: `client ensure` reconciles rather than mints, because a site can already carry a client with NO registered redirect and shipping that one fails signInDirect at runtime; it reuses a client that carries --redirect-uri, PATCHes one that matches --name but lacks it (unioning, so another app's redirect survives), and creates only when there is nothing to reuse (--new forces a fresh one). --name IS the idempotency key. `client` auth is SITE-scoped (`wix token --site <msid>`) because minting is owner-of-that-site, and --yes gates every mutation: a site that already has a usable client resolves WITHOUT it, but --write-provisioning needs --yes even then because it rewrites a manifest on disk (the dry run's planned.provisioning names the file). That query endpoint IGNORES paging (measured: query.paging, cursorPaging and a top-level paging all return the full set at offset 0), so when pagingMetadata reports more clients than came back, `ensure` REFUSES EVERY outcome \u2014 create, patch AND reuse. Reuse is refused too because pickClient breaks ties on the OLDEST createdDate, so an omitted older same-named MOBILE client with the same redirect would win a later complete read and the resolved clientId would MOVE between runs \u2014 and it gets written into provisioning.json; convergence is what makes a stray duplicate untidy rather than harmful. --name cannot narrow the query (no filter is sent; the name is applied to the rows after they arrive), so the recovery is the dashboard; --new skips the query entirely. A `patched` result is verified against what Wix STORED \u2014 the redirect list is read off the PATCH response, and re-read via GET when the response omits it, because falling back to the list himi just sent would make the guard pass vacuously on exactly the silent no-op it exists to catch. `client list` is the read-only inventory of a site's clients: act on its `shippable` column rather than `hasRedirect`, because shippable ALSO requires a MOBILE applicationType and `ensure` refuses a WEB client \u2014 a WEB one carrying your redirect looks right and is not a candidate \u2014 and it sets truncated:true rather than presenting a partial inventory as the whole one. `analyze --read-only` returns API-only generation context as JSON (identity, access probes, CMS permissions and selected redacted Velo source); it needs no URL/browser and never uploads or applies anything. `analyze` reads a site into a business-book folder (REST + Playwright, GET-only); `site function` list/get/put/publish/invokes that site's Velo HTTP functions and is not `himi functions` (serve-plane jobs). `--apply` bakes into a brandable shell; `--shell` takes the same names `himi dist --shell` does and defaults to branded-lite, and a shell that ships no prepare:customer is refused by name rather than half-built. It never overwrites any shell's committed apps/<shell>/provisioning.json.", required: "create <name> [--type wix|headless|branded-app|vibe] [--template <guid>] [--verticals stores,bookings,\u2026] [--folder <guid>] [--redirect-uri <uri>] [--no-oauth-app] [--write-provisioning [path]] [--site-url <url>] [--json] --yes | list | client ensure|list --site <msid> [--name <name>] [--redirect-uri <uri>] [--new] [--write-provisioning [path]] [--site-url <url>] [--json] --yes | analyze --read-only [--site <msid>] [--client-id <id>] [--json] | analyze --client-id <id> --url <https> [--site <msid>] [--out <dir>] [--redirect-uri <uri>] [--apply] [--shell " + SHELL_ENUM + "] | function list|get|put|publish|invoke [--site <msid>] [--url <https>] [--method GET|POST|PUT|DELETE] [--source <file>] [--body <json>] [--publish] [--invoke]" },
65314
+ { name: "site", summary: "PROVISION a Wix project, then analyze or extend it. `create` is the one door that makes a site instead of adopting one: POST /funnel/projects/v1/create (a WIX site, a HEADLESS backend, a BRANDED_APP, or a VIBE site), and for headless it also mints the OAuth app whose id IS the `WIX_CLIENT_ID` \u2014 with --redirect-uri already registered, because an unregistered redirect makes native signInDirect fail at redirect-session. `--verticals` REQUIRES `--template` and reports only what Wix confirmed: measured, installing onto a project created from the BLANK template returns 200 while the vertical's API answers 428 forever, because there is no site for it to attach to (the install asks for CREATE_SITE). A real template ships its verticals installed and populated, and installing further ones onto it then works. It MUTATES your Wix account and needs --yes; without it you get the payload and nothing is sent. `list` is the read-only site inventory (with each site's premium flag, which is what explains an installed-but-428 vertical). Auth is the account-scoped `wix token` (no --site), falling back to WIX_API_KEY + WIX_ACCOUNT_ID. `client` is the same OAuth step for a site that ALREADY exists \u2014 the one himi adopts rather than makes \u2014 so a clientId no longer means a manual trip to the dashboard: `client ensure` reconciles rather than mints, because a site can already carry a client with NO registered redirect and shipping that one fails signInDirect at runtime; it reuses a client that carries --redirect-uri, PATCHes one that matches --name but lacks it (unioning, so another app's redirect survives), and creates only when there is nothing to reuse (--new forces a fresh one). --name IS the idempotency key. `client` auth is SITE-scoped (`wix token --site <msid>`) because minting is owner-of-that-site, and --yes gates every mutation: a site that already has a usable client resolves WITHOUT it, but --write-provisioning needs --yes even then because it rewrites a manifest on disk (the dry run's planned.provisioning names the file). That query endpoint IGNORES paging (measured: query.paging, cursorPaging and a top-level paging all return the full set at offset 0), so when pagingMetadata reports more clients than came back, `ensure` REFUSES EVERY outcome \u2014 create, patch AND reuse. Reuse is refused too because pickClient breaks ties on the OLDEST createdDate, so an omitted older same-named MOBILE client with the same redirect would win a later complete read and the resolved clientId would MOVE between runs \u2014 and it gets written into provisioning.json; convergence is what makes a stray duplicate untidy rather than harmful. --name cannot narrow the query (no filter is sent; the name is applied to the rows after they arrive), so the recovery is the dashboard; --new skips the query entirely. A `patched` result is verified against what Wix STORED \u2014 the redirect list is read off the PATCH response, and re-read via GET when the response omits it, because falling back to the list himi just sent would make the guard pass vacuously on exactly the silent no-op it exists to catch. `client list` is the read-only inventory of a site's clients: act on its `shippable` column rather than `hasRedirect`, because shippable ALSO requires a MOBILE applicationType and `ensure` refuses a WEB client \u2014 a WEB one carrying your redirect looks right and is not a candidate \u2014 and it sets truncated:true rather than presenting a partial inventory as the whole one. `analyze --read-only` returns API-only generation context as JSON (identity, access probes, CMS permissions and selected redacted Velo source); it needs no URL/browser and never uploads or applies anything. `analyze` reads a site into a business-book folder (REST + Playwright, GET-only); `--url` alone is the visitor-only pass over the published pages, and `--client-id` adds the vertical probes and the app binding `--apply` requires; `site function` list/get/put/publish/invokes that site's Velo HTTP functions and is not `himi functions` (serve-plane jobs). `--apply` bakes into a brandable shell; `--shell` takes the same names `himi dist --shell` does and defaults to branded-lite, and a shell that ships no prepare:customer is refused by name rather than half-built. It never overwrites any shell's committed apps/<shell>/provisioning.json.", required: "create <name> [--type wix|headless|branded-app|vibe] [--template <guid>] [--verticals stores,bookings,\u2026] [--folder <guid>] [--redirect-uri <uri>] [--no-oauth-app] [--write-provisioning [path]] [--site-url <url>] [--json] --yes | list | client ensure|list --site <msid> [--name <name>] [--redirect-uri <uri>] [--new] [--write-provisioning [path]] [--site-url <url>] [--json] --yes | analyze --read-only [--site <msid>] [--client-id <id>] [--json] | analyze --url <https> [--client-id <id>] [--site <msid>] [--out <dir>] [--redirect-uri <uri>] [--apply] [--shell " + SHELL_ENUM + "] | function list|get|put|publish|invoke [--site <msid>] [--url <https>] [--method GET|POST|PUT|DELETE] [--source <file>] [--body <json>] [--publish] [--invoke]" },
64952
65315
  { name: "doctor", summary: 'off-VPN preflight in one shot: probe the PUBLIC data plane (www.wixapis.com \u2014 reachable off-VPN), the INTERNAL serve/control plane for the selected target (VPN-gated), and report auth state (dpx session on disk + stored control token). --dpx additionally runs a live, EADDRINUSE-safe `dpx whoami`. Machine-readable with --json (an unreachable internal plane carries a stable code:"needs_vpn" so an agent branches on the code, not the prose); human-readable otherwise. Never loads the authoring runtime \u2014 works off-repo.', required: "[--target <t>] [--dpx] [--json] [--data-url <url>] [--timeout <ms>]" },
64953
65316
  { name: "upgrade", summary: "install the newest published CLI (`npm i -g @wix/himalaya-cli@latest`) and report what moved. This is the explicit door: himi also updates itself when the running build is more than a week old \u2014 unattended it just does it and re-runs your command, at a terminal it asks first \u2014 and `HIMI_NO_UPDATE_CHECK=1` turns that off, leaving this command as the way to catch up. Reports `unchanged` rather than claiming success when npm exits 0 without moving the version, which is what a registry mirror serving its own pinned copy looks like.", required: "[--json]" },
64954
65317
  { name: "push", summary: "lint + build + publish a release (default --draft: servable via ?release= but NOT live). REFUSES to publish on any error unless --force; --strict promotes warnings. Prints preview + agent-context links. The published slug is NAMESPACED per owner \u2014 `<ownerPrefix>.<name>`, composed offline from the control token's ownerPrefix claim, so the CLI and the server derive the identical string and himalaya.content.json is NOT rewritten (a legacy token with no ownerPrefix publishes the declared name unchanged). An app claimed BEFORE namespacing is stored under its FLAT slug, so the claim route answers `alreadyYours` naming that slug and the push adopts it \u2014 reported as `adoptedSlug`, since it is the one case where the published `app` differs from what the declared name composes to. `--no-claim` skips the route that knows this, so a pre-namespacing app pushed with it still fails 403. --visibility public|unlisted decides whether /v1/apps + the Himi Player picker LIST the app (also declarable as `visibility` in himalaya.content.json; omitted, a brand-new app defaults to unlisted \u2014 share by link \u2014 and an existing setting is kept). --account-id names which account owns the app when your Wix session resolves to several (the auto-claim of a brand-new slug cannot guess \u2014 without it a multi-account caller is told to name one). An unforced push of a release that carries SCREENS also refuses when `.himi/ux-audit.json` holds no mobile-UX drive recorded against that release id, or holds one that FAILED (#2955) \u2014 a published draft IS the owner-private preview a customer is handed; `himi ux record` clears it and `--force` overrides it, which the success envelope then says out loud. `--only <selector>` publishes ONLY the named items as a release derived from the app's live release (or `--from-release <id>`), running the identical build + lint gate and simply publishing less; it requires an explicit `--draft` or `--live` because a partial publish has no safe default, never claims a slug, and carries neither attestation nor a source archive (both named in the output). Selectors are comma-separated in ONE `--only`: `changed` (every item whose bytes differ from the base), `screen:<id|glob>`, `bundle:<name>`, `tokens`, `app-config` \u2014 a selected screen drags its logic bundle and a selected bundle drags the screens that reference it, or the bytes land under a key nothing reads.", required: "[--content <dir>] [--draft] [--live] [--force] [--strict] [--label] [--notes] [--visibility public|unlisted] [--no-claim] [--account-id <id>] [--only <selector>] [--from-release <id>] [--accept-regressions] [--accept-partial] [--app <slug>] (--control-token)" },
@@ -71722,10 +72085,11 @@ ${report2.ok ? "OK \u2014 data + serve plane reachable." : typeof report2.hint =
71722
72085
  }
71723
72086
  }
71724
72087
  if (sub !== "analyze") {
71725
- io.error(JSON.stringify({ error: sub ? `unknown site subcommand "${sub}"` : "site needs a subcommand", usage: `himi site create <name> [--type wix|headless|branded-app|vibe] --yes | himi site list | himi site client ensure|list --site <msid> | himi site analyze --client-id <id> --url <https://\u2026> [--site <msid>] [--out <dir>] [--apply [--shell <id>]] | himi site function list|get|put|publish|invoke \u2026` }));
72088
+ io.error(JSON.stringify({ error: sub ? `unknown site subcommand "${sub}"` : "site needs a subcommand", usage: `himi site create <name> [--type wix|headless|branded-app|vibe] --yes | himi site list | himi site client ensure|list --site <msid> | himi site analyze --url <https://\u2026> [--client-id <id>] [--site <msid>] [--out <dir>] [--apply [--shell <id>]] | himi site function list|get|put|publish|invoke \u2026` }));
71726
72089
  return 2;
71727
72090
  }
71728
- const clientId = str6(flags["client-id"]) ?? process.env.WIX_CLIENT_ID;
72091
+ const clientFlag = str6(flags["client-id"]);
72092
+ const clientId = clientFlag ?? process.env.WIX_CLIENT_ID;
71729
72093
  const siteUrl = str6(flags.url) ?? str6(flags["site-url"]);
71730
72094
  if (flags["read-only"] === true) {
71731
72095
  if (flags.apply || flags.out || flags.shell || flags["redirect-uri"]) {
@@ -71755,24 +72119,27 @@ ${report2.ok ? "OK \u2014 data + serve plane reachable." : typeof report2.hint =
71755
72119
  print({ ok: true, context });
71756
72120
  return 0;
71757
72121
  }
71758
- if (!clientId || !siteUrl) {
72122
+ const analyzeUsage = "himi site analyze --url https://\u2026 [--client-id <headless-oauth-client-id>] [--site <msid>] [--out .himi/business-book] [--redirect-uri <uri>] [--apply [--shell " + SHELL_ENUM + "]]";
72123
+ if (!siteUrl) {
72124
+ io.error(JSON.stringify({ error: "himi site analyze needs --url", usage: analyzeUsage }));
72125
+ return 2;
72126
+ }
72127
+ const redirectFlag = str6(flags["redirect-uri"]);
72128
+ if (!clientId && (flags.apply === true || redirectFlag)) {
72129
+ const { APPLY_NEEDS_CLIENT: APPLY_NEEDS_CLIENT2, REDIRECT_NEEDS_CLIENT: REDIRECT_NEEDS_CLIENT2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
71759
72130
  io.error(JSON.stringify({
71760
- error: "himi site analyze needs --client-id and --url",
71761
- // Analyze is GET-only and takes the client as INPUT, so a missing one used to
71762
- // be a dead end that named only the dashboard. Name the command that resolves
71763
- // it instead — that is what lets an unattended loop start from a site it did
71764
- // not create.
71765
- ...clientId ? {} : {
71766
- hint: "no --client-id? `himi site client ensure --site <msid> --yes` resolves (or mints) the site's Headless client and prints it; `himi site client list --site <msid>` shows what the site already has. WIX_CLIENT_ID is read as a fallback."
71767
- },
71768
- usage: "himi site analyze --client-id <headless-oauth-client-id> --url https://\u2026 [--site <msid>] [--out .himi/business-book] [--redirect-uri <uri>] [--apply [--shell " + SHELL_ENUM + "]]"
72131
+ error: flags.apply === true ? APPLY_NEEDS_CLIENT2 : REDIRECT_NEEDS_CLIENT2,
72132
+ usage: analyzeUsage
71769
72133
  }));
71770
72134
  return 2;
71771
72135
  }
71772
72136
  try {
71773
72137
  const { runSiteAnalyze: runSiteAnalyze2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
71774
72138
  const r = await runSiteAnalyze2({
71775
- clientId,
72139
+ ...clientId ? { clientId } : {},
72140
+ // The AMBIENT `WIX_SITE_ID` selects owner mode only beside an explicit --client-id,
72141
+ // so exported env vars cannot turn a bare `--url` run into an owner read (#3283).
72142
+ explicitClient: !!clientFlag,
71776
72143
  siteUrl,
71777
72144
  siteFlag: str6(flags.site),
71778
72145
  outDir: str6(flags.out),
@@ -71782,14 +72149,14 @@ ${report2.ok ? "OK \u2014 data + serve plane reachable." : typeof report2.hint =
71782
72149
  // Analyze cannot discover it (GET-only; the registered list lives behind
71783
72150
  // `oauth-app/v1`), and the shell refuses to reuse its own default once a customer
71784
72151
  // client is in play — so it is passed in by whoever registered it.
71785
- redirectUri: str6(flags["redirect-uri"]),
72152
+ redirectUri: redirectFlag,
71786
72153
  cwd: str6(flags.content) ?? process.cwd()
71787
72154
  });
71788
72155
  print({ ok: true, ...r });
71789
72156
  return 0;
71790
72157
  } catch (e) {
71791
72158
  io.error(JSON.stringify({ error: e.message }));
71792
- return 1;
72159
+ return e.exit === 2 ? 2 : 1;
71793
72160
  }
71794
72161
  }
71795
72162
  default: