@wix/mcp 1.0.94 → 1.0.96

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 (86) hide show
  1. package/README.md +2 -2
  2. package/build/bin-standalone.js +1457 -332
  3. package/build/cjs/index.cjs +3379 -1333
  4. package/build/cjs/toolkit.cjs +2158 -373
  5. package/build/dts/api-call/headless-release.d.ts +56 -0
  6. package/build/dts/api-call/headless-release.js +135 -0
  7. package/build/dts/api-call/index.d.ts +9 -1
  8. package/build/dts/api-call/index.js +36 -8
  9. package/build/dts/api-call/site-files.d.ts +14 -0
  10. package/build/dts/api-call/site-files.js +106 -10
  11. package/build/dts/api-call/upload-headless-website-files.d.ts +117 -5
  12. package/build/dts/api-call/upload-headless-website-files.js +565 -20
  13. package/build/dts/api-call/upload-image-tool.d.ts +2 -0
  14. package/build/dts/api-call/upload-image-tool.js +8 -4
  15. package/build/dts/bin.d.ts +1 -1
  16. package/build/dts/bin.js +3 -1
  17. package/build/dts/code-mode/attachments.d.ts +9 -6
  18. package/build/dts/code-mode/attachments.js +23 -16
  19. package/build/dts/code-mode/index.d.ts +6 -1
  20. package/build/dts/code-mode/index.js +32 -12
  21. package/build/dts/code-mode/truncate-response.js +5 -1
  22. package/build/dts/config/default-config.d.ts +35 -5
  23. package/build/dts/config/default-config.js +339 -182
  24. package/build/dts/config/file-params.d.ts +22 -0
  25. package/build/dts/config/file-params.js +24 -0
  26. package/build/dts/config/param-descriptions.d.ts +14 -2
  27. package/build/dts/config/param-descriptions.js +33 -14
  28. package/build/dts/constants.d.ts +1 -0
  29. package/build/dts/constants.js +1 -0
  30. package/build/dts/docs/docs.d.ts +12 -1
  31. package/build/dts/docs/docs.js +274 -43
  32. package/build/dts/docs/fetch-article.d.ts +21 -0
  33. package/build/dts/docs/fetch-article.js +66 -4
  34. package/build/dts/docs/get-to-know.d.ts +7 -2
  35. package/build/dts/docs/get-to-know.js +36 -14
  36. package/build/dts/docs/guessed-url.d.ts +41 -0
  37. package/build/dts/docs/guessed-url.js +185 -0
  38. package/build/dts/docs/mintlify-docs.d.ts +121 -0
  39. package/build/dts/docs/mintlify-docs.js +391 -0
  40. package/build/dts/docs/semanticSearch.d.ts +2 -1
  41. package/build/dts/docs/semanticSearch.js +14 -7
  42. package/build/dts/docs/start-deploy-website.d.ts +7 -6
  43. package/build/dts/docs/start-deploy-website.js +13 -8
  44. package/build/dts/docs/start-headless-build.d.ts +8 -10
  45. package/build/dts/docs/start-headless-build.js +14 -28
  46. package/build/dts/index.d.ts +2 -2
  47. package/build/dts/index.js +2 -2
  48. package/build/dts/infra/redact-personal-data.d.ts +4 -0
  49. package/build/dts/infra/redact-personal-data.js +148 -0
  50. package/build/dts/site-widget-tools/constants.d.ts +50 -2
  51. package/build/dts/site-widget-tools/constants.js +65 -8
  52. package/build/dts/site-widget-tools/index.d.ts +1 -0
  53. package/build/dts/site-widget-tools/index.js +1 -0
  54. package/build/dts/site-widget-tools/pick-images-tool/index.d.ts +14 -0
  55. package/build/dts/site-widget-tools/pick-images-tool/index.js +138 -0
  56. package/build/dts/site-widget-tools/pull-site-creation-job-tool/index.js +1 -1
  57. package/build/dts/site-widget-tools/search-site-templates-tool/index.js +25 -2
  58. package/build/dts/site-widget-tools/site-builder-tool/index.js +35 -3
  59. package/build/dts/site-widget-tools/site-creation-tool/index.d.ts +7 -1
  60. package/build/dts/site-widget-tools/site-creation-tool/index.js +22 -51
  61. package/build/dts/site-widget-tools/suggested-domain-tool/index.js +26 -12
  62. package/build/dts/support/index.d.ts +4 -0
  63. package/build/dts/support/index.js +13 -7
  64. package/build/dts/tool-utils.d.ts +8 -1
  65. package/build/dts/tool-utils.js +26 -2
  66. package/build/dts/toolkit-entry.d.cts +15 -10
  67. package/build/dts/toolkit-entry.d.ts +15 -10
  68. package/build/dts/toolkit.d.ts +2 -2
  69. package/build/dts/toolkit.js +21 -8
  70. package/build/dts/wix-mcp-server.d.ts +10 -1
  71. package/build/dts/wix-mcp-server.js +25 -7
  72. package/build/dts/wixel/constants.d.ts +8 -1
  73. package/build/dts/wixel/constants.js +8 -1
  74. package/build/dts/wixel/index.d.ts +1 -1
  75. package/build/dts/wixel/index.js +2 -2
  76. package/build/dts/wixel/wixel-a2a-client.d.ts +83 -0
  77. package/build/dts/wixel/wixel-a2a-client.js +166 -0
  78. package/build/dts/wixel/wixel-bi.d.ts +2 -1
  79. package/build/dts/wixel/wixel-bi.js +2 -1
  80. package/build/dts/wixel/{external-agent-api.d.ts → wixel-session-entries.d.ts} +11 -33
  81. package/build/dts/wixel/{external-agent-api.js → wixel-session-entries.js} +33 -54
  82. package/build/dts/wixel/wixel-session-tools.d.ts +21 -26
  83. package/build/dts/wixel/wixel-session-tools.js +494 -599
  84. package/build/esm/index.js +3422 -1388
  85. package/build/esm/toolkit.js +2158 -373
  86. package/package.json +4 -4
@@ -10682,6 +10682,8 @@ var runSemanticSearch = async (toolName, toolParams, maxResults = 20, rerank = f
10682
10682
  kbNames.push("VELO_DOCS_KB_ID", "VELO_METHODS_KB_ID");
10683
10683
  } else if (toolName === "CLI") {
10684
10684
  kbNames.push("CLI_KB_ID");
10685
+ } else if (toolName === "HELP_CENTER") {
10686
+ kbNames.push("HELP_CENTER_KB_ID");
10685
10687
  }
10686
10688
  logger.log(
10687
10689
  `[SemanticSearch] Tool: ${toolName}, KBs: [${kbNames.join(", ")}], Query: "${toolParams.searchTerm}"`
@@ -10714,9 +10716,9 @@ var runSemanticSearch = async (toolName, toolParams, maxResults = 20, rerank = f
10714
10716
  );
10715
10717
  return data?.results;
10716
10718
  };
10717
- var runPlatformizedSemanticSearch = async (toolName, searchTerm, maxResults = 20, linesInEachResult, extraDocumentTypes) => {
10719
+ var runPlatformizedSemanticSearch = async (toolName, searchTerm, maxResults = 20, linesInEachResult, extraDocumentTypes, documentTypesOverride) => {
10718
10720
  const startTime = Date.now();
10719
- const documentTypes = extraDocumentTypes?.length ? [toolName, ...extraDocumentTypes] : void 0;
10721
+ const documentTypes = documentTypesOverride?.length ? documentTypesOverride : extraDocumentTypes?.length ? [toolName, ...extraDocumentTypes] : void 0;
10720
10722
  logger.log(
10721
10723
  `[SemanticSearch/v2] Tool: ${documentTypes ? documentTypes.join("+") : toolName}, Query: "${searchTerm}"`
10722
10724
  );
@@ -10760,15 +10762,17 @@ var runSemanticSearchAndFormat = async ({
10760
10762
  rerank,
10761
10763
  linesInEachResult,
10762
10764
  usePlatformizedSearch = true,
10763
- extraDocumentTypes
10765
+ extraDocumentTypes,
10766
+ documentTypes
10764
10767
  }) => {
10765
- if (usePlatformizedSearch) {
10768
+ if (usePlatformizedSearch || documentTypes?.length) {
10766
10769
  return runPlatformizedSemanticSearch(
10767
10770
  toolName,
10768
10771
  toolParams.searchTerm,
10769
10772
  maxResults,
10770
10773
  linesInEachResult,
10771
- extraDocumentTypes
10774
+ extraDocumentTypes,
10775
+ documentTypes
10772
10776
  );
10773
10777
  } else {
10774
10778
  const results = await runSemanticSearch(
@@ -11685,6 +11689,7 @@ function captureException(exception, hint) {
11685
11689
  // src/constants.ts
11686
11690
  var RAW_DOCS_API_URL = "https://dev.wix.com/rawdocs/api";
11687
11691
  var GET_ARTICLE_CONTENT_URL = `${RAW_DOCS_API_URL}/get-article-content`;
11692
+ var GET_SOURCE_CONTENT_URL = `${RAW_DOCS_API_URL}/get-source-content`;
11688
11693
  var PORTAL_INDEX_URL = `${RAW_DOCS_API_URL}/portal-index`;
11689
11694
  var DOCS_API_BASE_URL = "https://dev.wix.com/docs/api/v1";
11690
11695
  var GET_SKILLS_BY_PORTAL_URL = `${DOCS_API_BASE_URL}/docs-web-app/menu-node/get-skills-by-portal`;
@@ -11705,7 +11710,13 @@ var SKILLS_REGISTRY_URL = new RegExp(
11705
11710
  "^https://(?:dev\\.wix\\.com(?:/docs)?|www\\.wix\\.com(?:/velo/reference)?)/skills(?:[/.?#]|$)|^https://www\\.wix\\.com/headless/"
11706
11711
  );
11707
11712
  function isSkillsRegistryUrl(url) {
11708
- return SKILLS_REGISTRY_URL.test(url.trim());
11713
+ return SKILLS_REGISTRY_URL.test(normalizeSkillsUrl(url));
11714
+ }
11715
+ var GITHUB_SKILLS_URL = /^https:\/\/(?:www\.)?github\.com\/wix\/skills\/(?:blob|raw|tree)\/[^/]+\/skills\/([^#?]+)/;
11716
+ function normalizeSkillsUrl(url) {
11717
+ const apex = url.trim().replace(/^https:\/\/wix\.com\//, "https://www.wix.com/");
11718
+ const gh = apex.match(GITHUB_SKILLS_URL);
11719
+ return gh ? `https://www.wix.com/skills/${gh[1]}` : apex;
11709
11720
  }
11710
11721
  async function fetchSkillsRegistryPage(httpClient, url) {
11711
11722
  let data;
@@ -11803,6 +11814,40 @@ async function fetchArticleContentFromDigor(httpClient, articleUrl, mode, stripH
11803
11814
  }
11804
11815
  return articleContent;
11805
11816
  }
11817
+ function parseSourceContentUrl(url) {
11818
+ let parsed;
11819
+ try {
11820
+ parsed = new URL(url);
11821
+ } catch {
11822
+ return void 0;
11823
+ }
11824
+ if (`${parsed.origin}${parsed.pathname}` !== GET_SOURCE_CONTENT_URL) {
11825
+ return void 0;
11826
+ }
11827
+ const repo = parsed.searchParams.get("repo");
11828
+ const path = parsed.searchParams.get("path");
11829
+ const commitHash = parsed.searchParams.get("commitHash");
11830
+ if (!repo || !path) return void 0;
11831
+ return commitHash ? { repo, path, commitHash } : { repo, path };
11832
+ }
11833
+ async function fetchSourceContent(httpClient, repo, path, commitHash) {
11834
+ const url = new URL(GET_SOURCE_CONTENT_URL);
11835
+ url.searchParams.set("repo", repo);
11836
+ url.searchParams.set("path", path);
11837
+ if (commitHash) url.searchParams.set("commitHash", commitHash);
11838
+ let data;
11839
+ try {
11840
+ ({ data } = await httpClient.get(url.toString()));
11841
+ } catch (error) {
11842
+ const { message, status } = getHttpErrorDetails(error);
11843
+ const statusSuffix = status ? ` (status ${status})` : "";
11844
+ throw new Error(`${message}${statusSuffix}`);
11845
+ }
11846
+ if (!data?.content) {
11847
+ throw new Error("Source content is empty");
11848
+ }
11849
+ return data.content;
11850
+ }
11806
11851
  async function fetchArticleContent(httpClient, articleUrl, mode, stripHeader = false, skillsPortalUrl, repo, commitHash, fetchMcpWhiteList) {
11807
11852
  const effectiveUrl = applyPortalUrlOverride(articleUrl, skillsPortalUrl);
11808
11853
  const isOverridden = effectiveUrl !== articleUrl;
@@ -11816,7 +11861,10 @@ async function fetchArticleContent(httpClient, articleUrl, mode, stripHeader = f
11816
11861
  `[fetch] Override failed for ${effectiveUrl}, falling back to ${articleUrl}:`,
11817
11862
  error instanceof Error ? error.message : error
11818
11863
  );
11819
- content = isSkillsRegistryUrl(articleUrl) ? await fetchSkillsRegistryPage(httpClient, articleUrl) : await fetchArticleContentFromDigor(
11864
+ content = isSkillsRegistryUrl(articleUrl) ? await fetchSkillsRegistryPage(
11865
+ httpClient,
11866
+ normalizeSkillsUrl(articleUrl)
11867
+ ) : await fetchArticleContentFromDigor(
11820
11868
  httpClient,
11821
11869
  articleUrl,
11822
11870
  mode,
@@ -11824,7 +11872,10 @@ async function fetchArticleContent(httpClient, articleUrl, mode, stripHeader = f
11824
11872
  );
11825
11873
  }
11826
11874
  } else if (isSkillsRegistryUrl(articleUrl)) {
11827
- content = await fetchSkillsRegistryPage(httpClient, articleUrl);
11875
+ content = await fetchSkillsRegistryPage(
11876
+ httpClient,
11877
+ normalizeSkillsUrl(articleUrl)
11878
+ );
11828
11879
  } else if (mode === "raw") {
11829
11880
  let parsedUrl;
11830
11881
  try {
@@ -11967,6 +12018,140 @@ async function fetchPlatformizedMenu(menuUrl) {
11967
12018
  return data.content;
11968
12019
  }
11969
12020
 
12021
+ // src/docs/guessed-url.ts
12022
+ var SKILLS_BASE = "https://www.wix.com/skills/";
12023
+ var registryUrl = (url) => normalizeSkillsUrl(url).replace(
12024
+ /^https:\/\/(?:dev\.wix\.com(?:\/docs)?|www\.wix\.com\/velo\/reference)\/skills\//,
12025
+ SKILLS_BASE
12026
+ );
12027
+ var DOCS_BASE = "https://dev.wix.com/docs/";
12028
+ var FILLER = /* @__PURE__ */ new Set([
12029
+ "",
12030
+ "md",
12031
+ "a",
12032
+ "an",
12033
+ "the",
12034
+ "and",
12035
+ "or",
12036
+ "of",
12037
+ "to",
12038
+ "wix",
12039
+ "s"
12040
+ ]);
12041
+ var slugWords = (slug) => new Set(
12042
+ slug.toLowerCase().replace(/\.md$/, "").split(/[^a-z0-9]+/).filter((w) => !FILLER.has(w)).map((w) => w.length > 3 && w.endsWith("s") ? w.slice(0, -1) : w)
12043
+ );
12044
+ function slugSimilarity(a, b) {
12045
+ const x = slugWords(a);
12046
+ const y = slugWords(b);
12047
+ if (x.size === 0 || y.size === 0) return 0;
12048
+ let shared = 0;
12049
+ for (const w of x) if (y.has(w)) shared++;
12050
+ return shared / (x.size + y.size - shared);
12051
+ }
12052
+ var lastSegment = (path) => path.slice(path.lastIndexOf("/") + 1);
12053
+ var folderOf = (path) => path.includes("/") ? path.slice(0, path.lastIndexOf("/")) : "";
12054
+ var SKILL_FILE_MATCH = 0.5;
12055
+ function closestSkillFile(url, manifest, manifestUrl) {
12056
+ const normalized = registryUrl(url).replace(/[?#].*$/, "");
12057
+ if (!normalized.startsWith(SKILLS_BASE)) return null;
12058
+ const [, ...rest] = normalized.slice(SKILLS_BASE.length).split("/");
12059
+ const asked = rest.join("/");
12060
+ const paths = (manifest.files ?? []).map((f) => f.path);
12061
+ if (!asked || paths.length === 0) return null;
12062
+ const base = manifest.base ?? manifestUrl.replace(/\.manifest\.json$/, "/");
12063
+ const name = lastSegment(asked);
12064
+ const folder = folderOf(asked);
12065
+ const sameName = paths.filter((p) => lastSegment(p) === name);
12066
+ if (sameName.length === 1) {
12067
+ return { kind: "file", path: sameName[0], url: base + sameName[0] };
12068
+ }
12069
+ const ranked = paths.filter((p) => folderOf(p) === folder).map((p) => ({ p, score: slugSimilarity(lastSegment(p), name) })).sort((a, b) => b.score - a.score);
12070
+ const [best, next] = ranked;
12071
+ if (best && best.score >= SKILL_FILE_MATCH && best.score > (next?.score ?? 0)) {
12072
+ return { kind: "file", path: best.p, url: base + best.p };
12073
+ }
12074
+ if (ranked.length > 0) {
12075
+ return {
12076
+ kind: "folder",
12077
+ folder,
12078
+ files: ranked.map((r) => base + r.p),
12079
+ manifestUrl
12080
+ };
12081
+ }
12082
+ const top = [...new Set(paths.map((p) => p.split("/")[0]))].sort();
12083
+ return {
12084
+ kind: "folder",
12085
+ folder: "",
12086
+ files: top.map((t) => base + t),
12087
+ manifestUrl
12088
+ };
12089
+ }
12090
+ function skillManifestUrl(url) {
12091
+ const normalized = registryUrl(url);
12092
+ if (!normalized.startsWith(SKILLS_BASE)) return null;
12093
+ const skill = normalized.slice(SKILLS_BASE.length).split(/[/?#]/)[0];
12094
+ return skill ? `${SKILLS_BASE}${skill}.manifest.json` : null;
12095
+ }
12096
+ var MENU_WALK_UP_LEVELS = 3;
12097
+ var MIN_MENU_SEGMENTS = 3;
12098
+ function parentMenuUrls(url) {
12099
+ const clean = url.replace(/[?#].*$/, "").replace(/\/+$/, "");
12100
+ if (!clean.startsWith(DOCS_BASE)) return [];
12101
+ const segments = clean.slice(DOCS_BASE.length).split("/");
12102
+ const out = [];
12103
+ for (let up = 1; up <= MENU_WALK_UP_LEVELS; up++) {
12104
+ const kept = segments.length - up;
12105
+ if (kept < MIN_MENU_SEGMENTS) break;
12106
+ out.push(DOCS_BASE + segments.slice(0, kept).join("/"));
12107
+ }
12108
+ return out;
12109
+ }
12110
+ function closestMenuLinks(menuText, menuUrl, askedUrl, limit = 3) {
12111
+ const asked = lastSegment(
12112
+ askedUrl.replace(/[?#].*$/, "").replace(/\/+$/, "")
12113
+ );
12114
+ const under = `${menuUrl.replace(/\/+$/, "")}/`;
12115
+ const links = [
12116
+ ...new Set(
12117
+ menuText.match(/https:\/\/dev\.wix\.com\/docs\/[^\s)\]>"'`]+/g) ?? []
12118
+ )
12119
+ ].filter((l) => l.startsWith(under));
12120
+ return links.map((l) => ({
12121
+ l,
12122
+ score: slugSimilarity(lastSegment(l.replace(/\.md$/, "")), asked)
12123
+ })).filter((r) => r.score > 0).sort((a, b) => b.score - a.score).slice(0, limit).map((r) => r.l);
12124
+ }
12125
+ var SMALL_MENU = 3;
12126
+ var MENU_ENTRY_MATCH = 0.75;
12127
+ function subMenusToRead(menuText, menuUrl, askedUrl, limit = 2) {
12128
+ const asked = lastSegment(
12129
+ askedUrl.replace(/[?#].*$/, "").replace(/\/+$/, "")
12130
+ );
12131
+ const under = `${menuUrl.replace(/\/+$/, "")}/`;
12132
+ const methods = new Set(
12133
+ [
12134
+ ...menuText.matchAll(
12135
+ /\]\((https:\/\/dev\.wix\.com\/docs\/[^)\s]+)\)\s*_\((?:GET|POST|PUT|PATCH|DELETE)\)_/g
12136
+ )
12137
+ ].map((m) => m[1])
12138
+ );
12139
+ const entries = [
12140
+ ...new Set(
12141
+ menuText.match(/https:\/\/dev\.wix\.com\/docs\/[^\s)\]>"'`]+/g) ?? []
12142
+ )
12143
+ ].filter(
12144
+ (l) => l.startsWith(under) && !l.slice(under.length).replace(/\.md$/, "").includes("/") && !methods.has(l)
12145
+ );
12146
+ const scored = entries.map((l) => ({
12147
+ l,
12148
+ score: slugSimilarity(lastSegment(l.replace(/\.md$/, "")), asked)
12149
+ })).sort((a, b) => b.score - a.score);
12150
+ if (scored.some((s) => s.score >= MENU_ENTRY_MATCH)) return [];
12151
+ if (entries.length <= SMALL_MENU) return entries;
12152
+ return scored.filter((s) => s.score > 0).slice(0, limit).map((s) => s.l);
12153
+ }
12154
+
11970
12155
  // ../../node_modules/dedent/dist/dedent.mjs
11971
12156
  function ownKeys(object, enumerableOnly) {
11972
12157
  var keys = Object.keys(object);
@@ -12111,54 +12296,97 @@ function rethrowIfUnauthorized(error) {
12111
12296
  var ARRAY_QUERY_PARAMS_RULE = "Repeat array query params once per value: `?fieldsets=URL&fieldsets=RICH_CONTENT`. Do NOT comma-join values (`fieldsets=URL,RICH_CONTENT`), do NOT leave a trailing comma, and do NOT use a value the method schema does not list, even if a sibling method accepts it (draft posts have no `SEO` fieldset).";
12112
12297
  var WQL_FILTER_RULE = "Query and search filters are written in WQL: each field takes a single operator, so combine conditions with the logical operators \u2014 `$and` and `$or` take an array of expressions, `$not` takes one, and they can nest.";
12113
12298
 
12299
+ // src/config/file-params.ts
12300
+ var FILE_PARAM_CLIENTS = "OpenAI clients (ChatGPT, Codex)";
12301
+ function fileParamNote(otherwise) {
12302
+ return `${FILE_PARAM_CLIENTS} only: pass each file as you have it (attached in the chat or created in your sandbox, by its file id or path) and the client swaps it for { download_url, file_id, file_name?, mime_type? } before the call reaches Wix. No other client does this swap, so elsewhere a path or file:// URL here fails at once; leave this out and ${otherwise}.`;
12303
+ }
12304
+
12305
+ // src/site-widget-tools/constants.ts
12306
+ var WIX_BUSINESS_FEATURES = "a store, bookings or appointments, rentals, events and tickets, a restaurant menu with online orders or reservations, a blog, a portfolio, pricing plans and memberships, donations, Wix Forms, members and login, a CMS, payments";
12307
+ var SITE_CREATION_DEFAULT_ACTIONS = "add products, set up bookings, and adjust your content";
12308
+ var SITE_CREATION_CUSTOM_ACTIONS_GUIDANCE = `2\u20133 specific, high-value setup actions for the site's industry, written as a comma-separated phrase (it slots into "\u2026ask me to ___ right here in the chat"). Use the following domain patterns as guidance:
12309
+ * E-commerce / Retail: "add products, set up payment methods, and organize collections"
12310
+ * Restaurants / Food / Bakeries: "add menu items, set up online ordering, and collect table reservations"
12311
+ * Services / Fitness / Salons / Consultants: "set up booking slots, add service pricing, and manage appointments"
12312
+ * Creative / Portfolios / Photographers: "upload project galleries, feature client reviews, and customize contact forms"
12313
+ * Events / Weddings / Real Estate: "add event schedules, display listings, and manage RSVP forms"
12314
+ * General / Unspecified: "${SITE_CREATION_DEFAULT_ACTIONS}"
12315
+ If none of these patterns fit the site, infer 2\u20133 actions that suit what that kind of business would manage on its site.`;
12316
+ var SITE_BUILDER_CHOICE = {
12317
+ here: {
12318
+ label: "Build it here with me",
12319
+ pitch: "I build it in code right here, and you change anything by asking me"
12320
+ },
12321
+ editor: {
12322
+ label: "Design it in Wix's editor",
12323
+ pitch: "Wix designs a first version or you start from a template, then you edit it in Wix"
12324
+ },
12325
+ /** The editor's second step, in the widget and its reply. */
12326
+ editorStart: {
12327
+ wix: {
12328
+ label: "Let Wix design it",
12329
+ pitch: "Wix designs a first version from your description for you to edit"
12330
+ },
12331
+ template: {
12332
+ label: "Browse templates",
12333
+ pitch: "start from a ready-made design and edit it in Wix"
12334
+ }
12335
+ },
12336
+ /** Asked in chat when no widget is shown: who builds it. */
12337
+ whoQuestion: "Do you want me to build it here, so you can change anything just by asking me, or do you want to design it yourself in Wix's editor?",
12338
+ /** Asked next, only when the user chose Wix's editor. */
12339
+ editorQuestion: "Should Wix design a first version for you to edit, or do you want to start from a template?",
12340
+ /** What the widget sends as the user's message when they pick "Build it here with me". */
12341
+ hereFollowUp: "Build it here with me, in code, so I can keep changing it by asking you."
12342
+ };
12343
+
12114
12344
  // src/config/default-config.ts
12115
12345
  var AGENT_GOAL = dedent_default`
12116
- You are an agent that helps the user manage their Wix site.
12117
- Your goal is to get the user's prompt/task and execute it by using the appropriate tools eventually calling the correct Wix APIs with the correct parameters until the task is completed.
12346
+ You help the user with their Wix sites and account.
12347
+ Work out what the task needs and do it: read the Wix skills, docs and API schemas that cover it, and write and run the code that calls Wix APIs, until the task is done.
12118
12348
  `;
12349
+ var CALL_WIX_SITE_API_RECONNECT_LINE = "If the Wix MCP offers no ExecuteWixAPI at all, your Wix connection is likely an old or cached version: tell the user once to reconnect the Wix connector or restart the client, and use this tool until then.";
12350
+ var CALL_WIX_SITE_API_NO_EXECUTE_LINE = "If ExecuteWixAPI isn't in your tool list, use this tool.";
12119
12351
  var DISCOVERY_FLOWS = [
12120
- "- CONVERSATION CONTEXT BASED: find relevant docs article or API example for the user's prompt/task in the conversation context -> call API using CallWixSiteAPI() based on the docs article or API example",
12121
- "- METHOD CODE EXAMPLE BASED: BrowseWixRESTDocsMenu() or SearchWixRESTDocumentation() -> find relevant method -> read method article using ReadFullDocsArticle() to get method code examples -> call API using CallWixSiteAPI() based on the method code examples",
12122
- "- FULL SCHEMA BASED: BrowseWixRESTDocsMenu() or SearchWixRESTDocumentation() -> find relevant method -> read method article using ReadFullDocsArticle() -> no method code examples found -> inspect the method schema using SearchWixAPISpec or ReadFullDocsMethodSchema -> call API using CallWixSiteAPI() based on the schema"
12352
+ "- CONVERSATION CONTEXT BASED: find relevant docs article or API example for the user's prompt/task in the conversation context -> call the API with ExecuteWixAPI based on the docs article or API example",
12353
+ "- METHOD CODE EXAMPLE BASED: BrowseWixRESTDocsMenu() or SearchWixRESTDocumentation() -> find relevant method -> read method article using ReadFullDocsArticle() to get method code examples -> call the API with ExecuteWixAPI based on the method code examples",
12354
+ "- FULL SCHEMA BASED: BrowseWixRESTDocsMenu() or SearchWixRESTDocumentation() -> find relevant method -> read method article using ReadFullDocsArticle() -> no method code examples found -> inspect the method schema using SearchWixAPISpec or ReadFullDocsMethodSchema -> call the API with ExecuteWixAPI based on the schema"
12123
12355
  ];
12356
+ function directRouteLines(includeReadme, includeImageUploadGuidance) {
12357
+ return [
12358
+ ...includeReadme ? [
12359
+ `- **Creating a new site.** Skip the docs too. A site is written in code and uploaded: StartDeployWebsite returns the upload guide; StartHeadlessBuild returns the headless guide, for a site that needs Wix business features (${WIX_BUSINESS_FEATURES}). Call one right away, without asking the user anything first: StartHeadlessBuild when the site needs Wix business features, otherwise StartDeployWebsite. WixHarmonyEditorBuild or SearchSiteTemplates only when the user asked for Wix's editor, the Wix Harmony Editor, a template, Wix Studio or the Classic Editor. Never create a site through ManageWixSite, CallWixSiteAPI or ExecuteWixAPI.`,
12360
+ "- **Suggesting domain names**: call GetSuggestedDomains directly, with the business or site name as the query."
12361
+ ] : [],
12362
+ "- **Listing, showing or finding their Wix sites**: call ListWixSites directly.",
12363
+ ...includeImageUploadGuidance ? [
12364
+ "- **Uploading local or attached image files to a Wix site**: call UploadImageToWixSite directly, with no docs, schema or API calls. Do NOT use ExecuteWixAPI, SearchWixAPISpec, or any Media Manager REST API for image uploads."
12365
+ ] : []
12366
+ ];
12367
+ }
12124
12368
  function buildSystemReminderGuidelines(includeReadme, includeImageUploadGuidance) {
12125
- const exception = includeReadme ? "**Exception:** " : "";
12126
12369
  const lines = [];
12127
- if (includeReadme) {
12128
- lines.push(
12129
- "if the WixREADME tool is available to you, YOU MUST USE IT AT THE BEGINNING OF ANY CONVERSATION and then continue with calling the other tools and calling the Wix APIs until the task is completed.",
12130
- "**Creating a new Wix site \u2014 and only then \u2014 skip WixREADME and the docs.** First settle one thing: will the user keep working on this site with you, in code, or design it themselves in Wix's editor? In code \u2014 you or the user have code, the two of you designed it here, they asked you to build it, they asked for a headless site, or it is an app: StartDeployWebsite if the code exists in any form, StartHeadlessBuild if not, called right away (it only returns the guide; the site's purpose is not needed first). In Wix's editor: the site-creation tools; their descriptions carry the rules. Everything else starts with WixREADME as usual.",
12131
- "Never default to AI or search templates unless the user explicitly requests it. When the user named no creation method - let the user make that choice as the site-creation tools' descriptions direct. Never route site creation through ManageWixSite, CallWixSiteAPI, or ExecuteWixAPI."
12132
- );
12133
- }
12134
12370
  lines.push(
12135
- `${exception}If the user asks to list, show, or find their Wix sites, ${includeReadme ? "skip WixREADME and " : ""}call ListWixSites directly.`
12371
+ includeReadme ? "Start every Wix task with WixREADME, questions included, then use the other tools and the Wix APIs until the task is done. If WixREADME is not available, follow the flows without it. These requests skip it and go straight to their own tool:" : "These requests go straight to their own tool, with no docs or API calls first:"
12136
12372
  );
12137
- if (includeImageUploadGuidance) {
12138
- lines.push(
12139
- `${exception}If the user wants to upload local or attached image files to a Wix site, skip ${includeReadme ? "WixREADME and " : ""}all docs/schema/API flows \u2014 call UploadImageToWixSite directly. Do NOT use ExecuteWixAPI, SearchWixAPISpec, or any Media Manager REST API for image uploads.`
12140
- );
12141
- }
12142
- if (includeReadme) {
12143
- lines.push(
12144
- "If the WixREADME tool is not available to you, you should use the other flows as described without using the WixREADME tool until the task is completed."
12145
- );
12146
- }
12373
+ lines.push(...directRouteLines(includeReadme, includeImageUploadGuidance));
12147
12374
  lines.push(
12148
- `This applies to QUESTIONS too, not only action tasks (the ${includeReadme ? "exceptions" : "direct-tool routes"} above still apply \u2014 question-shaped variants of those intents, e.g. "can you show my sites?", follow their ${includeReadme ? "exception's " : ""}direct tool): never answer a Wix-specific question from memory, and never reply that you lack the tools or APIs to answer, without first ${includeReadme ? "calling the WixREADME tool (if available) and checking its recipes index, searching the docs," : "searching the docs"} and reading the matching docs articles \u2014 many questions (including ones that need no API call) are answered by a ${includeReadme ? "recipe or docs article" : "docs article"}.`,
12149
- "If the user prompt / task is an instruction to do something in Wix, You should not tell the user what Docs to read or what API to call, your task is to do the work and complete the task in minimal steps and time with minimal back and forth with the user, unless absolutely necessary."
12375
+ `A question follows the same routes: one about those requests goes to its tool ("can you show my sites?" is ListWixSites). Any other Wix question is answered from ${includeReadme ? "WixREADME, the Wix skills, " : ""}the docs search and the matching docs articles, never from memory, and never with a reply that you lack the tools or APIs to answer. Many questions, including ones that need no API call, are answered by a ${includeReadme ? "Wix skill or docs article" : "docs article"}.`,
12376
+ "When the user asks you to do something in Wix, do it, in as few steps and questions as you can; don't tell them which docs to read or which API to call."
12150
12377
  );
12151
12378
  return lines.join("\n");
12152
12379
  }
12153
12380
  function buildSystemReminderFlows(includeReadme) {
12154
12381
  const lines = ["Wix MCP Site Management Flows"];
12382
+ const noSkill = "no Wix skill covers the user's prompt/task -> ";
12155
12383
  if (includeReadme) {
12156
12384
  lines.push(
12157
12385
  "With WixREADME tool:",
12158
- "- RECIPE BASED (PREFERRED!): WixREADME() -> find relevant recipe for the user's prompt/task -> read recipe using ReadFullDocsArticle() -> call Wix API using CallWixSiteAPI() based on the recipe",
12386
+ "- SKILL BASED (PREFERRED!): WixREADME() -> find the Wix skill for the user's prompt/task (WixREADME says how to read one) -> follow it -> call the API with ExecuteWixAPI based on it",
12159
12387
  DISCOVERY_FLOWS[0],
12160
- "- EXAMPLE BASED: WixREADME() -> no relevant recipe found for user's prompt/task -> BrowseWixRESTDocsMenu() or SearchWixRESTDocumentation() -> find relevant method -> read method article using ReadFullDocsArticle() to get method code examples -> call API using CallWixSiteAPI() based on the method code examples",
12161
- "- SCHEMA BASED, FALLBACK: WixREADME() -> no relevant recipe found for user's prompt/task -> BrowseWixRESTDocsMenu() or SearchWixRESTDocumentation() -> find relevant method -> read method article using ReadFullDocsArticle() -> no method code examples found -> inspect the method schema using SearchWixAPISpec or ReadFullDocsMethodSchema -> call API using CallWixSiteAPI() based on the schema",
12388
+ `- EXAMPLE BASED: WixREADME() -> ${noSkill}BrowseWixRESTDocsMenu() or SearchWixRESTDocumentation() -> find relevant method -> read method article using ReadFullDocsArticle() to get method code examples -> call the API with ExecuteWixAPI based on the method code examples`,
12389
+ `- SCHEMA BASED, FALLBACK: WixREADME() -> ${noSkill}BrowseWixRESTDocsMenu() or SearchWixRESTDocumentation() -> find relevant method -> read method article using ReadFullDocsArticle() -> no method code examples found -> inspect the method schema using SearchWixAPISpec or ReadFullDocsMethodSchema -> call the API with ExecuteWixAPI based on the schema`,
12162
12390
  "Without WixREADME tool:"
12163
12391
  );
12164
12392
  }
@@ -12191,41 +12419,22 @@ function buildSystemReminder(includeReadme, includeImageUploadGuidance = true) {
12191
12419
  `;
12192
12420
  }
12193
12421
  var SYSTEM_REMINDER = buildSystemReminder(true);
12422
+ var HELP_CENTER_IS_FOR_PEOPLE = "The Help Center is written for people doing things by hand in Wix's dashboard and editors. To act on a site yourself, the Wix developer docs come first: find the method with SearchWixRESTDocumentation or BrowseWixRESTDocsMenu.";
12194
12423
  var SITE_CREATION_COPY = {
12195
12424
  templatePick: {
12196
12425
  nonWidget: "After the user selects a template, call `CreateSiteFromTemplate`.",
12197
12426
  widget: "The gallery creates the site when the user picks one."
12198
- },
12199
- unstatedMethod: {
12200
- nonWidget: "you MUST ask whether they want to build with AI or start from a template, and create nothing and show nothing until they answer",
12201
- widget: "you MUST call `WixSiteCreation` and let them choose"
12202
12427
  }
12203
12428
  };
12204
- var SITE_GUIDANCE = dedent_default`
12205
- First decide who keeps building the site:
12206
- - **You, in code**: a Wix Headless site, with full control of the design, changed and redeployed from this conversation, Wix business features added as a backend. This is the case when you or the user have code (hand-written files, a static build, a zip, another builder's export, a page you made here): call StartDeployWebsite. It is also the case when nothing is coded yet but the user asked you to build it or asked for a headless site, the two of you worked out its design in this conversation, or it is an app (a game, a dashboard, a tool): call StartHeadlessBuild right away, before asking what the site is for. Do not offer AI or templates for either.
12207
- - **The user, in Wix's editor**: Wix generates it with AI or starts it from a template, and the user designs it in Wix. That is what the site-creation tools do; they never take files you already have.
12208
-
12209
- For a new site in Wix's editor there are two things to settle: what the site is for, and how to build it. The how is AI generation vs. starting from a template, so naming templates, a template id or Wix Studio (Wix Studio is also the template side) already settles it.
12210
-
12211
- Settle what the site is for first: its purpose, topic, business type, audience, or main visitor action. Naming a method is not a description of the site, and neither is the business or industry on its own.
12212
- If you can't tell yet, ask the preferred question below and nothing else — one question, no creation-method question in the same reply, and no internal tool names.
12213
- Once the purpose is clear, unless the user already named a method, ${SITE_CREATION_COPY.unstatedMethod.nonWidget}. A method they named is settled — never re-open it. Never default to AI or to templates, and never ask the user to choose between Harmony and Wix Studio; infer that from their words.
12214
- With the purpose and the method known, that's enough to begin — route and refine the specifics with the user afterward.
12215
-
12216
- Preferred question when the user has given no site context at all:
12217
- Happy to help. To give you the right starting point, tell me a bit more about what you have in mind:
12218
- - What's the site for? (e.g. an online clothing store, a yoga studio that takes bookings, a restaurant)
12219
- - What's the vibe? (e.g. sleek and modern, cozy and rustic, bold and energetic)
12220
- Give me a quick summary and I'll take it from there
12221
-
12222
- Tone: helpful, practical, short. Speak about the user's site — what you're making and what comes next — not the tools, payloads, or routing behind it.`;
12429
+ var EDITOR_TAIL = `When the user wants Wix's editor, or to design the site themselves, without saying how to start, ask them first: "${SITE_BUILDER_CHOICE.editorQuestion}" A new site is otherwise written in code and uploaded: call StartDeployWebsite, or StartHeadlessBuild when it needs Wix business features, right away and without asking the user anything first.`;
12223
12430
  var toWidget = (text) => Object.values(SITE_CREATION_COPY).reduce(
12224
12431
  (acc, { nonWidget, widget }) => acc.replaceAll(nonWidget, widget),
12225
12432
  text
12226
12433
  );
12434
+ var WIX_API_DATA = "products, inventory, carts, checkouts, orders, bookings, services, staff, time slots, rentals, restaurant menus, online orders, reservations, events, tickets, RSVPs, blog posts, CMS collections and items, pricing plans, subscriptions, portfolios, gift cards, coupons, donations, forms, form submissions, contacts, members, loyalty points, groups, reviews, inbox messages, CRM tasks, automations, email campaigns, payments, invoices, SEO, redirects, translations, locations, analytics, media files, site properties, installed apps, and the account's sites and domains (searching whether a domain is available, buying one, and connecting one bought elsewhere)";
12227
12435
  var EXECUTE_WIX_API_INTRO = dedent_default`
12228
- Execute JavaScript code against the Wix REST API when code is useful for chaining related calls, pagination, loops, branching, or in-memory transformation.
12436
+ Reads and changes the data of a Wix site or account: query, list, count, create, update and delete it with one JavaScript function over the Wix REST API.
12437
+ One function holds the whole task: chained calls, pagination, loops, branching, and the reads that feed its writes.
12229
12438
  Putting a site live or changing a headless site's files starts with StartDeployWebsite; building a new site in code with StartHeadlessBuild.
12230
12439
 
12231
12440
  CODE CONTRACT:
@@ -12311,7 +12520,7 @@ var ACCOUNT_SITE_EXECUTE_SCOPE = dedent_default`
12311
12520
  declare const files: Array<{ path: string; content: string; encoding?: "base64" }>;
12312
12521
  \`\`\`
12313
12522
 
12314
- FILE UPLOADS: files never go inline in \`code\`; two params feed the \`files\` global. \`attachments\` works in ChatGPT only: pass files attached in the chat or created in your sandbox (any type, up to 10 MB per call: a zip of a whole site, HTML, images, PDFs) as you have them, by file id or /mnt/data path, all in one call. ChatGPT swaps each for \`{ download_url, file_id, file_name?, mime_type? }\` before the call reaches Wix, and the bytes arrive as base64 entries named by file name. Other clients send the value unchanged and the call fails before running; there, leave \`attachments\` out, or upload from your shell if it reaches the internet. The \`files\` param takes content you have as text here: text goes raw; a small binary (icon, logo, font) rides as a \`base64\`-tagged entry — large images/video are referenced by URL (\`<img src="https://...">\`), not bundled. \`wix.multipart()\` turns the whole set into one upload body (bytes, so binary is safe):
12523
+ FILE UPLOADS: files never go inline in \`code\`; two params feed the \`files\` global. \`attachments\`: ${fileParamNote("upload from your shell if it reaches the internet")} Any type, up to 10 MB per call, all in one call: a zip of a whole site, HTML, images, PDFs; the bytes arrive as base64 entries named by file name. The \`files\` param takes content you have as text here: text goes raw; a small binary (icon, logo, font) rides as a \`base64\`-tagged entry — large images/video are referenced by URL (\`<img src="https://...">\`), not bundled. \`wix.multipart()\` turns the whole set into one upload body (bytes, so binary is safe):
12315
12524
  \`\`\`javascript
12316
12525
  // files param:
12317
12526
  // === FILE: index.html ===
@@ -12326,6 +12535,7 @@ var ACCOUNT_SITE_EXECUTE_SCOPE = dedent_default`
12326
12535
  return res.data;
12327
12536
  }
12328
12537
  \`\`\`
12538
+ HEADLESS SITE EDITS: \`wix.headless.files.list({siteId, prefix?, offset?, limit?})\` returns file metadata; \`read({siteId, path})\` returns UTF-8 text (up to 256 KB); \`patch({siteId, files?, deletePaths?, agent?})\` applies changed files and explicit deletions while preserving everything else. Patch defaults to the Execute \`files\` global (including resolved attachments and ZIPs). Paths are literal site-relative paths; no wrapping-folder stripping. It returns added/replaced/deleted/unchanged/missingDeletes path lists. List/read reuse one downloaded ZIP snapshot for the current site within an Execute call; switching sites discards the previous snapshot, and a new call downloads afresh. Patch downloads a fresh ZIP, then drops the full merged site and clears the read snapshot. Serialize edits to the same site; there is no atomic concurrency guarantee. For a full replacement, use the existing upload/drop path. See the upload-static-site recipe for examples and older-server fallback.
12329
12539
  A site zip passed through \`attachments\` is one base64 entry; unzip it and upload the entries:
12330
12540
  \`\`\`javascript
12331
12541
  // attachments: [site.zip] → files = [{ path: "site.zip", content: "<base64>", encoding: "base64" }]
@@ -12449,22 +12659,37 @@ var executeWixApiDescription = dedent_default`
12449
12659
 
12450
12660
  ${EXECUTE_WIX_API_MULTI_SITE_EXAMPLE}
12451
12661
  `;
12452
- function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true) {
12453
- const reminder = buildSystemReminder(
12662
+ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true, { usageGuideOnce = false } = {}) {
12663
+ const usageGuide = buildSystemReminder(
12454
12664
  includeReadme,
12455
12665
  includeImageUploadGuidance
12456
12666
  );
12457
- return {
12667
+ const reminder = includeReadme && usageGuideOnce ? "" : usageGuide;
12668
+ const descriptions = {
12669
+ // Named in SERVER_INSTRUCTIONS: when this tool's name or what it is for changes, update them to match.
12458
12670
  WixREADME: dedent_default`
12459
12671
  # Tool: WixREADME
12460
- **Directive:** \`WixREADME\` is the **MANDATORY FIRST STEP** for all Wix-related tasks. Its output (including relevant linked documents) provides foundational context for all other Wix tools. Adherence to this protocol is **NON-NEGOTIABLE**.
12461
-
12462
- **Mid-conversation site switching:** If the user references a different site by name or ID after WixREADME has already been called, use \`GetSiteContext\` directly — no need to call WixREADME again.
12463
- ${reminder}
12672
+ The context every other Wix tool assumes. Call it first, once per conversation, for any Wix task or question; for another site later in the conversation, call \`GetSiteContext\` instead of calling this again.
12673
+ A Wix task includes reading or changing a site's or account's data: ${WIX_API_DATA}.
12674
+ These requests skip it and go straight to their own tool:
12675
+ ${directRouteLines(includeReadme, includeImageUploadGuidance).join("\n")}
12676
+ A question about one of those goes to the same tool ("can you show my sites?" is ListWixSites). Any other Wix question is answered from a Wix skill or the docs, never from memory, and never with a reply that you lack the tools to answer.
12677
+ It returns, in full:
12678
+ - **Source of truth**: nothing Wix-specific from memory. Every URL, ID, setting location, product fact, request shape and field name comes from the docs tools, a Wix skill, an earlier tool result in the conversation, or the user.
12679
+ - **The site**: the account's sites with their properties and installed apps. The \`siteId\` comes from there (ask when several fit); a business solution's API works only on a site with its app installed, and an app's version changes its endpoints.
12680
+ - **Find the method**: an earlier tool result, else a Wix skill's recipe (wix-manage to manage a site, wix-headless-kit to build a headless one), else the docs: SearchWixRESTDocumentation or BrowseWixRESTDocsMenu, then ReadFullDocsArticle; the SDK, CLI, headless and apps docs for code.
12681
+ - **The exact shape**: model a call only on an example that matches the case; fill the rest with SearchWixAPISpec on the method's docs URL (request and response schemas, \`method.publicUrl\` for REST, \`method.sdk\` for the JavaScript SDK). A query's filter and sort fields and operators are a closed list per method.
12682
+ - **The call, or the code**: ExecuteWixAPI runs one async function holding every step code can resolve, the reads and the writes they feed together, \`hasMutations: true\` when anything writes. Code the user keeps (an app, a headless site, a script over REST or the JavaScript SDK) is written from the same shapes; the headless skills are a shortcut on that path.
12683
+ - **When a call fails**: the error says what was wrong and carries a Wix request id; fix that, never resend unchanged or guess.
12684
+ - **Wix skills**, what each is for and how to read one: from disk when installed (the Wix plugin, or \`npx skills add wix/skills\`, \`--skill <name>\` for one, \`-g\` for the user's home, into \`.agents/skills/<skill>/\` or a similar folder the client reads, such as \`.claude/skills/<skill>/\`), else online with ReadFullDocsArticle at \`https://www.wix.com/skills/<skill>/<path>\`.
12685
+ - **wix-manage**: REST recipes for managing a site's business solutions, one per task; follow the recipe and make its calls with ExecuteWixAPI, its docs URL in \`sourceDocUrls\`. Its list of recipes is \`SKILL.md\` in the installed skill, or online https://dev.wix.com/docs/api-reference/articles/ai-tools/wix-mcp/skills/wix-management-recipes-index.md, and each API area has its own \`<API section>/skills.md\` (for example https://dev.wix.com/docs/api-reference/business-solutions/bookings/skills.md).
12686
+ - **wix-headless-kit**: builds a Wix Headless site on shipped, verified code from wix-headless-templates, a \`@wix/sdk\` React core or the same data layer as plain REST calls: scaffold with the CLI, deploy, seed, design on the shipped hooks, release. https://www.wix.com/skills/headless-kit/SKILL.md
12687
+ - **wix-headless-templates**: the code wix-headless-kit builds from, one folder per Wix Business Solution. https://www.wix.com/skills/headless-templates/
12688
+ When the user asks you to do something in Wix, do it yourself, in as few steps and questions as you can; don't tell them which docs to read or which API to call.
12464
12689
  `,
12465
12690
  SearchWixRESTDocumentation: dedent_default`
12466
12691
  **Searches the official Wix REST API documentation — the starting point for any Wix REST API task.** Unless you already have the exact method \`docsUrl\` in context, begin the task here; this is how you discover the right endpoint. Reach for it before \`SearchWixAPISpec\`, and avoid writing code that scans or filters \`lightIndex\` to find an endpoint (e.g. \`lightIndex.filter(...)\`, \`lightIndex.flatMap(...)\`, or substring-matching on \`docsUrl\`/\`name\`/\`summary\`) — that has no relevance ranking and silently misses composite and method-level operations. This is the ranked semantic-search discovery tool over the Wix REST API docs: describe your intent in natural language and it returns the matching REST endpoints/methods (with their \`docsUrl\`) ranked by relevance. It is the first step whenever you need to do something on a Wix site via HTTP and don't already have the exact \`docsUrl\` in context — for creating, reading, updating, or deleting any kind of Wix entity, across every business domain.
12467
- It covers all Wix REST domains equally — Stores, eCommerce/orders, Bookings, CRM/contacts, Members, CMS/data collections, Events, Blog, Forms, Marketing, Media, site settings, SEO, and the rest. Just describe the resource, action, or goal in plain words (e.g. 'add a catalog item with inventory', 'book a service', 'update a data collection item', 'create a discount', 'list contacts', 'get site details endpoint', 'REST authentication'); the ranking handles matching your wording to the right API regardless of domain.
12692
+ It covers all Wix REST domains equally, every product's data: ${WIX_API_DATA}, and the rest. Just describe the resource, action, or goal in plain words (e.g. 'add a catalog item with inventory', 'book a service', 'update a data collection item', 'create a discount', 'list contacts', 'get site details endpoint', 'REST authentication'); the ranking handles matching your wording to the right API regardless of domain.
12468
12693
  **Next step after this tool:** take the \`docsUrl\` it returns and inspect that method's exact request/response schema with \`SearchWixAPISpec\` (\`getResourceSchemaByUrl(docsUrl)\`), then execute. \`SearchWixAPISpec\` is only for INSPECTING a \`docsUrl\` you already have — it is NOT a discovery tool. Skipping this step to hand-search \`lightIndex\` yourself is a mistake: raw index filtering has no relevance ranking and misses operations this ranked search finds. Discover here first, every time.
12469
12694
  If you can't find what you need, rephrase your search term or use a bigger maxResults value.
12470
12695
  ${reminder}
@@ -12486,6 +12711,7 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
12486
12711
  `,
12487
12712
  CallWixSiteAPI: dedent_default`
12488
12713
  Call Wix apis on a business or site. Use this to create, read, update, and delete data and other Wix entities in your Wix site.
12714
+ ExecuteWixAPI is the preferred tool for Wix API calls: one function holds every call of the task, the reads and the writes they feed together, where this tool makes one call per invocation. If your client loads tools on demand (tool search, deferred tools), load ExecuteWixAPI before using this tool. ${CALL_WIX_SITE_API_RECONNECT_LINE}
12489
12715
  Site-level: this tool reads and writes inside the single site named by the required \`siteId\`.
12490
12716
  **Prefer using the "ListWixSites" tool when the user asks to list or show their sites.** Only use this tool for site listing if the user needs advanced filtering or specific site details beyond what ListWixSites provides.
12491
12717
  For POST/PATCH/PUT requests, pass the request body as a JSON object or array in the "body" parameter with all the required fields and values as described in the API schema, code examples, or docs you retrieved (e.g. body: {"name": "value", "nested": {"key": "value"}} or body: [{"key": "value"}]).
@@ -12500,6 +12726,7 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
12500
12726
  Docs urls like https://dev.wix.com/docs/... are not api urls, if you want to read the docs, use the "ReadFullDocsArticle" tool
12501
12727
  ${reminder}
12502
12728
  `,
12729
+ // Named in SERVER_INSTRUCTIONS: when this tool's name or what it is for changes, update them to match.
12503
12730
  ListWixSites: dedent_default`
12504
12731
  **Use this tool whenever the user asks to list, show, get, or find their Wix sites.** This is the dedicated tool for listing Wix sites for the current user. By default it returns all sites, but you can filter by name.${includeReadme ? "\n\n**Do NOT use WixREADME before this tool.** When the user asks to list their sites, call this tool directly \u2014 no need to read documentation first." : ""}
12505
12732
 
@@ -12520,13 +12747,13 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
12520
12747
  ${reminder}
12521
12748
  `,
12522
12749
  ManageWixSite: dedent_default`
12523
- Use account level API in order to update a site.
12524
- Account-level only: this tool acts on the account itself, or on sites as objects within it, and sends no site context. Anything that reads or writes inside a site is site-level and does not belong here — use a site-level tool with that site's \`siteId\`.
12750
+ Calls an account-level Wix API: one that acts on the account itself (its sites list, domains, account settings) or on sites as objects in it (create, rename, duplicate, delete a site). It sends no site, so an API that reads or writes inside a site (its products, data, bookings, members, pages, publishing) fails here: call that through ExecuteWixAPI with the site's \`siteId\`.
12525
12751
  For POST/PATCH/PUT requests, pass the request body as a JSON object or array in the "body" parameter with all the required fields and values as described in the API schema, code examples, or docs you retrieved (e.g. body: {"name": "value", "nested": {"key": "value"}} or body: [{"key": "value"}]).
12526
12752
  The API endpoint url param MUST ALWAYS be taken from the conversation context.
12527
12753
  By conversation context we mean the endpoint url was given in the user prompt or got into the conversation context ${includeReadme ? 'by the "WixREADME" tool or ' : ""}by the "SearchWixRESTDocumentation" tool or by the "BrowseWixRESTDocsMenu" tool or by the "ReadFullDocsArticle" tool.
12528
12754
  ${reminder}
12529
12755
  `,
12756
+ // Named in SERVER_INSTRUCTIONS: when this tool's name or what it is for changes, update them to match.
12530
12757
  UploadImageToWixSite: dedent_default`
12531
12758
  Upload one or more images to a Wix site's Media Manager. Returns wixstatic.com URL and media ID.
12532
12759
  Do NOT use ExecuteWixAPI or code execution for image uploads — use this tool directly.
@@ -12536,34 +12763,39 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
12536
12763
  NEVER call this tool with a missing, empty, or guessed siteId — ask first instead.
12537
12764
 
12538
12765
  Parameters — provide image input via image and/or imageUrls, OR imageBase64:
12539
- • image (array): chat file ATTACHMENTS — files the user attached, or files you created in your own file sandbox. Each item is an object with download_url and file_id (both required), plus optional file_name and mime_type. Pass ALL attachments in one call.
12766
+ • image (array): ${fileParamNote("send the image as imageBase64 (the whole file, one per call) or as a public URL in imageUrls")} Pass ALL of them in one call.
12540
12767
  • imageUrls (array): plain public image URLs the user pasted or other tools produced (no file_id). Pass ALL URLs in one call. Can be combined with image.
12541
12768
  • imageBase64 (string): base64-encoded image + mimeType. One image at a time. Do NOT combine with image/imageUrls.
12542
12769
 
12543
12770
  Delivering the bytes — this is where uploads most often fail:
12544
- • Pass a file as a file, in image: the platform resolves it into download_url and file_id before it reaches this tool — this includes files you created in your sandbox. This server does not share your filesystem, so a bare path string (e.g. /mnt/..., /home/user/..., /Users/...) is not a file. Without a file to pass, use a public http(s) URL (imageUrls) or the complete base64 (imageBase64).
12771
+ • This server does not share your filesystem: outside image's clients, a file you can only reach by path (e.g. /mnt/..., /home/user/..., /Users/...) goes as imageBase64 (the whole file, one per call) or, if it is public, as imageUrls.
12545
12772
  • imageBase64 must be the WHOLE file, not truncated. For large images prefer imageUrls, since long base64 is easily cut off and will be rejected as unreadable.
12546
12773
  `,
12547
- SupportAndFeedback: dedent_default`
12548
- Send the user's feedback about the experience of building with Wix — the Wix MCP tools, APIs, docs, and tooling — to Wix, attributed to the authenticated user.
12549
- This is meta-feedback about building with Wix; it is NOT a support channel and not for the user's own site content.
12774
+ // Registered for Claude clients only, beside UploadImageToWixSite.
12775
+ PickImagesForWixMedia: dedent_default`
12776
+ Shows a file picker where the user selects images from their device; they upload to the site's Media Manager and the result (each image's media ID and wixstatic URL) comes back in the conversation.
12777
+ Use it only when you can't deliver the image yourself with UploadImageToWixSite: the image is on the user's device and not in your files, or it is a file in your sandbox that you can't pass as a file, a public URL or base64. When UploadImageToWixSite can take it, use that.
12778
+ `,
12779
+ ReportIssueToWix: dedent_default`
12780
+ Report one specific problem with Wix's tools, APIs or docs to the Wix team, attributed to the authenticated user: an API error or a confusing API, wrong or missing docs, a Wix MCP tool or auth dead end. It is NOT a support channel and not for the user's own site content.
12550
12781
 
12551
12782
  When to offer it (user-approved only — NEVER auto-send):
12552
- - The user explicitly asks to send feedback / report something to Wix ("tell Wix that...", "report this", "send feedback"). Then just confirm the wording and send.
12553
- - The user complains, is frustrated, or reports a Wix bug in the course of the work ("this API is broken", "the docs are wrong", "why is this so hard"). Acknowledge, then offer to pass it on.
12554
- - The session hit substantial friction — repeated API failures, wrong/missing docs, a tooling dead end, or a workaround you had to invent. When you notice the pattern, offer: "This tripped us up a few times — want me to send it to Wix as feedback?"
12555
-
12556
- When you offer, invite the user to add anything in their own words — whatever they give you goes into the message verbatim.
12557
-
12558
- What to send — a summary of the whole run, not just the last error. Structure it in four layers:
12559
- 1. Provenance — agent/model; Wix tooling used; platform/hosting; Wix areas/APIs touched; Wix products; project type/frontend; site id; relevant links (dashboard, docs pages, live site, repos); skill areas/recipes involved; other ids created this run.
12560
- 2. Narrative — user intent and run summary (what they set out to build, what worked vs. what fought back); a condensed play-by-play of the session (key exchanges, tool calls, decisions, course-corrections).
12561
- 3. Friction points — for each friction point: the step/endpoint, HTTP status and error message, the gap, the workaround you had to invent, and what you expected instead. Include ones you recovered from silently — a retry that eventually worked is exactly the signal Wix wants.
12562
- 4. Attribution — per friction point, where the fault likely lives: API behavior / API schema / API reference docs / docs articles or examples / Wix tooling / other/unsure. Don't force a tag; "unsure" beats a wrong route.
12563
- End with a bottom line: one or two sentences on the single most important problem and its impact — a plain engineering assessment, not softened or hedged.
12564
-
12565
- **IMPORTANT**: This tool actually sends the message to Wix. Confirm the final wording with the user and call it only after an explicit yes.
12566
- Do not send on a single transient error, and never more than once for the same issue. Feedback cannot be replied to or tracked by the user.
12783
+ - The user asks to report something to Wix ("tell Wix that...", "report this", "send feedback"). Then confirm the wording and send.
12784
+ - The user complains or reports a Wix bug in the course of the work ("this API is broken", "the docs are wrong"). Acknowledge, then offer to pass it on.
12785
+ - A Wix API, tool or doc kept failing, or you had to invent a workaround for it. Offer: "This tripped us up — want me to report it to Wix?"
12786
+
12787
+ When you offer, invite the user to add anything in their own words — whatever they give you goes into the report verbatim.
12788
+
12789
+ What to send — that one issue, not the session:
12790
+ - What was being attempted, in a sentence.
12791
+ - Where it failed: the tool or endpoint, the HTTP status and error message, and the request id if there is one (pass it as requestId).
12792
+ - What you expected instead, and the workaround if you found one.
12793
+ - Where the fault likely lives: API behavior / API schema / API reference docs / docs articles or examples / Wix tooling / unsure.
12794
+ - Only the context needed to reproduce it: agent/model, site id, the Wix area or API.
12795
+ No session summary or play-by-play, and nothing unrelated to the issue. Several distinct issues are separate reports, each approved by the user.
12796
+
12797
+ **IMPORTANT**: This tool actually sends the report to Wix. Confirm the final wording with the user and call it only after an explicit yes.
12798
+ Do not send on a single transient error, and never more than once for the same issue. Reports cannot be replied to or tracked by the user.
12567
12799
  `,
12568
12800
  BrowseWixRESTDocsMenu: dedent_default`
12569
12801
  Browse the Wix REST API documentation menu hierarchy.
@@ -12585,25 +12817,7 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
12585
12817
  ${reminder}
12586
12818
  `,
12587
12819
  WixSiteCreation: dedent_default`
12588
- ${SITE_GUIDANCE}
12589
-
12590
- Opens the in-chat Wix site creation chooser as an interactive widget where the user chooses between "Build with AI" and "Pick a template."
12591
-
12592
- **Once you can tell what the site is for, call this tool directly** — no ${includeReadme ? "WixREADME, skill," : "skill"} or docs search first.${includeReadme ? " Any other Wix request still starts with WixREADME as usual." : ""}
12593
-
12594
- Call this for normal new Wix site creation requests when the user's site intent is clear, but they have not chosen AI, templates, or Wix Studio.
12595
- Do not guess, and never default to AI — even after the user answers a clarifying question about the site's purpose or features.
12596
- This chooser is how the builder choice gets made — never ask in chat whether they want AI or a template; that choice is made in the widget.
12597
- The widget also does the creating: once the user picks, it runs that path itself and the site is created there, so never call \`WixSiteBuilder\`, \`SearchSiteTemplates\`, or \`CreateSiteFromTemplate\` yourself after opening it.
12598
- After calling, reply with the message it returns, which presents both options and the next steps.
12599
-
12600
- Do not call this if the user explicitly asks to build with AI; call \`WixSiteBuilder\` instead.
12601
- Do not call this if the user asks for templates or Wix Studio; call \`SearchSiteTemplates\` instead.
12602
- Not for a site the user wants you to build in code, a design you worked out together, or an app: call \`StartHeadlessBuild\`.
12603
-
12604
- When calling this tool, infer \`customActions\` from the user's request so the reply can be tailored — 2-3 concrete, industry-specific setup actions for the site (see the input's guidance).
12605
-
12606
- After calling, you MUST speak the message provided in the tool result to the user as your reply — do not stay silent and do not replace it with a one-line pointer to the widget. Write out ALL of it, word for word: its opening line (the "Ready to set up your … site?" question INCLUDING "Choose your path in the widget above:"), BOTH of its option bullets, and its "Next up:" line, each on its own line and in that order. Do not summarize, shorten, drop any line, invent your own opener, rename the options, or add an intro or outro.
12820
+ Returns which Wix tool creates a new site, and creates nothing itself. A new site is written in code and uploaded: StartDeployWebsite returns the upload guide, and StartHeadlessBuild returns the guide for a site that needs Wix business features. Use WixHarmonyEditorBuild or SearchSiteTemplates only when the user asked for Wix's editor, a template, Wix Studio or the Classic Editor.
12607
12821
  `,
12608
12822
  "import-claude-design-from-url": dedent_default`
12609
12823
  Import a design created in Claude's design tool into Wix. Only for the signed \`claudeusercontent.com\` bundle URL Claude Design's "Send to Wix" produces (a self-contained HTML bundle with all images, fonts, and styles inlined). Creates a live Wix-hosted site and returns its URL.
@@ -12633,6 +12847,13 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
12633
12847
  `,
12634
12848
  SearchWixWDSDocumentation: dedent_default`
12635
12849
  Searches the Wix Design System (WDS) documentation.
12850
+ `,
12851
+ SearchWixHelpCenter: dedent_default`
12852
+ Searches the Wix Help Center (support.wix.com): how site owners do things in the Wix dashboard and editors, such as connecting a domain, managing a Premium plan, setting up a store or bookings, and fixing account or site issues.
12853
+ ${HELP_CENTER_IS_FOR_PEOPLE}
12854
+ Use it for "how do I ... in Wix" questions about the product, or to give the user click-by-click steps for something no API covers.
12855
+ Not for building with code: for Wix APIs, SDK, CLI, apps, or Headless, use the matching developer documentation search.
12856
+ Results are excerpts; read a full article by passing its support.wix.com link to \`ReadFullDocsArticle\`.
12636
12857
  `,
12637
12858
  SearchWixSDKDocumentation: dedent_default`
12638
12859
  Searches the Wix JavaScript SDK documentation.
@@ -12643,7 +12864,7 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
12643
12864
  `,
12644
12865
  SearchDevelopmentDocumentation: dedent_default`
12645
12866
  Broad Wix development documentation (platform concepts, developer guides, getting started). **Last resort — development tasks only.**
12646
- Skip it entirely for general, business, support, or Wix-editor questions.
12867
+ Skip it entirely for general, business, support, or Wix-editor questions; for those, use \`SearchWixHelpCenter\`.
12647
12868
  Call it only after the specific search for your task returned nothing useful: \`SearchWixRESTDocumentation\` (REST), \`SearchWixSDKDocumentation\` (JS SDK), \`SearchWixCLIDocumentation\` (CLI), \`SearchBuildAppsDocumentation\` (apps), \`SearchWixHeadlessDocumentation\` (Headless), \`SearchWixWDSDocumentation\` (WDS). Never use it as a first search.
12648
12869
  `,
12649
12870
  SearchWixHeadlessDocumentation: dedent_default`
@@ -12655,7 +12876,8 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
12655
12876
  // `sdk` is simply absent on methods the SDK docs don't list.
12656
12877
  SearchWixAPISpec: dedent_default`
12657
12878
  Inspect the Wix REST API spec, and the JavaScript SDK call for each method, by writing JavaScript code that runs in a sandboxed read-only environment.
12658
- **Precondition:** you already have a method \`docsUrl\` — from \`SearchWixRESTDocumentation\`, \`SearchWixSDKDocumentation\` (its \`?apiView=SDK\` URLs work here as-is), \`BrowseWixRESTDocsMenu\`, \`ReadFullDocsArticle\`, ${includeReadme ? "a `WixREADME` recipe, " : ""}or a prior tool result / the user's message in this conversation. If you don't have one yet, call \`SearchWixRESTDocumentation\` (or \`SearchWixSDKDocumentation\` when writing SDK code) FIRST to discover it; do not use this tool to search for or choose an endpoint.
12879
+ The spec covers every Wix product's methods and their data: ${WIX_API_DATA}.
12880
+ **Precondition:** you already have a method \`docsUrl\` — from \`SearchWixRESTDocumentation\`, \`SearchWixSDKDocumentation\` (its \`?apiView=SDK\` URLs work here as-is), \`BrowseWixRESTDocsMenu\`, \`ReadFullDocsArticle\`, ${includeReadme ? "a Wix skill, " : ""}or a prior tool result / the user's message in this conversation. If you don't have one yet, call \`SearchWixRESTDocumentation\` (or \`SearchWixSDKDocumentation\` when writing SDK code) FIRST to discover it; do not use this tool to search for or choose an endpoint.
12659
12881
  **Never pass a \`docsUrl\` you produced from memory.** If the exact URL you are about to pass to \`getResourceSchemaByUrl\` did not appear verbatim in an earlier tool result (or the user's message), it is a guess — call \`SearchWixRESTDocumentation\` first and use a URL from its ranked results. A guessed URL that merely looks plausible routinely resolves to the WRONG sibling method (e.g. \`.../products-v3/create-product\` when the task needs \`.../products-v3/create-product-with-inventory\`), sending the whole turn down a dead end. When in doubt, discover — don't guess.
12660
12882
  **This tool's job is to INSPECT the exact schema of a method you have ALREADY located — its request/response shape, nested types, and enums, and its SDK call (\`method.sdk\`). It is NOT a discovery tool for choosing which method to use.**
12661
12883
  With a method \`docsUrl\` in hand, call \`getResourceSchemaByUrl(methodDocsUrl)\` to get the resource schema, then read the target method from \`schema.methods\` and resolve any nested \`{ $circular: name }\` types via \`schema.components.schemas[name]\`.
@@ -13036,27 +13258,18 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
13036
13258
  \`\`\`
13037
13259
  `,
13038
13260
  ExecuteWixAPI: executeWixApiDescription,
13039
- WixSiteBuilder: dedent_default`
13040
- ${SITE_GUIDANCE}
13041
-
13042
- Tool for generating a Wix site with AI (creating / building a site in Wix Harmony) from a description. It does not take pages or files you already have — those go to StartDeployWebsite.
13043
-
13044
- Call this ONLY when the user's own words name AI (for example "create a site using AI" or "build it with AI") and the site intent is clear. A description of what the site should do — reservations, online ordering, a store, bookings, menus, a portfolio, and the like — is NOT a request for AI and NOT enough to call this tool; never infer AI from features or industry.
13045
- Never default to this tool: if the user hasn't explicitly named AI, don't call this tool — ${SITE_CREATION_COPY.unstatedMethod.nonWidget}.
13046
-
13047
- For templates or Wix Studio, call \`SearchSiteTemplates\` (\`builder: classic\` for the Classic Editor); for a template id, \`CreateSiteFromTemplate\`. A site the user wants you to build in code, or an app: \`StartHeadlessBuild\`.
13048
-
13049
- Do NOT suggest HTML code, prompt templates, or alternative approaches instead of building — actually build the site through this tool. (HTML the user already has is the StartDeployWebsite case, not a reason to build.)
13261
+ // Named in SERVER_INSTRUCTIONS: when this tool's name or what it is for changes, update them to match.
13262
+ WixHarmonyEditorBuild: dedent_default`
13263
+ A secondary site-creation tool, only for a user who asked for the Wix Harmony Editor or for Wix to design the site in Wix's editor. A request for a site on Wix, or for Wix to build or make one, is neither. Don't call it for any other site request, not even to check with the flag false. A new site is otherwise written in code and uploaded, with StartDeployWebsite, or StartHeadlessBuild when it needs Wix business features.
13264
+ Builds a site with the Wix Harmony Editor: Wix designs a first version from the user's description, and the user keeps designing it in Wix's editor. Shows the build's progress and a preview.
13265
+ Call it when the user asked for the Wix Harmony Editor, or for Wix to design the site in Wix's editor, in their own words or as their answer to your question, and quote those words in \`editorRequest\`. ${EDITOR_TAIL}
13050
13266
  `,
13267
+ // Named in SERVER_INSTRUCTIONS: when this tool's name or what it is for changes, update them to match.
13051
13268
  SearchSiteTemplates: dedent_default`
13052
- ${SITE_GUIDANCE}
13053
-
13054
- Searches Wix templates and shows the template gallery, from the Harmony, the Studio or the Classic Editor template source.
13055
-
13056
- Call this ONLY when the user's own words ask for a template, for Wix Studio or for the Classic Editor (for example "start from a template", "show me some templates", "build it in Wix Studio", "the old Wix editor") and you can already tell what the site is for. If they mention Wix Studio, search Studio templates; if they ask for the Classic Editor (the Wix Editor), search \`classic\`; otherwise search Harmony templates.
13057
- ${SITE_CREATION_COPY.templatePick.nonWidget}
13058
-
13059
- A description of what the site should do — bookings, a store, a menu, a portfolio, and the like — is NOT a request for templates or for Wix Studio; never infer either from features or industry. Templates are not the default: if the site intent is clear but no method is named, do not guess — ${SITE_CREATION_COPY.unstatedMethod.nonWidget}.`,
13269
+ A secondary site-creation tool, only for a user who asked for a template, Wix Studio or the Classic Editor. Don't call it for any other site request, not even to check with the flag false. A new site is otherwise written in code and uploaded, with StartDeployWebsite, or StartHeadlessBuild when it needs Wix business features.
13270
+ Shows a gallery of Wix templates, from Harmony, Wix Studio or the Classic Editor, for a site the user will design in Wix's editor. Wix templates come with Wix's business features (a store, online ordering, bookings, forms), so a request for a template comes here even when the site needs them.
13271
+ Call it when the user asked for a template, Wix Studio or the Classic Editor, in their own words or as their answer to your question, and quote those words in \`templateRequest\`. ${EDITOR_TAIL} ${SITE_CREATION_COPY.templatePick.nonWidget}
13272
+ `,
13060
13273
  CreateSiteFromTemplate: dedent_default`
13061
13274
  Creates a Wix site from a specific template (by \`metaSiteId\` or \`templateId\`), publishes it when possible, and shows a live preview with links to the editor and the published site.
13062
13275
 
@@ -13090,102 +13303,211 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
13090
13303
  Set \`hasMutations: true\` for any document containing a mutation root field.
13091
13304
  `,
13092
13305
  UploadHeadlessWebsiteFiles: dedent_default`
13093
- The upload step of the StartDeployWebsite and StartHeadlessBuild guides, and nothing else: puts a finished static website's files — index.html plus its CSS/JS/images, a static build, or a zip of the site — onto an existing Wix Headless site by its metaSiteId. Call it ONLY after one of them has returned its guide in this conversation and the site exists: for a new site, first run the recipe's provision call and pass the metaSiteId it returns; for a site deployed earlier, pass its metaSiteId. It does not create sites. Every upload replaces the whole file set and the URL stays. Returns the live URL and the dashboard link, and points back to the recipe for what comes next.
13094
- Pass the files themselves: in ChatGPT as \`attachments\` (any type, a zip is unpacked), elsewhere as the \`files\` text bundle (\`=== FILE: <path> ===\` sections, \`base64\` tag for small binaries). The site must contain index.html at its root.
13095
- NOT a site-creation tool: a request to create, build, design or generate a site — with or without a description — never comes here (those are the site-creation tools, after their own questions). Also NOT for media uploads to an existing site (UploadImageToWixSite) or any other API call (ExecuteWixAPI).
13306
+ The upload step of the StartDeployWebsite and StartHeadlessBuild guides, and nothing else: puts a finished website's files — a static site (index.html plus its CSS/JS/images, a static build, or a zip of the site) or a Wix Astro build — onto an existing Wix Headless site by its metaSiteId. Call it ONLY after one of them has returned its guide in this conversation and the site exists: for a new site, first run the recipe's provision call and pass the metaSiteId it returns; for a site deployed earlier, pass its metaSiteId. It does not create sites. Every upload replaces the whole file set and the URL stays. Returns the live URL and the dashboard link, and points back to the recipe for what comes next.
13307
+ Pass the files themselves: in ${FILE_PARAM_CLIENTS} as \`attachments\` (any type, a zip is unpacked), elsewhere as the \`files\` text bundle (\`=== FILE: <path> ===\` sections, \`base64\` tag for small binaries). A static site must contain index.html at its root. A Wix Astro build with no file handle, from a shell that reaches the internet, goes in two calls: zip the build, call with \`prepare: true\` and the zip's \`size\` in bytes, PUT the zips it names with the \`curl\` commands it returns (the build, and the project source when the site's repository accepts it), then call again with the \`uploadKey\` it returned; that releases the build. The two calls, literally: {siteId, prepare: true, size} then {siteId, uploadKey, sourceUploadId?, expectedHeadRevisionId?, releaseNotes?}. A sandbox whose network is limited to a few hosts cannot upload to those URLs. A static site does not use the two calls. A Wix Astro build (the project's .wix/ and dist/ after \`astro build\` with the Wix hosting adapter, zipped from the project root) needs no index.html: it is released to the site's Wix hosting instead, server-rendered pages, the same site and URL. The project's source is stored with the release when the upload holds the project and the site's repository accepts it.
13308
+ NOT a site-creation tool: a request to create, build, design or generate a site — with or without a description — never comes here first. Unless the user asked for Wix's editor or a template (WixHarmonyEditorBuild, SearchSiteTemplates), call StartDeployWebsite right away, without asking the user anything: it also takes files the user brought, uploaded first even when the site needs business features. StartHeadlessBuild instead for a new site with no files that needs Wix business features. Also NOT for media uploads to an existing site (UploadImageToWixSite) or any other API call (ExecuteWixAPI).
13096
13309
  `,
13310
+ // Named in SERVER_INSTRUCTIONS: when this tool's name or what it is for changes, update them to match.
13097
13311
  StartDeployWebsite: dedent_default`
13098
- The user — or you, earlier in this conversation — ALREADY HAVE a finished page or site, in any form, and it should be live on Wix — or an already-deployed Wix Headless site whose files should be replaced. The site can be HTML/CSS/JS files, a static build (dist/, out/), a zip, another AI builder's export, or a page/design you created in this conversation (export it to HTML first — a finished design IS a finished page). Call this when the user says deploy, host, put online, make it live, get a URL, upload my site / index.html, or wants to update or redeploy the files of a site you or the user deployed as code.
13099
- Updates keep the same site and URL. Also for implementing a backend on such a site as a Wix Headless project — same site, same URL. Works with or without a Wix login (anonymous deploy with a save link), through the Wix MCP alone or from a shell.
13100
- Missing real functionality — a fake checkout, a form that goes nowhere, bookings — is NOT a reason to rebuild the site with a site-creation tool: deploy the page as is, then add the backend afterwards (the recipe covers it).
13101
- Returns the full recipe; follow it and run the flow yourself. When the UploadHeadlessWebsiteFiles tool is available, use it for the recipe's upload step — after the site exists (the recipe's provision call, or a metaSiteId from an earlier deploy) — instead of building the multipart body in code; never before this tool. The drop page is only the fallback when you can't.
13102
- NOT for: a screenshot or reference of a site the user wants Wix to generate (the site-creation tools); a site described in words with no code yet (StartHeadlessBuild); a Wix editor site's content or settings (WixSiteBuilder's edit mode, or WixREADME); a Claude Design bundle URL (import-claude-design-from-url); migrating a live site by URL or CSV exports; adding HTML or code into an existing Wix site; uploading images or documents to a site's media (UploadImageToWixSite).
13312
+ Creates a website on Wix and puts it live, or replaces the files of one deployed earlier. You write the site's code, or take the files the user brought (HTML/CSS/JS, a static build, a zip, another builder's export), and this returns the guide to create the site and upload them. Updates keep the same site and URL. Works with or without a Wix login (anonymous deploy with a save link), through the Wix MCP alone or from a shell. Call it right away, without asking the user anything first.
13313
+ When a new site needs Wix business features (${WIX_BUSINESS_FEATURES}), call StartHeadlessBuild instead. When the user brought files and the site needs those features, upload the files here first; the upload's result then points to StartHeadlessBuild to connect them.
13314
+ Returns the guide; follow it and do the work yourself. Upload with UploadHeadlessWebsiteFiles once the site exists; the drop page is only the fallback.
13315
+ NOT for: a user who asked for Wix's editor, the Wix Harmony Editor, a template, Wix Studio or the Classic Editor (WixHarmonyEditorBuild, SearchSiteTemplates); a Claude Design bundle URL (import-claude-design-from-url); migrating a live site by URL or CSV exports; adding HTML or code into an existing Wix editor site; uploading images or documents to a site's media (UploadImageToWixSite).
13103
13316
  `,
13317
+ // Named in SERVER_INSTRUCTIONS: when this tool's name or what it is for changes, update them to match.
13104
13318
  StartHeadlessBuild: dedent_default`
13105
- The user wants a site or web app that you build and keep changing in code, here with them, and no code exists yet. Wix runs its business side (store, bookings, CMS, forms, members, payments) as a Wix Headless site. Call this when the user asks you to build it yourself rather than Wix's AI or a template; when the two of you worked out a design in this conversation and nothing is coded yet; when it is an app (a game, a dashboard, a tool); when the user asks for a headless site; when a prompt links \`wix.com/headless/skill.md\` or a project's \`download.zip\`; or to move a Wix site to code.
13106
- Call it right away: it only returns the guide, and the site's purpose is not needed first (pass "not stated yet" for what you don't know). Returns the full guide; follow it and do the work yourself, choosing its route by what you can reach (a shell that reaches Wix, or this MCP alone).
13107
- NOT for: files, a build, a zip or a page that already exist (StartDeployWebsite); a site the user will design in Wix's editor (the site-creation tools); a Claude Design URL (import-claude-design-from-url).
13319
+ Returns Wix's headless guide, for a site that needs Wix business features (${WIX_BUSINESS_FEATURES}), run by Wix as a Wix Headless site. You write the site; the guide creates it, connects the features, and uploads it. Also to add those features to an existing Wix Headless site (one deployed from files is one), from a project download link, or to move a Wix site to code.
13320
+ Call it right away: it only returns the guide, and the site's details are not needed first.
13321
+ NOT for: a site that needs no Wix business feature, or files the user brought that are not live yet (StartDeployWebsite: upload them first, then come back here for the features); a user who asked for Wix's editor, the Wix Harmony Editor, a template, Wix Studio or the Classic Editor, even when the site needs business features (WixHarmonyEditorBuild, SearchSiteTemplates); a Claude Design URL (import-claude-design-from-url).
13108
13322
  `
13109
13323
  };
13324
+ descriptions.SupportAndFeedback = descriptions.ReportIssueToWix;
13325
+ return descriptions;
13110
13326
  }
13111
- var defaultToolDescriptions = buildToolDescriptions(true);
13327
+ var defaultToolDescriptions = buildToolDescriptions(true, true, { usageGuideOnce: true });
13112
13328
  var executeCodeModeToolDescriptions = {
13113
13329
  ExecuteWixAPI: executeWixApiDescription
13114
13330
  };
13331
+ var WIX_SKILLS_README_SECTION = dedent_default`
13332
+ ## Wix skills
13333
+
13334
+ Wix publishes agent skills: folders of instructions, scripts and shipped code for building on Wix, each with a \`SKILL.md\` at its root that says what the skill does and which of its files to read when. The source is https://github.com/wix/skills. Three of them matter here:
13335
+
13336
+ - **wix-headless-kit**: builds a Wix Headless site on shipped, verified code. In the Wix plugin. Its contents are below.
13337
+ - **wix-headless-templates**: the code wix-headless-kit builds from, one folder per Wix Business Solution. In the Wix plugin for Codex and Cursor. Its contents are below.
13338
+ - **wix-manage**: REST recipes to configure and manage a site's business solutions. In the Wix plugin. Its \`SKILL.md\` is the index of every recipe by area, with what it does and a link to read it.
13339
+
13340
+ On a site built in code (one deployed through StartDeployWebsite, started through StartHeadlessBuild, or any Wix Headless project), wix-headless-kit carries the work, with wix-headless-templates as its code, and it names the wix-manage recipes it uses as steps.
13341
+
13342
+ ### How to read a skill
13343
+
13344
+ A skill is a folder, and every file in it has a path inside that folder (\`SKILL.md\`, \`guides/api-run.md\`). The same path finds the file on disk and online, so the paths below work either way.
13345
+
13346
+ 1. **From disk, when it is already there.** This is the preferred way: the files are complete and need no network. The Wix plugin loads its skills into the client; \`npx skills add\` puts them under \`.agents/skills/<skill>/\` in the project, or in the user's home with \`-g\`; an earlier install may have left them wherever the client keeps skills (for example \`.claude/skills/\` or the plugin's folder). Open \`<skill folder>/SKILL.md\` and the files it names. An installed copy can be an older snapshot. A file whose byte size differs from the one in the skill's manifest (below) is out of date, so read it online; a matching size makes a file likely current, not certain, so when a file decides the work, read it online too.
13347
+ 2. **Install them, with a shell that reaches the internet.** \`npx skills add wix/skills\` installs every skill; \`npx skills add wix/skills --skill wix-headless-kit\` installs one. Then read them from disk. To keep them across projects, the user can install the Wix plugin instead (in Claude Code: \`/plugin marketplace add wix/skills\`, then \`/plugin install wix@wix\`).
13348
+ 3. **Read them online, with ReadFullDocsArticle**, when neither works: no shell, or a sandbox without internet. Call ReadFullDocsArticle with the URL as \`articleUrl\`; it returns the file whole, where a client's own web tool often cannot reach www.wix.com or summarizes what it fetches. The skills registry mirrors every skill from the main branch of wix/skills:
13349
+ - **A file**: \`https://www.wix.com/skills/<skill>/<path>\`, with \`<path>\` its path inside the skill folder. Start with \`<path>\` = \`SKILL.md\`, then read the files it names.
13350
+ - **A folder**: \`https://www.wix.com/skills/<skill>/<folder>\` returns the full URL of every file under it, at any depth. \`https://www.wix.com/skills/<skill>/<folder>/_content\` returns those files' contents in one response, each under a heading with its URL, Markdown first; binary files and lockfiles are left out, and past 150K characters the rest are named by URL, to read one by one.
13351
+ - **The skill's file list**: \`https://www.wix.com/skills/<skill>/\`, with the trailing slash, returns the full URL of every file in the skill; without the slash it returns the SKILL.md.
13352
+ - **Every file in the skill**: \`https://www.wix.com/skills/<skill>.manifest.json\` lists each path with its size, under a \`base\` URL.
13353
+ - **Names and hosts**: \`<skill>\` is the folder name with or without its \`wix-\` prefix (\`wix-headless-kit\` or \`headless-kit\`), and \`dev.wix.com\` works in place of \`www.wix.com\`.
13354
+ - **What comes back**: a Markdown file comes with a short note on top (it is mirrored from the main branch of wix/skills; the skill's manifest, folder listings, \`_content\`, and ReadFullDocsArticle) and its relative links turned into full URLs; code, scripts and JSON come as they are.
13355
+ - **A wrong path**: the answer names the files with that name elsewhere in the skill and the manifest URL, so one more call reaches the right file. ReadFullDocsArticle shows that answer as an error.
13356
+ - **Not served**: a whole skill as one page (\`<skill>-full\`) returns 404; read the skill's files instead.
13357
+
13358
+ The wix-manage recipes are also published in the Wix docs, as articles at \`…/skills/<recipe>\` under each API's section. Find the one that matches the task in the skill's recipe index (\`SKILL.md\`) or with SearchWixRESTDocumentation, and read it with ReadFullDocsArticle. Only wix-manage is in the docs. For example, one recipe in all three places:
13359
+ - on disk: \`<wix-manage folder>/references/sites/upload-static-site.md\`;
13360
+ - in the registry: https://www.wix.com/skills/manage/references/sites/upload-static-site.md;
13361
+ - in the docs: https://dev.wix.com/docs/api-reference/account-level/sites/skills/upload-a-website-or-html-files.
13362
+
13363
+ ### wix-manage
13364
+
13365
+ REST recipes for managing a site's business solutions: one recipe per task (installing apps, products, booking services, CMS items, SEO, domains and more), each with the exact endpoints, request bodies and order of steps. Open the recipe for a task before calling its APIs.
13366
+
13367
+ Ways to find and read a recipe, in this order:
13368
+ 1. **On disk**: the Wix plugin's wix-manage folder, or \`.agents/skills/wix-manage/\` after \`npx skills add wix/skills --skill wix-manage\`. \`SKILL.md\` lists every recipe by area with what it does; the recipe itself is under \`references/<area>/\` (ecommerce nests one more folder, such as \`references/ecommerce/shipping/\`), so open the path \`SKILL.md\` gives rather than building it. An installed copy can be an older snapshot: when a recipe decides the work, read it online too.
13369
+ 2. **Online**: the list of every recipe by area, each a link to its docs article, is https://dev.wix.com/docs/api-reference/articles/ai-tools/wix-mcp/skills/wix-management-recipes-index.md. Read it with ReadFullDocsArticle, or with \`curl\` from a shell, then read the recipe for the task the same way. It is public: no sign-in or token.
13370
+
13371
+ Using a recipe: follow its steps, and make its calls with ExecuteWixAPI, passing the recipe's docs URL in \`sourceDocUrls\`. When a recipe's example doesn't fit a call, read that method's schema with SearchWixAPISpec first. When no recipe matches the task, find the method with SearchWixRESTDocumentation or BrowseWixRESTDocsMenu, read it with ReadFullDocsArticle, and call it the same way.
13372
+ Each API area also has a page that lists only its own recipes: the area's docs section with \`/skills.md\` appended. These are the ones today, but it is not a closed list: areas are added, so look a task up in the full list first, and for an API not named here try its section URL with \`/skills.md\` appended.
13373
+ - Accessibility: https://dev.wix.com/docs/api-reference/site/accessibility/skills.md
13374
+ - Analytics: https://dev.wix.com/docs/api-reference/business-management/analytics/skills.md
13375
+ - App Installation: https://dev.wix.com/docs/api-reference/business-management/app-installation/skills.md
13376
+ - App tools: https://dev.wix.com/docs/api-reference/app-management/app-tools/skills.md
13377
+ - Automations: https://dev.wix.com/docs/api-reference/business-management/automations/skills.md
13378
+ - Blog: https://dev.wix.com/docs/api-reference/business-solutions/blog/skills.md
13379
+ - Bookings: https://dev.wix.com/docs/api-reference/business-solutions/bookings/skills.md
13380
+ - Calendar: https://dev.wix.com/docs/api-reference/business-management/calendar/skills.md
13381
+ - CMS: https://dev.wix.com/docs/api-reference/business-solutions/cms/skills.md
13382
+ - Contacts: https://dev.wix.com/docs/api-reference/crm/members-contacts/contacts/skills.md
13383
+ - Domains: https://dev.wix.com/docs/api-reference/account-level/domains/skills.md
13384
+ - eCommerce: https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/skills.md
13385
+ - Events: https://dev.wix.com/docs/api-reference/business-solutions/events/skills.md
13386
+ - Forms: https://dev.wix.com/docs/api-reference/crm/forms/skills.md
13387
+ - Get Paid: https://dev.wix.com/docs/api-reference/business-management/get-paid/skills.md
13388
+ - Google Ads: https://dev.wix.com/docs/api-reference/business-management/marketing/ads/google-ads/skills.md
13389
+ - Google Business Profile: https://dev.wix.com/docs/api-reference/business-management/google-business-profile/skills.md
13390
+ - Google Search Console: https://dev.wix.com/docs/api-reference/business-management/seo/google-search-console/skills.md
13391
+ - Headless: https://dev.wix.com/docs/api-reference/business-management/headless/skills.md
13392
+ - Marketing: https://dev.wix.com/docs/api-reference/business-management/marketing/skills.md
13393
+ - Media: https://dev.wix.com/docs/api-reference/assets/media/skills.md
13394
+ - Pricing Plans: https://dev.wix.com/docs/api-reference/business-solutions/pricing-plans/skills.md
13395
+ - Restaurants: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/skills.md
13396
+ - Rich Content: https://dev.wix.com/docs/api-reference/assets/rich-content/skills.md
13397
+ - SEO: https://dev.wix.com/docs/api-reference/business-management/seo/skills.md
13398
+ - SEO redirects: https://dev.wix.com/docs/api-reference/business-management/seo/redirects/skills.md
13399
+ - Site Properties: https://dev.wix.com/docs/api-reference/business-management/site-properties/skills.md
13400
+ - Sites: https://dev.wix.com/docs/api-reference/account-level/sites/skills.md
13401
+ - Stores: https://dev.wix.com/docs/api-reference/business-solutions/stores/skills.md
13402
+ - Wix MCP: https://dev.wix.com/docs/api-reference/articles/ai-tools/wix-mcp/skills.md
13403
+
13404
+ ### wix-headless-kit
13405
+
13406
+ Builds a Wix Headless site on shipped, verified code instead of writing the integration: each Wix Business Solution comes with a typed data layer, hooks, components, pages and a seed script that are already correct. The data layer ships twice, on \`@wix/sdk\` for the React core and as plain REST calls for a stack that cannot run it (\`guides/reference-mode.md\`), and the agent's work narrows to brand, layout, copy and wiring. It runs as a CLI flow from a shell that reaches Wix, and as Wix API calls through this MCP when it cannot.
13407
+
13408
+ Paths are inside the skill folder; online, each is \`https://www.wix.com/skills/headless-kit/<path>\`.
13409
+ - Before the skill: the cold start installs it, signs in and hands off. It is another skill's file, https://www.wix.com/skills/headless-cold-start/headless-kit.md, and StartHeadlessBuild returns it.
13410
+ - \`SKILL.md\`: the steps of a build and the table of Business Solutions the kit ships.
13411
+ - \`guides/api-run.md\`: the whole run as Wix API calls through this MCP, for an agent whose shell cannot reach Wix or that has no shell.
13412
+ - \`guides/capabilities.md\`: which Wix product owns which feature, and what the kit ships for it.
13413
+ - \`guides/existing-site.md\`: a new frontend for a Wix site that already has its content.
13414
+ - \`guides/migration.md\`: a site moving to code after "Go Headless" in its dashboard.
13415
+ - \`guides/reference-mode.md\`: a stack that cannot run the shipped React code (a static site, a server-rendered app in another language).
13416
+ - \`guides/feedback.md\`: sending the user's building-with-Wix feedback to Wix.
13417
+ - \`install/\`: the bootstrap, setup, deploy and templates scripts, and \`install/pins.json\` with the pinned CLI and templates versions.
13418
+
13419
+ ### wix-headless-templates
13420
+
13421
+ The code wix-headless-kit builds from, one folder per Wix Business Solution: \`storefront\`, \`bookings\`, \`rentals\`, \`blog\`, \`cms\`, \`forms\`, \`members\`, \`events\`, \`restaurants\`, \`donations\`, \`pricing-plans\`, \`portfolio\`, \`faq\`. It holds no flow of its own; wix-headless-kit fetches it and copies, deploys and seeds from it.
13422
+
13423
+ Paths are inside the skill folder; online, each is \`https://www.wix.com/skills/headless-templates/<path>\`. Every Business Solution folder has the same parts, with \`<solution>\` one of the names above:
13424
+ - \`<solution>/INSTRUCTIONS.md\`: the playbook, covering what ships, what you build, and each page's contract.
13425
+ - \`<solution>/app/\`: the typed React core: the data layer under \`wix/<solution>/\`, hooks and components.
13426
+ - \`<solution>/app-astro/\`: the Astro page skeletons for Wix-managed Astro sites (every solution but \`forms\`).
13427
+ - \`<solution>/project/\`: the whole runnable project for that solution.
13428
+ - \`<solution>/rest/\`: the same data layer as plain REST calls, for stacks that cannot run the React core.
13429
+ - \`<solution>/seed/SEED.md\`: how to seed that solution's content, with the script that creates it in the right order.
13430
+
13431
+ Shared by every solution:
13432
+ - \`shared/DESIGN.md\`: the design floor when the brief gives no design direction.
13433
+ - \`shared/CONTENT.md\`: the copy floor for every page written.
13434
+ - \`shared/SUPPLIED-CONTENT.md\`: content the user brings (a CSV, a menu, a folder of photos) as the seed plan.
13435
+ - \`shared/CUSTOM_OPERATIONS.md\`: operations beyond what the shipped code covers.
13436
+ - \`shared/rest/client.ts\`: the REST client the \`rest/\` layers use.
13437
+
13438
+ Online, a solution's folder lists everything it ships: https://www.wix.com/skills/headless-templates/storefront returns the URLs of the store's files, and \`…/storefront/app\` those of its React core. A part is often small enough to read whole in one call: https://www.wix.com/skills/headless-templates/storefront/seed/_content returns the store's seeding guide and scripts together. A whole solution passes the 150K cap, so read it part by part.
13439
+ `;
13115
13440
  var recipesIndexDoc = {
13116
13441
  url: "https://dev.wix.com/docs/api-reference/articles/ai-tools/wix-mcp/skills/wix-management-recipes-index",
13117
13442
  action: "include"
13118
13443
  };
13119
13444
  var README_INTRO = dedent_default`
13120
- <wix-usage-guide>
13445
+ # How to work with Wix through these tools
13121
13446
 
13122
- How to work with Wix through these tools:
13447
+ ## Source of truth
13123
13448
 
13124
- <source-of-truth>
13125
- Do not answer from memory about anything Wix-specific — this covers every part of your answer, not only API calls: any URL, link, ID, setting location, or product fact you give the user must come from the docs tools, the recipes below, a previous tool result in this conversation, or the user's own message (identifiers and URLs the user supplies are fine to use as given). If a recipe in the index below matches what the user asked for, read it before answering. For APIs, use the search, browse, read-docs, and schema tools as the source of truth for the exact URL, HTTP method, request body shape, response shape, field names, required fields, and enum values before calling an API.
13126
- </source-of-truth>
13449
+ Do not answer from memory about anything Wix-specific — this covers every part of your answer, not only API calls: any URL, link, ID, setting location, or product fact you give the user must come from the docs tools, a Wix skill (installed, or read online as the Wix skills section below explains), a previous tool result in this conversation, or the user's own message (identifiers and URLs the user supplies are fine to use as given). When a Wix skill covers what the user asked for, read it before answering. For APIs, use the search, browse, read-docs, and schema tools as the source of truth for the exact URL, HTTP method, request body shape, response shape, field names, required fields, and enum values before calling an API.
13127
13450
 
13128
- <management-flow>
13129
- <goal>
13451
+ ## Goal
13130
13452
  `;
13131
- var README_FLOWS_HEADING = dedent_default`
13132
- </goal>
13453
+ var README_FLOWS_HEADING = "## Wix MCP Flows";
13454
+ var README_FLOWS_SITE = dedent_default`
13455
+ ### The site
13133
13456
 
13134
- ## Wix MCP Site Management Flows
13457
+ The site context below lists the account's sites with their installed apps. Take the \`siteId\` from there; when more than one site fits and the user's words don't pick one, ask which before any read or write. A business solution's API (Stores, Bookings, CMS and the rest) works only on a site with that app installed, and some apps have versions with different endpoints (Wix Stores V1 vs V3): check the site's apps first, and when the app is missing, install it (a Wix skill has the steps) before using its API.
13135
13458
  `;
13136
- var README_BODY = dedent_default`
13137
- ### Discovery & Call Flow
13459
+ var README_FIND_SOURCES = dedent_default`
13460
+ ### Find the method
13138
13461
 
13139
- Common paths (use the highest you can satisfy for each method you'll call):
13140
- - Already have what you need from previous tool results in this conversation (not from memory) → ExecuteWixAPI
13141
- - Pick a matching article from the Recipes index below → ReadFullDocsArticle (find relevant code examples) → (probe SearchWixAPISpec for response shapes if needed) → ExecuteWixAPI
13142
- - SearchWixRESTDocumentation or BrowseWixRESTDocsMenu → ReadFullDocsArticle (find relevant code examples) → (probe SearchWixAPISpec for response shapes if needed) → ExecuteWixAPI
13143
- - SearchWixRESTDocumentation or BrowseWixRESTDocsMenu → ReadFullDocsArticle (no usable example) → SearchWixAPISpec for request schemas → ExecuteWixAPI
13462
+ A task may need several API methods. For each, take the first source that gives it:
13463
+ - A previous tool result in this conversation (not memory) already shows its request: go straight to the call.
13464
+ - A Wix skill that covers the task (the Wix skills section below says what each is for): follow it, from disk or online. When its example for a call matches your case, that is the source: pass its docs URL in \`sourceDocUrls\` instead of searching for the method pages (when it has no docs URL, one SearchWixRESTDocumentation call on its title finds the article).
13465
+ - The docs: SearchWixRESTDocumentation (semantic search) or BrowseWixRESTDocsMenu (menu hierarchy), then ReadFullDocsArticle on the method's article for its code examples. For SDK code, the CLI, a headless site or an app, start from those docs instead: SearchWixSDKDocumentation, SearchWixCLIDocumentation, SearchWixHeadlessDocumentation, SearchBuildAppsDocumentation.
13144
13466
 
13145
- Fallbacks: use ReadFullDocsMethodSchema in place of SearchWixAPISpec when SearchWixAPISpec is unavailable or didn't return enough detail; use CallWixSiteAPI in place of ExecuteWixAPI when a single REST call is enough and code adds no value.
13467
+ Keep each method's docs URL even when no source gives a usable example: the shape comes from its schema, as the next section says.
13468
+ `;
13469
+ var README_FLOWS_BODY = dedent_default`
13470
+ ### The exact shape
13146
13471
 
13147
- Tools (relevant for the management flow):
13148
- - SearchWixRESTDocumentation — semantic search over the Wix REST docs by keywords or natural-language queries.
13149
- - BrowseWixRESTDocsMenu — navigate the Wix REST docs by menu hierarchy; alternative to keyword search.
13150
- - ReadFullDocsArticle — fetch a full docs article by its docs URL; returns prose, fields, and code examples.
13151
- - SearchWixAPISpec — JS-sandboxed query over the REST API spec; returns request schema, response schema, and the executable \`method.publicUrl\`.
13152
- - ExecuteWixAPI — runs an async JS function in a sandbox with \`wix.request()\`; a single invocation can chain multiple API calls, paginate, loop, transform, and branch.
13472
+ A code example is the model for your call only when it matches your case, with every field your case needs. Fill the rest from the method's schema: SearchWixAPISpec on its docs URL (REST or SDK) returns the request and response schemas, \`method.publicUrl\` and \`method.sdk\`; ReadFullDocsMethodSchema when SearchWixAPISpec isn't available. A query's filter and sort fields and operators are a closed list per method: use only what its schema declares.
13153
13473
 
13154
- Steps:
13474
+ None of this comes from memory.
13155
13475
 
13156
- 1. A single user task may need one or more API methods. For each method you'll call, find a code example that fits the user's intent — you'll model the API call (URL, method, body) on it. An example only "fits" if it is **complete** (not truncated) AND matches your exact case — same operation and same variant/shape (e.g. the same \`productType\`), with every field your case needs actually shown. **If the closest example is truncated (it shows a \`... N more lines ...\` marker), do NOT call the API off it; continue discovery first: \`ReadFullDocsArticle\` for the complete example, or \`SearchWixAPISpec\` for the exact request schema.** Never fill in required fields from memory. Skip this step for any method you already have from a previous tool result in this conversation (not from memory):
13157
- a. First try the WixREADME Recipes index below (pre-curated guides for common tasks). If a recipe matches, ReadFullDocsArticle on its docs URL to find relevant code examples; many recipes cover multi-step flows and supply code examples for several methods in one article.
13158
- b. If no recipe matches the user's intent, or the matched recipe didn't yield the code examples you need, discover each missing method's docs URL via SearchWixRESTDocumentation (semantic search) or BrowseWixRESTDocsMenu (menu hierarchy), then ReadFullDocsArticle on each to find relevant code examples.
13159
- c. If neither path turns up a usable code example for some method, continue without one for that method — step 2 will derive its request directly from the schema. Make sure you've still captured the docs URL of each method.
13476
+ ### The call, or the code that makes it
13160
13477
 
13161
- 2. Before calling, fill any remaining gaps with SearchWixAPISpec on each method's docs URL. It returns both request and response schemas. For each method, check:
13162
- - **Request** (URL, HTTP method, body fields) — skip the probe if a code example already shows them; otherwise SearchWixAPISpec returns publicUrl + exact request shape.
13163
- - **Response shape** (field paths your code will read) — only relevant if your code reads response fields; skip the probe if the article(s) already made it clear; otherwise probe SearchWixAPISpec for the response schema.
13164
- - **Filter and sort** (query/search methods only) — an example proves only the fields and operators it uses. Each method declares which fields it can filter and sort by, and with which operators; that list is closed for fields and their operators and covers that method only — \`$and\`/\`$or\`/\`$not\` are filter syntax and never appear in it. Anything else errors or silently returns the wrong rows. A listed operator is valid on its own, since a field takes a single operator; to combine conditions, use the logical operators instead — \`$and\` and \`$or\` take an array of expressions, \`$not\` takes one, and they can nest.
13165
- - Never guess any of this from memory.
13478
+ **Call it with ExecuteWixAPI**: one async JS function with \`wix.request()\`, holding every step of the task that code can resolve. The reads that fetch an ID, check whether a record exists or list items to choose from go in the same function as the writes they feed; step numbers in a guide or skill give logical order, not separate calls. Set \`hasMutations: true\` when any step writes. Return the write's response as is and report from it; don't read again to confirm. A new invocation only for what code can't resolve: the user's answer, or a job that finishes later. One function chains calls, paginates, lists all, loops, transforms and branches. Without ExecuteWixAPI, make each call with CallWixSiteAPI (a site's APIs) or ManageWixSite (the account's), from the same exact shape.
13166
13479
 
13167
- 3. Before calling: collapse ALL planned steps — preliminary reads AND mutations — into a single async function. Step numbers in any guide or skill doc describe logical order for human comprehension — they do NOT define invocation boundaries. Steps that feed into each other belong in a single invocation: read, use the result, write, and return. A read that fetches IDs or checks existence before a create/update is part of that mutation's invocation, not a separate preceding call. This includes any read whose result feeds directly into a subsequent write — fetching a required ID, checking whether a record already exists, listing items to choose one — all belong in the same invocation as the write that follows. Set hasMutations: true whenever any step in the function writes. Return the mutation response directly — do not follow up with a read to confirm what was just created or updated; trust the response. Only split into multiple invocations when a later step genuinely depends on information you cannot resolve in code (e.g. a user decision or an out-of-band event). A single invocation can chain several related calls, paginate, "list all", loop, transform, and branch on responses.
13480
+ **Write the code** when the task is code the user keeps: an app (built and run with the Wix CLI), a headless site, or a script that calls the REST API or the JavaScript SDK. The path is the same: find the method, learn its exact shape, then write the call from \`method.publicUrl\` (REST) or \`method.sdk\` (the JavaScript SDK), never from memory. Every way of building against Wix in code is this path; the headless skills are a shortcut on it, not a different one. wix-headless-kit builds a Wix Headless site from shipped, verified code (wix-headless-templates holds it, one folder per business solution: a \`@wix/sdk\` React core, and the same data layer as plain REST calls for a stack that cannot run it): scaffold with the CLI, deploy, seed, design the presentation on the shipped code, release. What no shipped solution covers is written from the docs, as above. A new site is written in code and uploaded: StartDeployWebsite returns the upload guide (create the site, upload its files, change them later); StartHeadlessBuild returns the headless guide, for a site that needs Wix business features: you write the site, and the guide creates it, connects the features and uploads it, through the cold start that hands off to the kit (or the same run as API calls when there is no shell).
13168
13481
 
13169
- </management-flow>
13482
+ ### When a call fails
13170
13483
 
13171
- <other-uses>
13172
- Beyond managing user sites, the Wix MCP tools can be used for any other purpose they support — for example, reading Wix SDK docs to write code against the SDK, using the Wix CLI to create apps, answering documentation questions, and so on.
13173
- </other-uses>
13174
- </wix-usage-guide>
13484
+ The error carries the failed call's status and message and a Wix request id. Read it: it says what was wrong, and that is what to fix before calling again, whether the request (from the schema), the site and what is installed on it, or what the account is allowed to do. Never resend the same request unchanged, and never guess a fix. When you report the failure to the user, include the request id.
13175
13485
  `;
13176
- var defaultReadmeDocs = [
13177
- {
13178
- action: "text",
13179
- content: [
13180
- README_INTRO,
13181
- AGENT_GOAL,
13182
- README_FLOWS_HEADING,
13183
- "",
13184
- README_BODY
13185
- ].join("\n")
13186
- },
13187
- recipesIndexDoc
13188
- ];
13486
+ function buildReadmeDocs({
13487
+ recipesIndex = true
13488
+ } = {}) {
13489
+ return [
13490
+ {
13491
+ action: "text",
13492
+ content: [
13493
+ README_INTRO,
13494
+ "",
13495
+ AGENT_GOAL,
13496
+ "",
13497
+ README_FLOWS_HEADING,
13498
+ "",
13499
+ README_FLOWS_SITE,
13500
+ "",
13501
+ README_FIND_SOURCES,
13502
+ "",
13503
+ README_FLOWS_BODY
13504
+ ].join("\n")
13505
+ },
13506
+ { action: "text", content: WIX_SKILLS_README_SECTION },
13507
+ ...recipesIndex ? [recipesIndexDoc] : []
13508
+ ];
13509
+ }
13510
+ var defaultReadmeDocs = buildReadmeDocs();
13189
13511
  function mergeConfig(base, overrides) {
13190
13512
  return {
13191
13513
  toolDescriptions: {
@@ -13224,7 +13546,7 @@ var defaultConfig = {
13224
13546
  var widgetSiteCreationConfig = {
13225
13547
  toolDescriptions: Object.fromEntries(
13226
13548
  [
13227
- "WixSiteBuilder",
13549
+ "WixHarmonyEditorBuild",
13228
13550
  "WixSiteCreation",
13229
13551
  "SearchSiteTemplates",
13230
13552
  "CreateSiteFromTemplate"
@@ -13371,6 +13693,9 @@ function renderApps(apps, knownApps) {
13371
13693
  if (app.catalogVersion) {
13372
13694
  line += ` \u2014 Catalog app version: **${app.catalogVersion}**`;
13373
13695
  }
13696
+ if (app.invoicesVersion) {
13697
+ line += ` \u2014 Invoices app version: **${app.invoicesVersion}**`;
13698
+ }
13374
13699
  lines.push(line);
13375
13700
  }
13376
13701
  return lines.join("\n");
@@ -13391,6 +13716,11 @@ function renderCustomAppsWithSkillsTools(apps) {
13391
13716
  }
13392
13717
  return lines.join("\n");
13393
13718
  }
13719
+ function renderCompanionApp(installedApps) {
13720
+ const appId = installedApps.find((app) => app.companionApp === true)?.appId;
13721
+ if (!appId || !GUID_PATTERN.test(appId)) return "";
13722
+ return `The site's code lives in its companion app (ID: \`${appId}\`).`;
13723
+ }
13394
13724
  function renderProperties(props) {
13395
13725
  const sections = [];
13396
13726
  const regionalFormat = [];
@@ -13417,7 +13747,7 @@ ${contact.join("\n")}`);
13417
13747
  }
13418
13748
  return sections.join("\n\n");
13419
13749
  }
13420
- function renderSite(site, index, knownApps) {
13750
+ function renderSite(site, index, knownApps, includeCompanionApp) {
13421
13751
  const lines = [];
13422
13752
  const heading = site.displayName ?? `Site ${index}`;
13423
13753
  lines.push(`## ${index}. ${heading}`);
@@ -13462,6 +13792,13 @@ function renderSite(site, index, knownApps) {
13462
13792
  lines.push("");
13463
13793
  lines.push(customAppsWithSkillsToolsContent);
13464
13794
  }
13795
+ const companionAppContent = includeCompanionApp ? renderCompanionApp(site.installedApps ?? []) : "";
13796
+ if (companionAppContent) {
13797
+ lines.push("");
13798
+ lines.push("### Site code (companion app)");
13799
+ lines.push("");
13800
+ lines.push(companionAppContent);
13801
+ }
13465
13802
  lines.push("");
13466
13803
  lines.push("---");
13467
13804
  lines.push("");
@@ -13481,6 +13818,7 @@ function enrichDynamicContext(data, knownApps = {}, options = {}) {
13481
13818
  const response = snakeToCamel(parsed);
13482
13819
  const includePartnerContext = options.includePartnerContext ?? false;
13483
13820
  const includeAccountContext = options.includeAccountContext ?? false;
13821
+ const includeCompanionApp = options.includeCompanionApp ?? false;
13484
13822
  const sections = [];
13485
13823
  const allSites = response.sites ?? [];
13486
13824
  const maxSites = options.maxSites ?? Infinity;
@@ -13533,9 +13871,18 @@ function enrichDynamicContext(data, knownApps = {}, options = {}) {
13533
13871
  );
13534
13872
  sections.push("");
13535
13873
  }
13874
+ const hasInvoicesVersion = sitesToRender.some(
13875
+ (site) => site.installedApps?.some((app) => app.appId && knownApps[app.appId] && app.invoicesVersion)
13876
+ );
13877
+ if (hasInvoicesVersion) {
13878
+ sections.push(
13879
+ "> **Wix Invoices \u2014 Version**: Some sites below have Wix Invoices installed and show their Invoices version (V1/V2). When working on an Invoices task, check the version for that site \u2014 only read recipes and call endpoints that match its version. Never use an Invoices V2 skill or API on a V1 site, and vice versa."
13880
+ );
13881
+ sections.push("");
13882
+ }
13536
13883
  if (sitesToRender.length) {
13537
13884
  for (let i = 0; i < sitesToRender.length; i++) {
13538
- sections.push(renderSite(sitesToRender[i], i + 1, knownApps));
13885
+ sections.push(renderSite(sitesToRender[i], i + 1, knownApps, includeCompanionApp));
13539
13886
  }
13540
13887
  }
13541
13888
  if (!response.account && !siteCount && (!includePartnerContext || !hasPartnerContext(response.partnerContext))) {
@@ -13543,9 +13890,11 @@ function enrichDynamicContext(data, knownApps = {}, options = {}) {
13543
13890
  }
13544
13891
  return sections.join("\n");
13545
13892
  }
13893
+ var GUID_PATTERN;
13546
13894
  var init_enrich = __esm2({
13547
13895
  "src/enrich.ts"() {
13548
13896
  "use strict";
13897
+ GUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
13549
13898
  }
13550
13899
  });
13551
13900
  init_enrich();
@@ -13608,6 +13957,7 @@ async function getDynamicContext(auth, httpClient, filter, options) {
13608
13957
  } : options;
13609
13958
  const includePartnerContext = contextOptions?.partnerContextEnabled ?? true;
13610
13959
  const includeAccountContext = contextOptions?.includeAccountContext ?? true;
13960
+ const includeCompanionApp = contextOptions?.includeCompanionApp ?? false;
13611
13961
  try {
13612
13962
  const accountHeaders = await auth.getAccountAuthHeaders();
13613
13963
  const noFilter = !filter?.siteId && !filter?.siteName;
@@ -13661,6 +14011,7 @@ async function getDynamicContext(auth, httpClient, filter, options) {
13661
14011
  const context = enrichDynamicContext2(response.data, known_apps_default, {
13662
14012
  includePartnerContext,
13663
14013
  includeAccountContext,
14014
+ includeCompanionApp,
13664
14015
  // Cap the account-wide view to the first N sites; a targeted fetch returns one site.
13665
14016
  ...noFilter ? { maxSites: MAX_SITES_FOR_DYNAMIC_CONTEXT, totalSiteCount } : {}
13666
14017
  });
@@ -13714,6 +14065,21 @@ function isApiUrlAllowed(url) {
13714
14065
  }
13715
14066
 
13716
14067
  // src/config/param-descriptions.ts
14068
+ var REPORT_ISSUE_TO_WIX_PARAMS = {
14069
+ message: [
14070
+ "The report of one issue, sent to Wix as free text. Only this issue \u2014 no session summary, play-by-play or unrelated problems:",
14071
+ "- What was being attempted, in a sentence.",
14072
+ "- The tool or endpoint, the HTTP status and the error message.",
14073
+ "- What you expected instead, and the workaround if any.",
14074
+ "- Where the fault likely lives (API behavior / API schema / docs / Wix tooling / unsure), and whether you confirmed it.",
14075
+ "- The context needed to reproduce it: agent/model, site id, the Wix area or API.",
14076
+ "- The user's own words, verbatim, if they added any.",
14077
+ "NEVER include secrets \u2014 no bearer tokens, API keys, or credentials \u2014 and no personal data beyond what the report needs."
14078
+ ].join("\n"),
14079
+ requestId: "Optional request id (x-wix-request-id) of the failed Wix API call this report is about."
14080
+ };
14081
+ var WHY_NOT_EXECUTE_RECONNECT = "if the Wix MCP doesn't offer it at all, say so here, and tell the user once that their Wix connection may be an old or cached version that a reconnect refreshes.";
14082
+ var WHY_NOT_EXECUTE_NO_EXECUTE = "if it isn't in your tool list, say so.";
13717
14083
  var paramDescriptions = {
13718
14084
  CallWixSiteAPI: {
13719
14085
  siteId: "The id of the site selected using site selection tool",
@@ -13730,6 +14096,7 @@ var paramDescriptions = {
13730
14096
  WQL_FILTER_RULE
13731
14097
  ].join("\n"),
13732
14098
  reason: "One sentence explaining the original user request and why you are calling this API to complete it.",
14099
+ whyNotExecuteWixAPI: `One sentence: why this call goes through CallWixSiteAPI and not ExecuteWixAPI, the preferred tool. If ExecuteWixAPI isn't loaded, load it through your client's tool search before calling this tool; ${WHY_NOT_EXECUTE_RECONNECT}`,
13733
14100
  sourceDocUrl: [
13734
14101
  "The URL of the documentation or recipe where you found this API endpoint.",
13735
14102
  "MAKE SURE THE ENDPOINT URL IS REALLY THERE AND YOUR ARE NOT GUESSING IT !!!",
@@ -13765,18 +14132,9 @@ var paramDescriptions = {
13765
14132
  'Use ["other"] ONLY IF YOU HAVE A VERY GOOD REASON TO DO SO'
13766
14133
  ].join("\n")
13767
14134
  },
13768
- SupportAndFeedback: {
13769
- message: [
13770
- "The composed feedback message to send to Wix. Wix receives only free text, so a bare sentence is low-signal \u2014 send a summary of the whole session, not just the last error. Structure it in three layers:",
13771
- "1. Provenance \u2014 labelled lines (omit ones that do not apply): agent/model, Wix tooling used, Wix areas/APIs touched, Wix products (stores, bookings, ...), site id, relevant links (dashboard, docs pages read, live site) and other ids created this run.",
13772
- `2. Narrative \u2014 "In the user's words": anything the user added when you offered, quoted verbatim (redact secrets only). Then user intent + run summary (what they set out to build, what worked vs. what fought back), a condensed play-by-play of the session (key exchanges, tool calls, decisions), and the friction points \u2014 every place something got in the way, each with specifics: the step/endpoint, HTTP status and error message, the gap, the workaround, and what you expected instead. Include issues you recovered from.`,
13773
- '3. Attribution \u2014 for each significant friction point, your best diagnosis of where the fault lives: API behavior / API schema / API reference docs / docs articles or examples / Wix tooling (MCP tools, auth flow) / other-unsure \u2014 plus whether you confirmed it or are assuming. Do not force a tag; "unsure" beats a wrong route.',
13774
- "End with a bottom line: one or two sentences naming the single most important problem and its impact \u2014 a concise engineering assessment, not softened or hedged.",
13775
- "Write it as a scannable few paragraphs or a short bulleted list \u2014 thorough on the friction, distilled rather than a transcript dump.",
13776
- "NEVER include secrets \u2014 no bearer tokens, API keys, or credentials \u2014 and no personal data beyond what the feedback needs."
13777
- ].join("\n"),
13778
- requestId: "Optional request id (x-wix-request-id) returned from a failed Wix API call this feedback relates to."
13779
- },
14135
+ ReportIssueToWix: REPORT_ISSUE_TO_WIX_PARAMS,
14136
+ // The tool's former name, kept for importers that read it by that key.
14137
+ SupportAndFeedback: REPORT_ISSUE_TO_WIX_PARAMS,
13780
14138
  SearchWixWDSDocumentation: {
13781
14139
  searchTerm: "The search term to search for in the Wix Design System Documentation",
13782
14140
  maxResults: "The maximum number of results to return, default is 10, max is 15"
@@ -13815,8 +14173,13 @@ var paramDescriptions = {
13815
14173
  maxResults: "The maximum number of results to return, default is 10, max is 15",
13816
14174
  reason: "One sentence describing the original user request and the task you are trying to accomplish with this search."
13817
14175
  },
14176
+ SearchWixHelpCenter: {
14177
+ searchTerm: 'What the site owner wants to do or fix, in plain words (e.g. "connect a domain bought elsewhere", "cancel a Premium plan")',
14178
+ maxResults: "The maximum number of articles to return (1\u201315, default 15). Each article is returned with up to 60 lines.",
14179
+ reason: "One sentence describing the original user request and the task you are trying to accomplish with this search."
14180
+ },
13818
14181
  ReadFullDocsArticle: {
13819
- articleUrl: "The URL of the docs article or method article to fetch. Should be something like https://dev.wix.com/docs/.../... For REST docs, use the URL as-is. For SDK docs, the URL SHOULD include the query param ?apiView=SDK (e.g. https://dev.wix.com/docs/...?apiView=SDK). Wix skills pages (https://www.wix.com/skills/..., https://dev.wix.com/docs/skills/...) and Wix Headless agent pages (https://www.wix.com/headless/..., e.g. https://www.wix.com/headless/skill.md) are read as-is, including their referenced files."
14182
+ articleUrl: "The URL of the docs article or method article to fetch. Should be something like https://dev.wix.com/docs/.../... For REST docs, use the URL as-is. For SDK docs, the URL SHOULD include the query param ?apiView=SDK (e.g. https://dev.wix.com/docs/...?apiView=SDK). Wix Help Center articles (https://support.wix.com/en/article/...) are read as-is. Wix skills pages (https://www.wix.com/skills/..., https://dev.wix.com/docs/skills/...) and Wix Headless agent pages (https://www.wix.com/headless/..., e.g. https://www.wix.com/headless/skill.md) are read as-is, including their referenced files. Source links (https://dev.wix.com/rawdocs/api/get-source-content?repo=...&path=...) found in skills and recipes are read as-is."
13820
14183
  },
13821
14184
  ReadFullDocsMethodSchema: {
13822
14185
  articleUrl: "The URL of the documentation to fetch. Should be something like https://dev.wix.com/docs/.../... For REST docs, use the URL as-is. For SDK docs, the URL SHOULD include the query param ?apiView=SDK (e.g. https://dev.wix.com/docs/...?apiView=SDK).",
@@ -13832,7 +14195,7 @@ var paramDescriptions = {
13832
14195
  },
13833
14196
  UploadImageToWixSite: {
13834
14197
  siteId: "The ID (UUID) of the Wix site to upload to. REQUIRED. If the user has not clearly specified which site, ASK them which site to upload to (or use ListWixSites to help them pick) BEFORE calling this tool. NEVER call this tool with a missing, empty, placeholder, or guessed siteId \u2014 ask first instead.",
13835
- image: "Array of file ATTACHMENTS: files the user uploaded in the chat, or files you created in your own file sandbox. Each item requires download_url and file_id, and may optionally include file_name and mime_type \u2014 the platform resolves a file passed here into these fields automatically, so just include the files. For plain image URLs the user pasted, use imageUrls instead. ALWAYS pass ALL attachments together in a single call \u2014 NEVER call this tool once per image. Do NOT use together with imageBase64.",
14198
+ image: `${fileParamNote("send the image as imageBase64 (the whole file, one per call) or as a public URL in imageUrls")} For plain image URLs the user pasted, use imageUrls instead. ALWAYS pass ALL files together in a single call \u2014 NEVER call this tool once per image. Do NOT use together with imageBase64.`,
13836
14199
  imageUrls: "Array of publicly accessible image URLs (e.g. links the user pasted or URLs produced by other tools). Use this for plain URLs that are NOT chat attachments \u2014 no file_id is needed. ALWAYS pass ALL URLs together in a single call. Can be combined with image; do NOT use together with imageBase64.",
13837
14200
  imageBase64: 'Base64-encoded image data. Use when the client can read a file and encode it as base64. May include a data URL prefix (e.g. "data:image/jpeg;base64,...") or be raw base64. Do NOT use together with image.',
13838
14201
  mimeType: 'MIME type of the image (e.g. "image/jpeg", "image/png", "image/webp"). Required when imageBase64 does not include a data URL prefix.',
@@ -13869,10 +14232,315 @@ var paramDescriptions = {
13869
14232
  };
13870
14233
  var desc = (toolName) => paramDescriptions[toolName];
13871
14234
 
14235
+ // src/docs/mintlify-docs.ts
14236
+ var MINTLIFY_LINKS_DOCS_SITE = "mintlify-links";
14237
+ var MINTLIFY_DOCS_SITE = "mintlify";
14238
+ var MINTLIFY_HOST = "https://wix-internal.mintlify.site";
14239
+ var MINTLIFY_HOSTNAMES = /* @__PURE__ */ new Set([
14240
+ "wix-internal.mintlify.site",
14241
+ "wix-internal.mintlify.app"
14242
+ ]);
14243
+ var DEV_WIX_DOCS = "https://dev.wix.com/docs";
14244
+ var PORTAL_SLUG_TO_TAB = {
14245
+ rest: "api-reference",
14246
+ "mintlify-sandbox": "sandbox"
14247
+ };
14248
+ var TAB_TO_PORTAL_SLUG = {
14249
+ sandbox: "mintlify-sandbox"
14250
+ };
14251
+ var MINTLIFY_TABS = /* @__PURE__ */ new Set(["api-reference", "sandbox", "kb"]);
14252
+ var NON_DOCS_SLUGS = /* @__PURE__ */ new Set(["skills", "kb-only", "api"]);
14253
+ var CHECK_TTL_MS = 10 * 60 * 1e3;
14254
+ var MAX_CHECK_ENTRIES = 5e3;
14255
+ var CHECK_TIMEOUT_MS = 5e3;
14256
+ var MAX_REDIRECTS = 3;
14257
+ var CHECK_CONCURRENCY = 8;
14258
+ function isMintlifyDocsSite(docsSite) {
14259
+ return docsSite === MINTLIFY_LINKS_DOCS_SITE || docsSite === MINTLIFY_DOCS_SITE;
14260
+ }
14261
+ var MINTLIFY_DOCUMENT_TYPES = {
14262
+ REST: ["MINTLIFY_REST"],
14263
+ // includes the skills, as REST + SKILLS does today
14264
+ SDK: ["MINTLIFY_SDK"]
14265
+ };
14266
+ function mintlifyDocumentTypes(docsSite, toolName) {
14267
+ return docsSite === MINTLIFY_DOCS_SITE ? MINTLIFY_DOCUMENT_TYPES[toolName] : void 0;
14268
+ }
14269
+ function isMintlifyUrl(url) {
14270
+ try {
14271
+ return MINTLIFY_HOSTNAMES.has(new URL(url).hostname);
14272
+ } catch {
14273
+ return false;
14274
+ }
14275
+ }
14276
+ var MINTLIFY_URL_PREFIXES = [...MINTLIFY_HOSTNAMES].map(
14277
+ (host) => `https://${host}/`
14278
+ );
14279
+ function cleanPath(pathname) {
14280
+ const path = pathname.replace(/\.md$/, "").replace(/\/+$/, "");
14281
+ return path === "" ? "/" : path;
14282
+ }
14283
+ function flavourOf(url) {
14284
+ return url.searchParams.get("apiView")?.toUpperCase() === "SDK" ? "sdk" : "rest";
14285
+ }
14286
+ function toMintlifyCandidate(rawUrl) {
14287
+ let url;
14288
+ try {
14289
+ url = new URL(rawUrl);
14290
+ } catch {
14291
+ return null;
14292
+ }
14293
+ if (MINTLIFY_HOSTNAMES.has(url.hostname)) {
14294
+ const path2 = cleanPath(url.pathname);
14295
+ return { url: `${MINTLIFY_HOST}${path2}`, path: path2, flavour: flavourOf(url) };
14296
+ }
14297
+ if (url.hostname !== "dev.wix.com" || !url.pathname.startsWith("/docs/")) {
14298
+ return null;
14299
+ }
14300
+ const segments = cleanPath(url.pathname.slice("/docs".length)).split("/").filter(Boolean);
14301
+ if (segments.length === 0 || NON_DOCS_SLUGS.has(segments[0])) {
14302
+ return null;
14303
+ }
14304
+ segments[0] = PORTAL_SLUG_TO_TAB[segments[0]] ?? segments[0];
14305
+ const path = `/${segments.join("/")}`;
14306
+ return { url: `${MINTLIFY_HOST}${path}`, path, flavour: flavourOf(url) };
14307
+ }
14308
+ function toDevWixUrl(rawUrl) {
14309
+ const url = new URL(rawUrl);
14310
+ const segments = cleanPath(url.pathname).split("/").filter(Boolean);
14311
+ if (segments.length > 0) {
14312
+ segments[0] = TAB_TO_PORTAL_SLUG[segments[0]] ?? segments[0];
14313
+ }
14314
+ const query = flavourOf(url) === "sdk" ? "?apiView=SDK" : "";
14315
+ return `${DEV_WIX_DOCS}/${segments.join("/")}${query}`;
14316
+ }
14317
+ var checkCache = /* @__PURE__ */ new Map();
14318
+ function rememberCheck(path, result) {
14319
+ const now = Date.now();
14320
+ checkCache.delete(path);
14321
+ if (checkCache.size >= MAX_CHECK_ENTRIES) {
14322
+ for (const [key, entry] of checkCache) {
14323
+ if (entry.expires <= now) checkCache.delete(key);
14324
+ }
14325
+ }
14326
+ while (checkCache.size >= MAX_CHECK_ENTRIES) {
14327
+ const oldest = checkCache.keys().next().value;
14328
+ if (oldest === void 0) break;
14329
+ checkCache.delete(oldest);
14330
+ }
14331
+ checkCache.set(path, { result, expires: now + CHECK_TTL_MS });
14332
+ }
14333
+ async function headOnce(url) {
14334
+ return fetch(url, {
14335
+ method: "HEAD",
14336
+ redirect: "manual",
14337
+ signal: AbortSignal.timeout(CHECK_TIMEOUT_MS)
14338
+ });
14339
+ }
14340
+ async function checkMintlifyPage(path) {
14341
+ const cached = checkCache.get(path);
14342
+ if (cached && cached.expires > Date.now()) {
14343
+ return cached.result;
14344
+ }
14345
+ if (cached) checkCache.delete(path);
14346
+ let result;
14347
+ try {
14348
+ let current = path;
14349
+ let hops = 0;
14350
+ for (; ; ) {
14351
+ const response = await headOnce(`${MINTLIFY_HOST}${current}`);
14352
+ if (response.status === 200) {
14353
+ result = {
14354
+ outcome: hops === 0 ? "identity" : "redirect",
14355
+ path: current
14356
+ };
14357
+ break;
14358
+ }
14359
+ const location = response.headers.get("location");
14360
+ if (response.status >= 300 && response.status < 400 && location && hops < MAX_REDIRECTS) {
14361
+ current = cleanPath(new URL(location, MINTLIFY_HOST).pathname);
14362
+ hops++;
14363
+ continue;
14364
+ }
14365
+ result = { outcome: response.status === 404 ? "missing" : "error" };
14366
+ break;
14367
+ }
14368
+ } catch (error) {
14369
+ logger.error(`[mintlify] page check failed for ${path}: ${error}`);
14370
+ result = { outcome: "error" };
14371
+ }
14372
+ rememberCheck(path, result);
14373
+ return result;
14374
+ }
14375
+ async function resolveDocsLink(rawUrl, { verify = true } = {}) {
14376
+ const candidate = toMintlifyCandidate(rawUrl);
14377
+ if (!candidate) {
14378
+ return { outcome: "not-docs", url: rawUrl };
14379
+ }
14380
+ const tab = candidate.path.split("/")[1];
14381
+ if (!MINTLIFY_TABS.has(tab)) {
14382
+ return { outcome: "other-portal", url: rawUrl };
14383
+ }
14384
+ const query = candidate.flavour === "sdk" ? "?apiView=SDK" : "";
14385
+ if (!verify) {
14386
+ return { outcome: "identity", url: `${candidate.url}${query}` };
14387
+ }
14388
+ const check = await checkMintlifyPage(candidate.path);
14389
+ if (check.path) {
14390
+ return {
14391
+ outcome: check.outcome,
14392
+ url: `${MINTLIFY_HOST}${check.path}${query}`
14393
+ };
14394
+ }
14395
+ return { outcome: check.outcome, url: rawUrl };
14396
+ }
14397
+ var DOCS_LINK_PATTERN = /https:\/\/(?:dev\.wix\.com\/docs|wix-internal\.mintlify\.(?:site|app))\/[^\s)\]"'<>`]*/g;
14398
+ function trimTrailingPunctuation(url) {
14399
+ return url.replace(/[.,;:!?*]+$/, "");
14400
+ }
14401
+ var MINTLIFY_LINK_PATTERN = /https:\/\/wix-internal\.mintlify\.(?:site|app)\/[^\s)\]"'<>`]*/g;
14402
+ function toDevWixHostText(text) {
14403
+ return text.replace(MINTLIFY_LINK_PATTERN, (match) => {
14404
+ const link = trimTrailingPunctuation(match);
14405
+ let url;
14406
+ try {
14407
+ url = new URL(link);
14408
+ } catch {
14409
+ return match;
14410
+ }
14411
+ if (!MINTLIFY_TABS.has(url.pathname.split("/")[1] ?? "")) return match;
14412
+ return toDevWixUrl(link) + url.hash + match.slice(link.length);
14413
+ });
14414
+ }
14415
+ async function mapWithConcurrency(items, limit, fn) {
14416
+ const results = new Array(items.length);
14417
+ let next = 0;
14418
+ const workers = Array.from(
14419
+ { length: Math.min(limit, items.length) },
14420
+ async () => {
14421
+ while (next < items.length) {
14422
+ const index = next++;
14423
+ results[index] = await fn(items[index]);
14424
+ }
14425
+ }
14426
+ );
14427
+ await Promise.all(workers);
14428
+ return results;
14429
+ }
14430
+ async function rewriteDocsLinks(text, { verify = true, label = "text" } = {}) {
14431
+ const found = [
14432
+ ...new Set(
14433
+ (text.match(DOCS_LINK_PATTERN) ?? []).map(trimTrailingPunctuation)
14434
+ )
14435
+ ];
14436
+ const stats = {
14437
+ links: found.length,
14438
+ byOutcome: {},
14439
+ misses: []
14440
+ };
14441
+ if (found.length === 0) {
14442
+ return { text, stats };
14443
+ }
14444
+ const resolved = await mapWithConcurrency(
14445
+ found,
14446
+ CHECK_CONCURRENCY,
14447
+ (url) => resolveDocsLink(url, { verify })
14448
+ );
14449
+ const replacements = /* @__PURE__ */ new Map();
14450
+ found.forEach((url, i) => {
14451
+ const { outcome, url: target } = resolved[i];
14452
+ stats.byOutcome[outcome] = (stats.byOutcome[outcome] ?? 0) + 1;
14453
+ if (outcome === "identity" || outcome === "redirect") {
14454
+ if (target !== url) replacements.set(url, target);
14455
+ } else if (outcome !== "not-docs") {
14456
+ stats.misses.push(`${outcome} ${url}`);
14457
+ }
14458
+ });
14459
+ const rewritten = text.replace(DOCS_LINK_PATTERN, (match) => {
14460
+ const url = trimTrailingPunctuation(match);
14461
+ const target = replacements.get(url);
14462
+ return target ? target + match.slice(url.length) : match;
14463
+ });
14464
+ logger.log(
14465
+ `[mintlify] ${label}: ${stats.links} links ${JSON.stringify(stats.byOutcome)}` + (stats.misses.length ? ` misses: ${stats.misses.slice(0, 10).join(" | ")}` : "")
14466
+ );
14467
+ return { text: rewritten, stats };
14468
+ }
14469
+ var INDEX_BANNER = /^> ## Documentation Index\n(?:>.*\n)*\n?/;
14470
+ var INLINED_SNIPPET = /^export const \w+ = [\s\S]*?^};\n/gm;
14471
+ var REST_TAB = /<Tab title="REST">([\s\S]*?)<\/Tab>/;
14472
+ var SDK_TAB = /<Tab title="SDK">([\s\S]*?)<\/Tab>/;
14473
+ function cleanMintlifyMarkdown(markdown, flavour) {
14474
+ const text = markdown.replace(INLINED_SNIPPET, "");
14475
+ const rest = text.match(REST_TAB);
14476
+ const sdk = text.match(SDK_TAB);
14477
+ if (!rest || !sdk || (text.match(/<Tab title=/g) ?? []).length !== 2) {
14478
+ return text;
14479
+ }
14480
+ const start = text.lastIndexOf("<Tabs>", rest.index);
14481
+ const end = text.indexOf("</Tabs>", sdk.index);
14482
+ if (start === -1 || end === -1) {
14483
+ return text;
14484
+ }
14485
+ const kept = (flavour === "sdk" ? sdk : rest)[1];
14486
+ const note = flavour === "sdk" ? "_This is the SDK view. Drop `?apiView=SDK` from the URL for the REST view._" : "_This is the REST view. Add `?apiView=SDK` to the URL for the JavaScript SDK view._";
14487
+ return `${text.slice(0, start)}${note}
14488
+
14489
+ ${kept.trim()}
14490
+ ${text.slice(end + "</Tabs>".length)}`;
14491
+ }
14492
+ var DOCS_URL_IN_TEXT = /https:\/\/dev\.wix\.com\/docs\/(?=api-reference\b|\.\.\.)/g;
14493
+ function toMintlifyHostText(text) {
14494
+ return text.replace(DOCS_URL_IN_TEXT, `${MINTLIFY_HOST}/`);
14495
+ }
14496
+ var SITE_RELATIVE_LINK = /(\]\(|href=["'])\/(?!\/)/g;
14497
+ function absolutizeSiteLinks(markdown) {
14498
+ return markdown.replace(
14499
+ SITE_RELATIVE_LINK,
14500
+ (_, open) => `${open}${MINTLIFY_HOST}/`
14501
+ );
14502
+ }
14503
+ async function readMintlifyMarkdown(rawUrl) {
14504
+ const resolved = await resolveDocsLink(rawUrl);
14505
+ if (resolved.outcome !== "identity" && resolved.outcome !== "redirect") {
14506
+ return null;
14507
+ }
14508
+ const pageUrl = new URL(resolved.url);
14509
+ const response = await fetch(`${MINTLIFY_HOST}${pageUrl.pathname}.md`, {
14510
+ headers: { accept: "text/markdown" },
14511
+ redirect: "follow",
14512
+ signal: AbortSignal.timeout(15e3)
14513
+ });
14514
+ if (response.status === 404) {
14515
+ return null;
14516
+ }
14517
+ if (!response.ok) {
14518
+ throw new Error(
14519
+ `Mintlify returned ${response.status} for ${pageUrl.pathname}.md`
14520
+ );
14521
+ }
14522
+ const flavour = toMintlifyCandidate(rawUrl)?.flavour ?? "rest";
14523
+ const cleaned = cleanMintlifyMarkdown(
14524
+ (await response.text()).replace(INDEX_BANNER, ""),
14525
+ flavour
14526
+ );
14527
+ const { text: rewritten } = await rewriteDocsLinks(cleaned, {
14528
+ label: `read ${pageUrl.pathname}`
14529
+ });
14530
+ const markdown = absolutizeSiteLinks(rewritten);
14531
+ logger.log(
14532
+ `[mintlify] read ${pageUrl.pathname}.md (${markdown.length} chars) for ${rawUrl}`
14533
+ );
14534
+ return { url: `${MINTLIFY_HOST}${pageUrl.pathname}`, markdown };
14535
+ }
14536
+
13872
14537
  // src/docs/get-to-know.ts
14538
+ var AX_SKILLS_REPO = "wix-private/ax-skills";
14539
+ var AX_SKILLS_INDEX_PATH = "skills/wix-manage/SKILL.md";
13873
14540
  function addGetSiteContextTool({
13874
14541
  server,
13875
14542
  authStrategy,
14543
+ includeCompanionApp = false,
13876
14544
  toolDescriptions
13877
14545
  }) {
13878
14546
  const d = desc("GetSiteContext");
@@ -13902,7 +14570,8 @@ function addGetSiteContextTool({
13902
14570
  try {
13903
14571
  context = await getDynamicContext(authStrategy, httpClient, filter, {
13904
14572
  partnerContextEnabled: false,
13905
- includeAccountContext: false
14573
+ includeAccountContext: false,
14574
+ includeCompanionApp
13906
14575
  });
13907
14576
  } catch (err) {
13908
14577
  rethrowIfUnauthorized(err);
@@ -13938,15 +14607,17 @@ async function addGetToKnowTools({
13938
14607
  dynamicContextEnabled,
13939
14608
  repo,
13940
14609
  commitHash,
13941
- skillsTags
14610
+ skillsTags,
14611
+ readmeRecipesIndexEnabled = true,
14612
+ docsSite
13942
14613
  }) {
13943
14614
  if (disableTools.includes("WixREADME")) {
13944
14615
  return;
13945
14616
  }
13946
14617
  const baseDocs = readmeDocs ?? defaultReadmeDocs;
13947
- const baseDocsWithoutRecipes = baseDocs.filter(
13948
- (doc) => doc.url !== RECIPES_INDEX_URL
13949
- );
14618
+ const isRecipesIndex = (doc) => doc.url?.replace(/\.md$/, "") === RECIPES_INDEX_URL;
14619
+ const baseDocsWithoutRecipes = baseDocs.filter((doc) => !isRecipesIndex(doc));
14620
+ const recipesIndexDocs = readmeRecipesIndexEnabled ? baseDocs.filter(isRecipesIndex) : [];
13950
14621
  server.tool(
13951
14622
  "WixREADME",
13952
14623
  toolDescriptions?.["WixREADME"] ?? defaultToolDescriptions.WixREADME,
@@ -14018,6 +14689,18 @@ ${responseText}` : responseText;
14018
14689
  if (!url) {
14019
14690
  throw new Error('URL is required for "include" action');
14020
14691
  }
14692
+ const readsAxSkills = isRecipesIndex({ action, url }) && !skillsPortalUrl && (repo ? repo === AX_SKILLS_REPO : !commitHash);
14693
+ if (readsAxSkills) {
14694
+ return {
14695
+ type: "text",
14696
+ text: await fetchSourceContent(
14697
+ httpClient,
14698
+ AX_SKILLS_REPO,
14699
+ AX_SKILLS_INDEX_PATH,
14700
+ repo === AX_SKILLS_REPO ? commitHash : void 0
14701
+ )
14702
+ };
14703
+ }
14021
14704
  const fetchedContent = await fetchArticleContent(
14022
14705
  httpClient,
14023
14706
  url,
@@ -14060,9 +14743,9 @@ Failed to fetch content: ${errorMsg}`
14060
14743
  }).catch((err) => {
14061
14744
  console.error("[WixREADME] Failed to fetch skills by portal:", err);
14062
14745
  return null;
14063
- }) : fetchDoc({ action: "include", url: RECIPES_INDEX_URL }).then(
14746
+ }) : readmeRecipesIndexEnabled ? fetchDoc({ action: "include", url: RECIPES_INDEX_URL }).then(
14064
14747
  (doc) => doc?.text ?? null
14065
- );
14748
+ ) : Promise.resolve(null);
14066
14749
  const filter = {};
14067
14750
  if (siteId) filter.siteId = siteId;
14068
14751
  else if (siteName) filter.siteName = siteName;
@@ -14082,7 +14765,7 @@ Failed to fetch content: ${errorMsg}`
14082
14765
  const recipesIntro = dedent_default`
14083
14766
  Reusable how-to guides for common ${isVisitorFlow ? "Wix site visitor" : "Wix management"} tasks, grouped by product area. Each recipe is a Wix docs article that bundles the right endpoints, request shapes, field names, and step ordering for one real-world use case.
14084
14767
 
14085
- Scan the index below and open any relevant article with \`ReadFullDocsArticle\`. If the article contains a code example that fits the user's intent — a **complete** example (not a truncated \`... N more lines ...\` snippet) that matches your exact case, same variant/shape — call the API directly. If the closest example is truncated or omits a field your case needs, do NOT call the API off it — continue discovery first: \`ReadFullDocsArticle\` for the complete example, or \`SearchWixAPISpec\` for the exact schema. On query and search methods an example proves only the fields and operators it uses; the endpoint's own declared list covers the rest. Otherwise (no fitting recipe, no usable code example, or persistent errors after retrying), fall back to docs discovery: \`SearchWixRESTDocumentation\` or \`BrowseWixRESTDocsMenu\` to find the right method, then \`SearchWixAPISpec\` for the exact schema.
14768
+ Open the recipe that matches the task with \`ReadFullDocsArticle\` and follow it. When its example for a call matches your case (the same variant and shape), call from it; when it doesn't, or leaves out a field your case needs, read the method's schema with \`SearchWixAPISpec\` first. On query and search methods an example proves only the fields and operators it uses; the method's own declared list covers the rest. When no recipe fits, find the method with \`SearchWixRESTDocumentation\` or \`BrowseWixRESTDocsMenu\`, then \`SearchWixAPISpec\` for its schema.
14086
14769
  `;
14087
14770
  const recipesContents = skillsContent !== null ? [
14088
14771
  await fetchDoc({
@@ -14092,9 +14775,7 @@ Failed to fetch content: ${errorMsg}`
14092
14775
 
14093
14776
  ${skillsContent}`
14094
14777
  })
14095
- ] : await Promise.all(
14096
- baseDocs.filter((doc) => doc.url === RECIPES_INDEX_URL).map(fetchDoc)
14097
- );
14778
+ ] : await Promise.all(recipesIndexDocs.map(fetchDoc));
14098
14779
  const recipeNotes = !skillsContent?.includes("Dashboard Navigation") ? [] : [
14099
14780
  await fetchDoc({
14100
14781
  action: "text",
@@ -14112,7 +14793,8 @@ ${skillsContent}`
14112
14793
  ).map((item) => item.text),
14113
14794
  ...dynamicContext ? [dynamicContext] : []
14114
14795
  ];
14115
- const text = parts.join("\n\n");
14796
+ const joined = parts.join("\n\n");
14797
+ const text = isMintlifyDocsSite(docsSite) ? (await rewriteDocsLinks(joined, { label: "readme WixREADME" })).text : joined;
14116
14798
  return {
14117
14799
  content: [{ type: "text", text }]
14118
14800
  };
@@ -14121,13 +14803,14 @@ ${skillsContent}`
14121
14803
  }
14122
14804
 
14123
14805
  // src/docs/start-deploy-website.ts
14806
+ var NO_PERSONAL_DATA = "No personal data: no names, emails, phones, passwords, keys or local paths.";
14124
14807
  var START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS = {
14125
- userRequest: "The user's request that led here, quoted if short, otherwise summarized in a sentence.",
14126
- siteDescription: "What the site is: its purpose and who it is for. One or two sentences.",
14127
- siteForm: "The form the site exists in right now (HTML/CSS/JS files, a static build, a zip, another builder's export, a design created in this conversation, or something else), roughly how many files, and whether it includes images.",
14128
- siteFunctionality: "What the site must be able to do for its visitors beyond showing pages, if anything (for example selling, bookings or reservations, tickets, menus and orders, rentals, forms, memberships or paid plans, donations, a blog, or anything else the user expects to work), in your own words. If it is a plain static site with no such needs, say so.",
14129
- agentIdentity: "Which agent or product you are and the harness you run in.",
14130
- wixAccess: "How you can reach Wix from where you run: through this MCP only, or also from a shell whose network can reach wixapis.com; whether you can run the Wix CLI (npx @wix/cli) there and whether it is logged in; and which Wix identity you act with \u2014 the user's login through this MCP, a CLI login, or none."
14808
+ userRequest: `A short summary of what the user asked for and the context that led here, in a sentence or two, including whether this is a new site or an edit or new version of a site already on Wix. ${NO_PERSONAL_DATA}`,
14809
+ siteDescription: `What the site is: its purpose and who it is for. One or two sentences. ${NO_PERSONAL_DATA}`,
14810
+ siteForm: `The form the site exists in right now (HTML/CSS/JS files, a static build, a zip, a framework project, another builder's export, a design created in this conversation, or something else), roughly how many files, and whether it includes images. For a framework project: which framework, whether you have its built static output (for example dist/) or only the source, and whether it needs a server at runtime (SSR, API routes, a backend), or that you don't know. Also where it came from: built in this conversation, the user's own project or repo, another builder (which one), or a Wix skill or template (which one). ${NO_PERSONAL_DATA}`,
14811
+ siteFunctionality: `What the site must be able to do for its visitors beyond showing pages, if anything (for example selling, bookings or reservations, tickets, menus and orders, rentals, forms, memberships or paid plans, donations, a blog, or anything else the user expects to work), in your own words. If it is a plain static site with no such needs, say so. ${NO_PERSONAL_DATA}`,
14812
+ agentIdentity: `Which agent or product you are, which model if you know it, and the harness you run in. ${NO_PERSONAL_DATA}`,
14813
+ wixAccess: `How you can reach Wix from where you run: through this MCP only, or also from a shell whose network can reach wixapis.com; whether you can run the Wix CLI (npx @wix/cli) there and whether it is logged in; which Wix identity you act with \u2014 the user's login through this MCP, a CLI login, or none; and which Wix skills you have (for example wix-headless-kit or wix-manage), by name and where they come from (installed on disk, a plugin, read online), or none. Name the kind of identity, not the account or its email. ${NO_PERSONAL_DATA}`
14131
14814
  };
14132
14815
  var START_DEPLOY_WEBSITE_PARAMS = Object.keys(
14133
14816
  START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS
@@ -14135,10 +14818,11 @@ var START_DEPLOY_WEBSITE_PARAMS = Object.keys(
14135
14818
  var START_DEPLOY_WEBSITE_TOOL_NAME = "StartDeployWebsite";
14136
14819
  var START_DEPLOY_WEBSITE_RECIPE_URL = "https://dev.wix.com/docs/api-reference/account-level/sites/skills/upload-a-website-or-html-files.md";
14137
14820
  var DROP_PAGE_URL = "https://www.wix.com/headless/drop?utm_campaign=mcp";
14821
+ var WRITE_THE_CODE_LINE = "When the user brought no files, write the site's code yourself, then upload it as the guide shows.";
14138
14822
  var FALLBACK_LINE = `Only if you cannot run it (you can't reach the file bytes, or a step fails and you can't fix it): send the user to ${DROP_PAGE_URL} \u2014 give that URL exactly as written, keeping the utm_campaign=mcp parameter.`;
14139
14823
  function startDeployWebsiteResponse(recipe) {
14140
14824
  return [
14141
- `The following is the content of ${START_DEPLOY_WEBSITE_RECIPE_URL}. Follow it end to end and do the publishing yourself \u2014 do not hand the user instructions.`,
14825
+ `The following is the content of ${START_DEPLOY_WEBSITE_RECIPE_URL}. Follow it end to end and do the publishing yourself \u2014 do not hand the user instructions. ${WRITE_THE_CODE_LINE}`,
14142
14826
  FALLBACK_LINE,
14143
14827
  "---",
14144
14828
  recipe.trim()
@@ -14146,7 +14830,7 @@ function startDeployWebsiteResponse(recipe) {
14146
14830
  }
14147
14831
  var START_DEPLOY_WEBSITE_POINTER_RESPONSE = [
14148
14832
  `Read and follow this recipe end to end: ${START_DEPLOY_WEBSITE_RECIPE_URL}`,
14149
- "It covers creating the site and uploading the files (with or without a Wix login), updating a site deployed this way, and implementing a backend on it (stores, bookings, CMS, members) as a Wix Headless project. Do the publishing yourself \u2014 do not hand the user instructions.",
14833
+ `It covers creating the site and uploading the files (with or without a Wix login), updating a site deployed this way, and implementing a backend on it (stores, bookings, CMS, members) as a Wix Headless project. Do the publishing yourself \u2014 do not hand the user instructions. ${WRITE_THE_CODE_LINE}`,
14150
14834
  FALLBACK_LINE
14151
14835
  ].join("\n\n");
14152
14836
  function addStartDeployWebsiteTool(server, disableTools = [], toolDescriptions, fetchOptions = {}) {
@@ -14194,36 +14878,21 @@ function addStartDeployWebsiteTool(server, disableTools = [], toolDescriptions,
14194
14878
  // src/docs/start-headless-build.ts
14195
14879
  var START_HEADLESS_BUILD_TOOL_NAME = "StartHeadlessBuild";
14196
14880
  var HEADLESS_GUIDE_URL = "https://www.wix.com/skills/headless-cold-start/headless-kit.md";
14197
- var START_HEADLESS_BUILD_STARTING_POINTS = [
14198
- "nothing yet",
14199
- "a design worked out in this conversation",
14200
- "an existing Wix Headless site",
14201
- "a project download link",
14202
- "a Wix site to move to code"
14203
- ];
14204
14881
  var START_HEADLESS_BUILD_PARAM_DESCRIPTIONS = {
14205
14882
  userRequest: START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS.userRequest,
14206
14883
  siteDescription: START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS.siteDescription,
14207
- startingPoint: "What the build starts from: nothing yet; a design worked out in this conversation; an existing Wix Headless site; a project download link (a download.zip from Wix); or a Wix site to move to code.",
14884
+ startingPoint: `What the site starts from, in your own words: a new site; an existing Wix Headless site (a site deployed from files is one), with its metaSiteId if you have it; a project download link (a download.zip from Wix); a Wix site moving to code; or something else. ${NO_PERSONAL_DATA}`,
14208
14885
  siteFunctionality: START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS.siteFunctionality,
14209
14886
  agentIdentity: START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS.agentIdentity,
14210
14887
  wixAccess: START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS.wixAccess
14211
14888
  };
14212
- var PREFIX2 = "The following is Wix's headless guide. Follow it end to end and do the work yourself. If you find the user already has the site's code, call StartDeployWebsite instead.";
14213
- var MOVE_TO_CODE_LINE = `Moving a Wix site to code starts from the site's dashboard (Headless settings, "Go Headless"), which hands the owner a prompt with the project's download link. Ask the user for that prompt, then follow the guide with it.`;
14214
- function startHeadlessBuildResponse(guide, startingPoint) {
14215
- const lines = [];
14216
- if (startingPoint === "a Wix site to move to code") {
14217
- lines.push(MOVE_TO_CODE_LINE);
14218
- }
14889
+ var BUILT_HERE = "The site is built here, in code: don't offer Wix's editor or a template instead.";
14890
+ var PREFIX2 = `The following is Wix's headless guide. Follow it end to end and do the work yourself: you write the site, and the guide creates it, connects its Wix business features, and uploads it. ${BUILT_HERE}`;
14891
+ function startHeadlessBuildResponse(guide) {
14219
14892
  if (guide && guide.trim()) {
14220
- lines.push(PREFIX2, "---", guide.trim());
14221
- } else {
14222
- lines.push(
14223
- `Wix's headless guide could not be loaded here. It is at ${HEADLESS_GUIDE_URL}: read it whole with ReadFullDocsArticle if you have that tool, otherwise fetch it with curl or your web tool, then follow it end to end and do the work yourself. If you find the user already has the site's code, call StartDeployWebsite instead.`
14224
- );
14893
+ return [PREFIX2, "---", guide.trim()].join("\n\n");
14225
14894
  }
14226
- return lines.join("\n\n");
14895
+ return `Wix's headless guide could not be loaded here. It is at ${HEADLESS_GUIDE_URL}: read it whole with ReadFullDocsArticle if you have that tool, otherwise fetch it with curl or your web tool, then follow it end to end and do the work yourself. ${BUILT_HERE}`;
14227
14896
  }
14228
14897
  function addStartHeadlessBuildTool(server, disableTools = [], toolDescriptions, fetchOptions = {}) {
14229
14898
  if (disableTools.includes(START_HEADLESS_BUILD_TOOL_NAME)) {
@@ -14236,13 +14905,13 @@ function addStartHeadlessBuildTool(server, disableTools = [], toolDescriptions,
14236
14905
  {
14237
14906
  userRequest: external_exports.string().min(1).describe(d.userRequest),
14238
14907
  siteDescription: external_exports.string().min(1).describe(d.siteDescription),
14239
- startingPoint: external_exports.enum(START_HEADLESS_BUILD_STARTING_POINTS).describe(d.startingPoint),
14908
+ startingPoint: external_exports.string().optional().describe(d.startingPoint),
14240
14909
  siteFunctionality: external_exports.string().min(1).describe(d.siteFunctionality),
14241
14910
  agentIdentity: external_exports.string().min(1).describe(d.agentIdentity),
14242
14911
  wixAccess: external_exports.string().min(1).describe(d.wixAccess)
14243
14912
  },
14244
14913
  { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
14245
- async (args, { httpClient }) => {
14914
+ async (_args, { httpClient }) => {
14246
14915
  let guide;
14247
14916
  try {
14248
14917
  guide = await fetchArticleContent(
@@ -14264,7 +14933,7 @@ function addStartHeadlessBuildTool(server, disableTools = [], toolDescriptions,
14264
14933
  content: [
14265
14934
  {
14266
14935
  type: "text",
14267
- text: startHeadlessBuildResponse(guide, args.startingPoint)
14936
+ text: startHeadlessBuildResponse(guide)
14268
14937
  }
14269
14938
  ]
14270
14939
  };
@@ -14300,9 +14969,16 @@ function shouldTryMenuFallbackForDocsRead(error, articleUrl) {
14300
14969
  if (!message.includes("404") || !isMenuFallbackUrl) {
14301
14970
  return false;
14302
14971
  }
14303
- return !["This resource is deprecated", "No schema found"].some(
14304
- (knownArticleError) => message.includes(knownArticleError)
14305
- );
14972
+ return !isKnownArticleError(error);
14973
+ }
14974
+ function isKnownArticleError(error) {
14975
+ const message = getErrorMessage(error);
14976
+ return [
14977
+ "This resource is deprecated",
14978
+ "This method is deprecated",
14979
+ "This article is deprecated",
14980
+ "No schema found"
14981
+ ].some((knownArticleError) => message.includes(knownArticleError));
14306
14982
  }
14307
14983
  function formatDocsReadError(toolAction, error) {
14308
14984
  return `Could not fetch ${toolAction}: "${getErrorMessage(error)}".`;
@@ -14315,7 +14991,8 @@ var addDocsTools = (server, allowedTools = [
14315
14991
  "WIX_HEADLESS",
14316
14992
  "BUSINESS_SOLUTIONS",
14317
14993
  "CLI",
14318
- "OVERVIEW"
14994
+ "OVERVIEW",
14995
+ "HELP_CENTER"
14319
14996
  ], options = {}) => {
14320
14997
  const {
14321
14998
  getToKnowWixEnabled = false,
@@ -14331,14 +15008,47 @@ var addDocsTools = (server, allowedTools = [
14331
15008
  skillsPr,
14332
15009
  commitHash,
14333
15010
  skillsTags,
15011
+ readmeRecipesIndexEnabled,
14334
15012
  dynamicContextEnabled,
15013
+ includeCompanionApp,
14335
15014
  usePlatformizedSearch = true,
14336
- usePlatformizedBrowse = true
15015
+ usePlatformizedBrowse = true,
15016
+ docsSite
14337
15017
  } = options;
14338
15018
  const effectiveRepo = repo ?? skillsRepo;
14339
15019
  const effectiveCommitHash = commitHash ?? skillsPr;
14340
- const getDescription = (toolName, defaultDesc) => toolDescriptions?.[toolName] ?? defaultDesc;
15020
+ const mintlifyDocs = isMintlifyDocsSite(docsSite);
15021
+ const runSearch = mintlifyDocs ? async (params) => {
15022
+ const documentTypes = mintlifyDocumentTypes(docsSite, params.toolName);
15023
+ const result = await runSemanticSearchAndFormat(
15024
+ documentTypes ? { ...params, documentTypes } : params
15025
+ );
15026
+ return (await rewriteDocsLinks(result, { label: `search ${params.toolName}` })).text;
15027
+ } : runSemanticSearchAndFormat;
15028
+ const docsUrlPrefixes = mintlifyDocs ? ["https://dev.wix.com/docs/", ...MINTLIFY_URL_PREFIXES] : ["https://dev.wix.com/docs/"];
15029
+ const toBackendUrl = (url) => mintlifyDocs && isMintlifyUrl(url) ? toDevWixUrl(url) : url;
15030
+ const sourceNote = (source) => mintlifyDocs ? [{ type: "text", text: `[docs source] ${source}` }] : [];
15031
+ const docsText = (text) => mintlifyDocs ? toMintlifyHostText(text) : text;
15032
+ const desc2 = (toolName) => {
15033
+ const descriptions = desc(toolName);
15034
+ if (!mintlifyDocs) return descriptions;
15035
+ return Object.fromEntries(
15036
+ Object.entries(descriptions).map(([param, text]) => [
15037
+ param,
15038
+ docsText(text)
15039
+ ])
15040
+ );
15041
+ };
15042
+ const getDescription = (toolName, defaultDesc) => docsText(toolDescriptions?.[toolName] ?? defaultDesc);
14341
15043
  const fetchMenuTextForUrl = async (httpClient, url) => {
15044
+ const text = await fetchMenuTextForBackendUrl(
15045
+ httpClient,
15046
+ toBackendUrl(url)
15047
+ );
15048
+ if (!text || !mintlifyDocs) return text;
15049
+ return (await rewriteDocsLinks(text, { verify: false, label: "menu" })).text;
15050
+ };
15051
+ const fetchMenuTextForBackendUrl = async (httpClient, url) => {
14342
15052
  if (usePlatformizedBrowse) {
14343
15053
  try {
14344
15054
  return await fetchPlatformizedMenu(url);
@@ -14355,10 +15065,77 @@ var addDocsTools = (server, allowedTools = [
14355
15065
  }
14356
15066
  return menuContent.length > MENU_CONTENT_THRESHOLD ? summarizeLargeMenu(menuContent, url) : normalizeMenuContent(menuContent);
14357
15067
  };
15068
+ const nearestToMissingPage = async (httpClient, url, isSkillsPage) => {
15069
+ try {
15070
+ if (isSkillsPage) {
15071
+ const manifestUrl = skillManifestUrl(url);
15072
+ if (!manifestUrl) return null;
15073
+ const manifest = JSON.parse(
15074
+ await fetchArticleContent(httpClient, manifestUrl, "article")
15075
+ );
15076
+ const guess = closestSkillFile(url, manifest, manifestUrl);
15077
+ if (!guess) return null;
15078
+ if (guess.kind === "file") {
15079
+ const text = await fetchArticleContent(
15080
+ httpClient,
15081
+ guess.url,
15082
+ "article"
15083
+ );
15084
+ return `Note: "${url}" does not exist. Found a similar file in the skill, ${guess.url}, and read it:
15085
+
15086
+ ${text}`;
15087
+ }
15088
+ return `Note: "${url}" does not exist, and no file in ${guess.folder ? `its folder (${guess.folder}/)` : "the skill"} has a name close to it. ${guess.folder ? "The files in that folder" : "The skill's top-level folders and files"}, to read the one you need (every file: ${guess.manifestUrl}):
15089
+
15090
+ ${guess.files.map((f) => `- ${f}`).join("\n")}`;
15091
+ }
15092
+ for (const menuUrl of parentMenuUrls(url)) {
15093
+ const text = await fetchMenuTextForUrl(httpClient, menuUrl);
15094
+ if (!text) continue;
15095
+ const subMenus = (await Promise.all(
15096
+ subMenusToRead(text, menuUrl, url).map(async (sub) => ({
15097
+ sub,
15098
+ text: await fetchMenuTextForUrl(httpClient, sub).catch(() => null)
15099
+ }))
15100
+ )).filter((m) => !!m.text);
15101
+ const closest = closestMenuLinks(
15102
+ [text, ...subMenus.map((m) => m.text)].join("\n"),
15103
+ menuUrl,
15104
+ url
15105
+ );
15106
+ return [
15107
+ `Note: "${url}" does not exist. Showing the nearest documentation menu above it, ${menuUrl}; read the page you need from its links.`,
15108
+ ...closest.length ? [
15109
+ `Closest to what you asked for:
15110
+ ${closest.map((l) => `- ${l}`).join("\n")}`
15111
+ ] : [],
15112
+ text
15113
+ ].join("\n\n");
15114
+ }
15115
+ } catch (error) {
15116
+ logger.error(`[docs] no nearest page for ${url}: ${error}`);
15117
+ }
15118
+ return null;
15119
+ };
15120
+ if (startDeployWebsiteEnabled) {
15121
+ addStartDeployWebsiteTool(server, disableTools || [], toolDescriptions, {
15122
+ skillsPortalUrl,
15123
+ repo: effectiveRepo,
15124
+ commitHash: effectiveCommitHash
15125
+ });
15126
+ }
15127
+ if (startHeadlessBuildEnabled) {
15128
+ addStartHeadlessBuildTool(server, disableTools || [], toolDescriptions, {
15129
+ skillsPortalUrl,
15130
+ repo: effectiveRepo,
15131
+ commitHash: effectiveCommitHash
15132
+ });
15133
+ }
14358
15134
  if (dynamicContextEnabled && authStrategy && !disableTools?.includes("GetSiteContext")) {
14359
15135
  addGetSiteContextTool({
14360
15136
  server,
14361
15137
  authStrategy,
15138
+ includeCompanionApp,
14362
15139
  toolDescriptions,
14363
15140
  disableTools: disableTools || []
14364
15141
  });
@@ -14374,21 +15151,9 @@ var addDocsTools = (server, allowedTools = [
14374
15151
  dynamicContextEnabled,
14375
15152
  repo: effectiveRepo,
14376
15153
  commitHash: effectiveCommitHash,
14377
- skillsTags
14378
- });
14379
- }
14380
- if (startDeployWebsiteEnabled) {
14381
- addStartDeployWebsiteTool(server, disableTools || [], toolDescriptions, {
14382
- skillsPortalUrl,
14383
- repo: effectiveRepo,
14384
- commitHash: effectiveCommitHash
14385
- });
14386
- }
14387
- if (startHeadlessBuildEnabled) {
14388
- addStartHeadlessBuildTool(server, disableTools || [], toolDescriptions, {
14389
- skillsPortalUrl,
14390
- repo: effectiveRepo,
14391
- commitHash: effectiveCommitHash
15154
+ skillsTags,
15155
+ readmeRecipesIndexEnabled,
15156
+ docsSite
14392
15157
  });
14393
15158
  }
14394
15159
  if (allowedTools.includes("WDS") && !disableTools?.includes("SearchWixWDSDocumentation")) {
@@ -14399,7 +15164,7 @@ var addDocsTools = (server, allowedTools = [
14399
15164
  defaultToolDescriptions.SearchWixWDSDocumentation ?? ""
14400
15165
  ),
14401
15166
  (() => {
14402
- const d = desc("SearchWixWDSDocumentation");
15167
+ const d = desc2("SearchWixWDSDocumentation");
14403
15168
  return {
14404
15169
  searchTerm: external_exports.string().describe(d.searchTerm),
14405
15170
  maxResults: external_exports.number().describe(d.maxResults).min(1).max(15).optional().default(15)
@@ -14409,7 +15174,7 @@ var addDocsTools = (server, allowedTools = [
14409
15174
  async ({ searchTerm, maxResults }, { panorama }) => {
14410
15175
  try {
14411
15176
  logger.log(`Searching for ${searchTerm} in Wix WDS`);
14412
- const result = await runSemanticSearchAndFormat({
15177
+ const result = await runSearch({
14413
15178
  toolName: "WDS",
14414
15179
  toolParams: {
14415
15180
  searchTerm
@@ -14452,7 +15217,7 @@ var addDocsTools = (server, allowedTools = [
14452
15217
  defaultToolDescriptions.SearchWixRESTDocumentation
14453
15218
  ),
14454
15219
  (() => {
14455
- const d = desc("SearchWixRESTDocumentation");
15220
+ const d = desc2("SearchWixRESTDocumentation");
14456
15221
  return {
14457
15222
  searchTerm: external_exports.string().describe(d.searchTerm),
14458
15223
  maxResults: external_exports.number().describe(d.maxResults).min(1).max(17).optional().default(17),
@@ -14465,7 +15230,7 @@ var addDocsTools = (server, allowedTools = [
14465
15230
  logger.log(
14466
15231
  `[SearchWixRESTDocumentation] searchTerm="${searchTerm}", reason="${reason}"`
14467
15232
  );
14468
- const result = await runSemanticSearchAndFormat({
15233
+ const result = await runSearch({
14469
15234
  toolName: "REST",
14470
15235
  toolParams: {
14471
15236
  searchTerm
@@ -14516,7 +15281,7 @@ var addDocsTools = (server, allowedTools = [
14516
15281
  defaultToolDescriptions.WixBusinessFlowsDocumentation ?? ""
14517
15282
  ),
14518
15283
  (() => {
14519
- const d = desc("WixBusinessFlowsDocumentation");
15284
+ const d = desc2("WixBusinessFlowsDocumentation");
14520
15285
  return {
14521
15286
  searchTerm: external_exports.string().describe(d.searchTerm),
14522
15287
  maxResults: external_exports.number().describe(d.maxResults).min(1).max(15).optional().default(15)
@@ -14526,7 +15291,7 @@ var addDocsTools = (server, allowedTools = [
14526
15291
  async ({ searchTerm, maxResults }, { panorama }) => {
14527
15292
  try {
14528
15293
  logger.log(`Searching for ${searchTerm} in Wix Sample Flows`);
14529
- const result = await runSemanticSearchAndFormat({
15294
+ const result = await runSearch({
14530
15295
  toolName: "BUSINESS_SOLUTIONS",
14531
15296
  toolParams: {
14532
15297
  searchTerm: "**RECIPE**: Business Recipes - full setup flow for::: " + searchTerm
@@ -14572,7 +15337,7 @@ var addDocsTools = (server, allowedTools = [
14572
15337
  defaultToolDescriptions.SearchWixSDKDocumentation ?? ""
14573
15338
  ),
14574
15339
  (() => {
14575
- const d = desc("SearchWixSDKDocumentation");
15340
+ const d = desc2("SearchWixSDKDocumentation");
14576
15341
  return {
14577
15342
  searchTerm: external_exports.string().describe(d.searchTerm),
14578
15343
  maxResults: external_exports.number().describe(d.maxResults).min(1).max(15).optional().default(15)
@@ -14583,7 +15348,7 @@ var addDocsTools = (server, allowedTools = [
14583
15348
  try {
14584
15349
  logger.log(`Searching for ${searchTerm} in Wix SDK`);
14585
15350
  panorama.transaction("SearchWixSDKDocumentation").start({ maxResults });
14586
- const result = await runSemanticSearchAndFormat({
15351
+ const result = await runSearch({
14587
15352
  toolName: "SDK",
14588
15353
  toolParams: {
14589
15354
  searchTerm
@@ -14631,7 +15396,7 @@ var addDocsTools = (server, allowedTools = [
14631
15396
  defaultToolDescriptions.SearchBuildAppsDocumentation ?? ""
14632
15397
  ),
14633
15398
  (() => {
14634
- const d = desc("SearchBuildAppsDocumentation");
15399
+ const d = desc2("SearchBuildAppsDocumentation");
14635
15400
  return {
14636
15401
  searchTerm: external_exports.string().describe(d.searchTerm),
14637
15402
  maxResults: external_exports.number().describe(d.maxResults).min(1).max(15).optional().default(15)
@@ -14641,7 +15406,7 @@ var addDocsTools = (server, allowedTools = [
14641
15406
  async ({ searchTerm, maxResults }, { panorama }) => {
14642
15407
  try {
14643
15408
  logger.log(`Searching for ${searchTerm} in Build Apps`);
14644
- const result = await runSemanticSearchAndFormat({
15409
+ const result = await runSearch({
14645
15410
  toolName: "BUILD_APPS",
14646
15411
  toolParams: {
14647
15412
  searchTerm: `wix cli docs for ${searchTerm}`
@@ -14683,7 +15448,7 @@ var addDocsTools = (server, allowedTools = [
14683
15448
  defaultToolDescriptions.SearchDevelopmentDocumentation ?? ""
14684
15449
  ),
14685
15450
  (() => {
14686
- const d = desc("SearchDevelopmentDocumentation");
15451
+ const d = desc2("SearchDevelopmentDocumentation");
14687
15452
  return {
14688
15453
  searchTerm: external_exports.string().describe(d.searchTerm),
14689
15454
  maxResults: external_exports.number().describe(d.maxResults).min(1).max(15).optional().default(15)
@@ -14693,7 +15458,7 @@ var addDocsTools = (server, allowedTools = [
14693
15458
  async ({ searchTerm, maxResults }, { panorama }) => {
14694
15459
  try {
14695
15460
  logger.log(`Searching for ${searchTerm} in Wix Overview`);
14696
- const result = await runSemanticSearchAndFormat({
15461
+ const result = await runSearch({
14697
15462
  toolName: "OVERVIEW",
14698
15463
  toolParams: {
14699
15464
  searchTerm
@@ -14735,7 +15500,7 @@ var addDocsTools = (server, allowedTools = [
14735
15500
  defaultToolDescriptions.SearchWixHeadlessDocumentation ?? ""
14736
15501
  ),
14737
15502
  (() => {
14738
- const d = desc("SearchWixHeadlessDocumentation");
15503
+ const d = desc2("SearchWixHeadlessDocumentation");
14739
15504
  return {
14740
15505
  searchTerm: external_exports.string().describe(d.searchTerm),
14741
15506
  maxResults: external_exports.number().describe(d.maxResults).min(1).max(17).optional().default(17)
@@ -14745,7 +15510,7 @@ var addDocsTools = (server, allowedTools = [
14745
15510
  async ({ searchTerm, maxResults }, { panorama }) => {
14746
15511
  try {
14747
15512
  logger.log(`Searching for ${searchTerm} in Headless`);
14748
- const result = await runSemanticSearchAndFormat({
15513
+ const result = await runSearch({
14749
15514
  toolName: "WIX_HEADLESS",
14750
15515
  toolParams: {
14751
15516
  searchTerm
@@ -14790,7 +15555,7 @@ var addDocsTools = (server, allowedTools = [
14790
15555
  defaultToolDescriptions.SearchWixVeloDocumentation ?? ""
14791
15556
  ),
14792
15557
  (() => {
14793
- const d = desc("SearchWixVeloDocumentation");
15558
+ const d = desc2("SearchWixVeloDocumentation");
14794
15559
  return {
14795
15560
  searchTerm: external_exports.string().describe(d.searchTerm),
14796
15561
  maxResults: external_exports.number().describe(d.maxResults).min(1).max(15).optional().default(15)
@@ -14801,7 +15566,7 @@ var addDocsTools = (server, allowedTools = [
14801
15566
  try {
14802
15567
  logger.log(`Searching for ${searchTerm} in Velo`);
14803
15568
  panorama.transaction("SearchWixVeloDocumentation").start({ maxResults });
14804
- const result = await runSemanticSearchAndFormat({
15569
+ const result = await runSearch({
14805
15570
  toolName: "VELO",
14806
15571
  toolParams: {
14807
15572
  searchTerm
@@ -14839,6 +15604,72 @@ var addDocsTools = (server, allowedTools = [
14839
15604
  }
14840
15605
  );
14841
15606
  }
15607
+ if (allowedTools.includes("HELP_CENTER") && !disableTools?.includes("SearchWixHelpCenter")) {
15608
+ server.tool(
15609
+ "SearchWixHelpCenter",
15610
+ getDescription(
15611
+ "SearchWixHelpCenter",
15612
+ defaultToolDescriptions.SearchWixHelpCenter ?? ""
15613
+ ),
15614
+ (() => {
15615
+ const d = desc2("SearchWixHelpCenter");
15616
+ return {
15617
+ searchTerm: external_exports.string().describe(d.searchTerm),
15618
+ maxResults: external_exports.number().describe(d.maxResults).min(1).max(15).optional().default(15),
15619
+ reason: external_exports.string().describe(d.reason)
15620
+ };
15621
+ })(),
15622
+ { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
15623
+ async ({ searchTerm, maxResults, reason }, { panorama }) => {
15624
+ try {
15625
+ logger.log(
15626
+ `[SearchWixHelpCenter] searchTerm="${searchTerm}", reason="${reason}"`
15627
+ );
15628
+ const result = await runSemanticSearchAndFormat({
15629
+ toolName: "HELP_CENTER",
15630
+ toolParams: {
15631
+ searchTerm
15632
+ },
15633
+ maxResults: Math.max(1, Math.min(maxResults ?? 15, 15)),
15634
+ linesInEachResult: 60,
15635
+ usePlatformizedSearch
15636
+ });
15637
+ return {
15638
+ content: [
15639
+ {
15640
+ type: "text",
15641
+ text: `${HELP_CENTER_IS_FOR_PEOPLE}
15642
+
15643
+ ${result}`
15644
+ }
15645
+ ]
15646
+ };
15647
+ } catch (error) {
15648
+ panorama.errorMonitor().reportError(error);
15649
+ captureException(error, {
15650
+ tags: {
15651
+ componentId: "SearchWixHelpCenter",
15652
+ toolName: "SearchWixHelpCenter"
15653
+ }
15654
+ });
15655
+ logger.error(
15656
+ `Error searching for ${searchTerm} in the Wix Help Center: ${error}`
15657
+ );
15658
+ return {
15659
+ isError: true,
15660
+ content: [
15661
+ {
15662
+ type: "text",
15663
+ text: `Error: ${error.message}
15664
+
15665
+ ${HELP_CENTER_IS_FOR_PEOPLE}`
15666
+ }
15667
+ ]
15668
+ };
15669
+ }
15670
+ }
15671
+ );
15672
+ }
14842
15673
  if (allowedTools.includes("CLI") && !disableTools?.includes("SearchWixCLIDocumentation")) {
14843
15674
  server.tool(
14844
15675
  "SearchWixCLIDocumentation",
@@ -14847,7 +15678,7 @@ var addDocsTools = (server, allowedTools = [
14847
15678
  defaultToolDescriptions.SearchWixCLIDocumentation ?? ""
14848
15679
  ),
14849
15680
  (() => {
14850
- const d = desc("SearchWixCLIDocumentation");
15681
+ const d = desc2("SearchWixCLIDocumentation");
14851
15682
  return {
14852
15683
  searchTerm: external_exports.string().describe(d.searchTerm),
14853
15684
  maxResults: external_exports.number().describe(d.maxResults).min(1).max(15).optional().default(15),
@@ -14860,7 +15691,7 @@ var addDocsTools = (server, allowedTools = [
14860
15691
  logger.log(
14861
15692
  `[SearchWixCLIDocumentation] searchTerm="${searchTerm}", reason="${reason}"`
14862
15693
  );
14863
- const result = await runSemanticSearchAndFormat({
15694
+ const result = await runSearch({
14864
15695
  toolName: "CLI",
14865
15696
  toolParams: {
14866
15697
  searchTerm
@@ -14904,14 +15735,39 @@ var addDocsTools = (server, allowedTools = [
14904
15735
  defaultToolDescriptions.ReadFullDocsArticle
14905
15736
  ),
14906
15737
  (() => {
14907
- const d = desc("ReadFullDocsArticle");
15738
+ const d = desc2("ReadFullDocsArticle");
14908
15739
  return {
14909
15740
  articleUrl: external_exports.string().url().describe(d.articleUrl)
14910
15741
  };
14911
15742
  })(),
14912
15743
  { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
14913
15744
  async ({ articleUrl }, { httpClient }) => {
14914
- const validPrefixes = ["https://dev.wix.com/docs/"];
15745
+ const source = parseSourceContentUrl(articleUrl);
15746
+ if (source) {
15747
+ try {
15748
+ const text = await fetchSourceContent(
15749
+ httpClient,
15750
+ source.repo,
15751
+ source.path,
15752
+ source.commitHash
15753
+ );
15754
+ return { content: [{ type: "text", text }] };
15755
+ } catch (error) {
15756
+ logger.error(
15757
+ `Error fetching source content for ${articleUrl}: ${error}`
15758
+ );
15759
+ return {
15760
+ isError: true,
15761
+ content: [
15762
+ {
15763
+ type: "text",
15764
+ text: formatDocsReadError("source content", error)
15765
+ }
15766
+ ]
15767
+ };
15768
+ }
15769
+ }
15770
+ const validPrefixes = [...docsUrlPrefixes, "https://support.wix.com/"];
14915
15771
  if (skillsPortalUrl) validPrefixes.push(skillsPortalUrl);
14916
15772
  const isSkillsPage = isSkillsRegistryUrl(articleUrl);
14917
15773
  if (!isSkillsPage && !validPrefixes.some((p) => articleUrl.startsWith(p))) {
@@ -14920,7 +15776,7 @@ var addDocsTools = (server, allowedTools = [
14920
15776
  content: [
14921
15777
  {
14922
15778
  type: "text",
14923
- text: `Invalid article URL: must start with https://dev.wix.com/docs/, or be a Wix skills page (https://www.wix.com/skills/..., https://www.wix.com/headless/...). Got: "${articleUrl}"`
15779
+ text: `Invalid article URL: must start with https://dev.wix.com/docs/ or https://support.wix.com/, or be a Wix skills page (https://www.wix.com/skills/..., https://www.wix.com/headless/..., or that file's github.com/wix/skills source). Got: "${articleUrl}"`
14924
15780
  }
14925
15781
  ]
14926
15782
  };
@@ -14928,6 +15784,27 @@ var addDocsTools = (server, allowedTools = [
14928
15784
  if (!isSkillsPage && articleUrl.startsWith("https://dev.wix.com/docs/")) {
14929
15785
  articleUrl = articleUrl.replace(/\.md$/, "");
14930
15786
  }
15787
+ if (mintlifyDocs && !isSkillsPage) {
15788
+ try {
15789
+ const page = await readMintlifyMarkdown(articleUrl);
15790
+ if (page) {
15791
+ return {
15792
+ content: [
15793
+ {
15794
+ type: "text",
15795
+ text: isQueryOrSearchEndpoint(page.url) ? [page.markdown, ARTICLE_CAPABILITY_NOTE].join("\n\n") : page.markdown
15796
+ },
15797
+ ...sourceNote(`Mintlify site: ${page.url}.md`)
15798
+ ]
15799
+ };
15800
+ }
15801
+ } catch (error) {
15802
+ logger.error(
15803
+ `[mintlify] reading ${articleUrl} from Mintlify failed, falling back: ${error}`
15804
+ );
15805
+ }
15806
+ articleUrl = toBackendUrl(articleUrl);
15807
+ }
14931
15808
  try {
14932
15809
  const articleContent = await fetchArticleContent(
14933
15810
  httpClient,
@@ -14948,7 +15825,10 @@ var addDocsTools = (server, allowedTools = [
14948
15825
  // The article's own URL, never its body — a filter/sort reference page links query
14949
15826
  // methods in its prose but isn't one.
14950
15827
  text: isQueryOrSearchEndpoint(articleUrl) ? [articleContent, ARTICLE_CAPABILITY_NOTE].join("\n\n") : articleContent
14951
- }
15828
+ },
15829
+ ...sourceNote(
15830
+ isSkillsPage ? `skills registry: ${articleUrl}` : `today's backend (dev.wix.com/rawdocs, ingvar), not on the Mintlify site: ${articleUrl}`
15831
+ )
14952
15832
  ]
14953
15833
  };
14954
15834
  } catch (error) {
@@ -14973,6 +15853,16 @@ ${text}`
14973
15853
  } catch {
14974
15854
  }
14975
15855
  }
15856
+ if (getErrorMessage(error).includes("404") && !isKnownArticleError(error)) {
15857
+ const nearest = await nearestToMissingPage(
15858
+ httpClient,
15859
+ articleUrl,
15860
+ isSkillsPage
15861
+ );
15862
+ if (nearest) {
15863
+ return { content: [{ type: "text", text: nearest }] };
15864
+ }
15865
+ }
14976
15866
  return {
14977
15867
  isError: true,
14978
15868
  content: [
@@ -14994,7 +15884,7 @@ ${text}`
14994
15884
  defaultToolDescriptions.ReadFullDocsMethodSchema
14995
15885
  ),
14996
15886
  (() => {
14997
- const d = desc("ReadFullDocsMethodSchema");
15887
+ const d = desc2("ReadFullDocsMethodSchema");
14998
15888
  return {
14999
15889
  articleUrl: external_exports.string().url().describe(d.articleUrl),
15000
15890
  reason: external_exports.string().describe(d.reason)
@@ -15002,7 +15892,7 @@ ${text}`
15002
15892
  })(),
15003
15893
  { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
15004
15894
  async ({ articleUrl, reason }, { httpClient }) => {
15005
- const validPrefixes = ["https://dev.wix.com/docs/"];
15895
+ const validPrefixes = [...docsUrlPrefixes];
15006
15896
  if (skillsPortalUrl) validPrefixes.push(skillsPortalUrl);
15007
15897
  if (!validPrefixes.some((p) => articleUrl.startsWith(p))) {
15008
15898
  return {
@@ -15018,6 +15908,7 @@ ${text}`
15018
15908
  if (articleUrl.startsWith("https://dev.wix.com/docs/")) {
15019
15909
  articleUrl = articleUrl.replace(/\.md$/, "");
15020
15910
  }
15911
+ articleUrl = toBackendUrl(articleUrl);
15021
15912
  try {
15022
15913
  logger.log(
15023
15914
  `[ReadFullDocsMethodSchema] url="${articleUrl}", reason="${reason}"`
@@ -15035,12 +15926,18 @@ ${text}`
15035
15926
  if (!articleContent) {
15036
15927
  throw new Error("Method schema is empty");
15037
15928
  }
15929
+ const schemaText = mintlifyDocs ? (await rewriteDocsLinks(articleContent, {
15930
+ label: "schema ReadFullDocsMethodSchema"
15931
+ })).text : articleContent;
15038
15932
  return {
15039
15933
  content: [
15040
15934
  {
15041
15935
  type: "text",
15042
- text: articleContent
15043
- }
15936
+ text: schemaText
15937
+ },
15938
+ ...sourceNote(
15939
+ `PAPI, through today's backend (dev.wix.com/rawdocs, ingvar): ${articleUrl}`
15940
+ )
15044
15941
  ]
15045
15942
  };
15046
15943
  } catch (error) {
@@ -15086,7 +15983,7 @@ ${text}`
15086
15983
  defaultToolDescriptions.BrowseWixRESTDocsMenu
15087
15984
  ),
15088
15985
  (() => {
15089
- const d = desc("BrowseWixRESTDocsMenu");
15986
+ const d = desc2("BrowseWixRESTDocsMenu");
15090
15987
  return {
15091
15988
  menuUrl: external_exports.string().optional().describe(d.menuUrl),
15092
15989
  reason: external_exports.string().describe(d.reason)
@@ -15095,7 +15992,7 @@ ${text}`
15095
15992
  { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
15096
15993
  async ({ menuUrl, reason }, { httpClient }) => {
15097
15994
  try {
15098
- if (menuUrl && !menuUrl.startsWith("https://dev.wix.com/docs/")) {
15995
+ if (menuUrl && !docsUrlPrefixes.some((p) => menuUrl.startsWith(p))) {
15099
15996
  return {
15100
15997
  content: [
15101
15998
  {
@@ -15156,6 +16053,16 @@ var getRetryHint = (errorDetails) => {
15156
16053
  }
15157
16054
  return /"operator":"\{/.test(errorDetails) ? SINGLE_OPERATOR_PER_FIELD_HINT : FIELD_CAPABILITIES_HINT;
15158
16055
  };
16056
+ var NO_SITE_IN_REQUEST = /META_SITE_NOT_FOUND|MetaSite(?:Id)? (?:context )?(?:is missing|was not found|not found)|NO_METASITE_IN_CALL_CONTEXT|MISSING_METASITE_ID|MISSING_REQUEST_SITE_CONTEXT|metasiteId from the request context/i;
16057
+ function missingSiteContext(status, responseData) {
16058
+ const details = typeof responseData === "string" ? responseData : JSON.stringify(responseData ?? "");
16059
+ if (NO_SITE_IN_REQUEST.test(details)) return "certain";
16060
+ const parsed = typeof responseData === "string" ? safeParseJSON(responseData) : responseData;
16061
+ if (status === 403 && parsed && typeof parsed === "object" && !parsed.message) {
16062
+ return "likely";
16063
+ }
16064
+ return null;
16065
+ }
15159
16066
  var safeParseJSON = (text) => {
15160
16067
  try {
15161
16068
  return JSON.parse(text);
@@ -15167,7 +16074,8 @@ var buildErrorMessage = ({
15167
16074
  status,
15168
16075
  statusText,
15169
16076
  requestId,
15170
- responseData
16077
+ responseData,
16078
+ retryHint
15171
16079
  }) => {
15172
16080
  const responseDataObject = typeof responseData === "string" ? safeParseJSON(responseData) : responseData;
15173
16081
  const errorDetails = typeof responseDataObject === "object" ? JSON.stringify(responseDataObject) : responseDataObject;
@@ -15179,7 +16087,7 @@ var buildErrorMessage = ({
15179
16087
  ].filter((str) => !!str).join("\n");
15180
16088
  return `${baseMessage}
15181
16089
 
15182
- ${getRetryHint(String(errorDetails))}`;
16090
+ ${retryHint ?? getRetryHint(String(errorDetails))}`;
15183
16091
  };
15184
16092
  var handleWixAPIResponse = async (response) => {
15185
16093
  const requestId = response.headers?.["x-wix-request-id"];
@@ -15375,7 +16283,7 @@ function resolveBase64Image(imageBase64, mimeType) {
15375
16283
  function errorResponse(text) {
15376
16284
  return { isError: true, content: [{ type: "text", text }] };
15377
16285
  }
15378
- async function uploadFromUrl(item, authHeaders, displayName) {
16286
+ async function uploadFromUrl(item, authHeaders, displayName, imagePickerToolName) {
15379
16287
  if (!item.download_url) {
15380
16288
  return {
15381
16289
  success: false,
@@ -15385,7 +16293,7 @@ async function uploadFromUrl(item, authHeaders, displayName) {
15385
16293
  if (!/^https?:\/\//i.test(item.download_url)) {
15386
16294
  return {
15387
16295
  success: false,
15388
- error: `download_url must be a publicly accessible http(s) URL, not a local or sandbox file path (got: "${item.download_url}"). For a user-attached file, pass the file so the platform resolves it to a real download_url; if you only have the raw bytes, use imageBase64 instead.`
16296
+ error: `This server can't read files on your machine or sandbox (got: "${item.download_url}"). Send this image as imageBase64 (the whole file, one per call) or as a public URL in imageUrls${imagePickerToolName ? `, or call ${imagePickerToolName} so the user picks it` : ""}.`
15389
16297
  };
15390
16298
  }
15391
16299
  try {
@@ -15483,7 +16391,10 @@ function formatBatchResults(results) {
15483
16391
  };
15484
16392
  }
15485
16393
  function addUploadImageTool(server, strategy, options) {
15486
- const description = options?.description ?? defaultToolDescriptions.UploadImageToWixSite;
16394
+ const imagePickerToolName = options?.imagePickerToolName;
16395
+ const baseDescription = options?.description || defaultToolDescriptions.UploadImageToWixSite;
16396
+ const description = imagePickerToolName ? `${baseDescription}
16397
+ If you can't deliver the image any of these ways, call ${imagePickerToolName} so the user picks it.` : baseDescription;
15487
16398
  const hints = {
15488
16399
  readOnlyHint: options?.hints?.readOnlyHint ?? false,
15489
16400
  destructiveHint: options?.hints?.destructiveHint ?? false,
@@ -15550,7 +16461,9 @@ function addUploadImageTool(server, strategy, options) {
15550
16461
  try {
15551
16462
  const authHeaders = await strategy.getSiteAuthHeaders(siteId);
15552
16463
  const urlResults = await Promise.all(
15553
- urlItems.map((item) => uploadFromUrl(item, authHeaders, displayName))
16464
+ urlItems.map(
16465
+ (item) => uploadFromUrl(item, authHeaders, displayName, imagePickerToolName)
16466
+ )
15554
16467
  );
15555
16468
  let base64Result = null;
15556
16469
  if (imageBase64) {
@@ -15586,6 +16499,9 @@ function addUploadImageTool(server, strategy, options) {
15586
16499
  var MAX_ALLOWED_RESPONSE_CHARS = 2e6;
15587
16500
 
15588
16501
  // src/api-call/index.ts
16502
+ var siteToolCall = (executeServed) => executeServed ? "through ExecuteWixAPI with that site's `siteId` (`scope: \"site\"`), loading ExecuteWixAPI through your client's tool search if it isn't loaded" : "through CallWixSiteAPI with that site's `siteId`";
16503
+ var manageNoSiteHint = (executeServed) => `This is a site-level API: it acts inside a site, and ManageWixSite sends no site, so the same call fails here every time. Call the same URL, method and body ${siteToolCall(executeServed)}.`;
16504
+ var manageEmpty403Hint = (executeServed) => `If this endpoint acts inside a site (its products, data, bookings, members, pages, publishing), it is site-level, and ManageWixSite sends no site: call the same URL, method and body ${siteToolCall(executeServed)}. If it acts on the account or on sites as objects in it, the account lacks the permission; don't retry.`;
15589
16505
  var DEFAULT_MAX_RESPONSE_SIZE = 5e4;
15590
16506
  function extractReturnedRows(responseData) {
15591
16507
  if (responseData === null || typeof responseData !== "object") {
@@ -15617,7 +16533,8 @@ function formatApiResponse(responseData, maxResponseSize = DEFAULT_MAX_RESPONSE_
15617
16533
  }
15618
16534
  return { text: responseText, isError: false, originalLength, returnedRows };
15619
16535
  }
15620
- function addApiCallTool(server, strategy, allowedTools = ["CallWixSiteAPI", "ListWixSites", "ManageWixSite"], disableTools = [], enableTools = [], toolDescriptions, toolHints, maxResponseSize) {
16536
+ function addApiCallTool(server, strategy, allowedTools = ["CallWixSiteAPI", "ListWixSites", "ManageWixSite"], disableTools = [], enableTools = [], toolDescriptions, toolHints, maxResponseSize, options) {
16537
+ const executeAdvice = (text, reconnect, plain) => options?.executeWixAPIServed ? text : text.replace(reconnect, plain);
15621
16538
  const effectiveAllowedTools = [
15622
16539
  .../* @__PURE__ */ new Set([...allowedTools, ...enableTools])
15623
16540
  ].filter((tool) => !disableTools.includes(tool));
@@ -15630,7 +16547,14 @@ function addApiCallTool(server, strategy, allowedTools = ["CallWixSiteAPI", "Lis
15630
16547
  if (effectiveAllowedTools.includes("CallWixSiteAPI") && !disableTools.includes("CallWixSiteAPI")) {
15631
16548
  server.tool(
15632
16549
  "CallWixSiteAPI",
15633
- getDescription("CallWixSiteAPI", defaultToolDescriptions.CallWixSiteAPI),
16550
+ executeAdvice(
16551
+ getDescription(
16552
+ "CallWixSiteAPI",
16553
+ defaultToolDescriptions.CallWixSiteAPI
16554
+ ),
16555
+ CALL_WIX_SITE_API_RECONNECT_LINE,
16556
+ CALL_WIX_SITE_API_NO_EXECUTE_LINE
16557
+ ),
15634
16558
  (() => {
15635
16559
  const d = desc("CallWixSiteAPI");
15636
16560
  return {
@@ -15639,6 +16563,13 @@ function addApiCallTool(server, strategy, allowedTools = ["CallWixSiteAPI", "Lis
15639
16563
  method: external_exports.string().describe(d.method),
15640
16564
  body: external_exports.union([external_exports.object({}).passthrough(), external_exports.array(external_exports.unknown())]).optional().describe(d.body),
15641
16565
  reason: external_exports.string().describe(d.reason),
16566
+ whyNotExecuteWixAPI: external_exports.string().optional().describe(
16567
+ executeAdvice(
16568
+ d.whyNotExecuteWixAPI,
16569
+ WHY_NOT_EXECUTE_RECONNECT,
16570
+ WHY_NOT_EXECUTE_NO_EXECUTE
16571
+ )
16572
+ ),
15642
16573
  sourceDocUrl: external_exports.string().describe(d.sourceDocUrl)
15643
16574
  };
15644
16575
  })(),
@@ -15649,9 +16580,17 @@ function addApiCallTool(server, strategy, allowedTools = ["CallWixSiteAPI", "Lis
15649
16580
  destructiveHint: true,
15650
16581
  openWorldHint: true
15651
16582
  }),
15652
- async ({ url, body, method, siteId, reason, sourceDocUrl }, { httpClient, setToolBiParams }) => {
16583
+ async ({
16584
+ url,
16585
+ body,
16586
+ method,
16587
+ siteId,
16588
+ reason,
16589
+ whyNotExecuteWixAPI,
16590
+ sourceDocUrl
16591
+ }, { httpClient, setToolBiParams }) => {
15653
16592
  logger.log(
15654
- `[CallWixSiteAPI] siteId=${siteId}, method=${method}, url=${url}, sourceDocUrl=${sourceDocUrl}, reason="${reason}", body=${JSON.stringify(body)}`
16593
+ `[CallWixSiteAPI] siteId=${siteId}, method=${method}, url=${url}, sourceDocUrl=${sourceDocUrl}, reason="${reason}", whyNotExecuteWixAPI="${whyNotExecuteWixAPI ?? ""}", body=${JSON.stringify(body)}`
15655
16594
  );
15656
16595
  const requestHeaders = {
15657
16596
  // Ask the Wix API edge for JSON error bodies (it serves a branded HTML
@@ -15886,6 +16825,18 @@ function addApiCallTool(server, strategy, allowedTools = ["CallWixSiteAPI", "Lis
15886
16825
  if (!response) {
15887
16826
  throw new Error(`Failed to call Wix Account API: ${error}`);
15888
16827
  }
16828
+ const noSite = missingSiteContext(response.status, response.data);
16829
+ if (noSite) {
16830
+ throw new Error(
16831
+ buildErrorMessage({
16832
+ status: response.status,
16833
+ statusText: response.statusText,
16834
+ requestId: response.headers?.["x-wix-request-id"] ?? null,
16835
+ responseData: response.data,
16836
+ retryHint: (noSite === "certain" ? manageNoSiteHint : manageEmpty403Hint)(!!options?.executeWixAPIServed)
16837
+ })
16838
+ );
16839
+ }
15889
16840
  return handleWixAPIResponse(response);
15890
16841
  }
15891
16842
  }
@@ -15894,7 +16845,8 @@ function addApiCallTool(server, strategy, allowedTools = ["CallWixSiteAPI", "Lis
15894
16845
  if (effectiveAllowedTools.includes("UploadImageToWixSite") && !disableTools.includes("UploadImageToWixSite")) {
15895
16846
  addUploadImageTool(server, strategy, {
15896
16847
  description: getDescription("UploadImageToWixSite", ""),
15897
- hints: toolHints?.["UploadImageToWixSite"]
16848
+ hints: toolHints?.["UploadImageToWixSite"],
16849
+ imagePickerToolName: options?.imagePickerToolName
15898
16850
  });
15899
16851
  }
15900
16852
  }
@@ -15912,10 +16864,14 @@ function truncateResponse(content, maxChars = DEFAULT_MAX_CHARS) {
15912
16864
  const truncated = text.slice(0, effectiveMax);
15913
16865
  const estimatedTokens = Math.ceil(text.length / CHARS_PER_TOKEN);
15914
16866
  const limitTokens = Math.ceil(effectiveMax / CHARS_PER_TOKEN);
15915
- return `${truncated}
16867
+ const cutChars = text.length - effectiveMax;
16868
+ const note = `Response was ~${estimatedTokens.toLocaleString()} tokens (limit: ${limitTokens.toLocaleString()}). Showing the first ${effectiveMax.toLocaleString()} of ${text.length.toLocaleString()} characters; the last ${cutChars.toLocaleString()} were cut. To get the rest, run again and return less per call \u2014 only the fields you need, the next page (cursor or offset), or the next slice of a large array \u2014 and repeat until you have it all.`;
16869
+ return `[TRUNCATED: ${note}]
16870
+
16871
+ ${truncated}
15916
16872
 
15917
16873
  --- TRUNCATED ---
15918
- Response was ~${estimatedTokens.toLocaleString()} tokens (limit: ${limitTokens.toLocaleString()}). Refine your code to return less data.`;
16874
+ ${note}`;
15919
16875
  }
15920
16876
 
15921
16877
  // src/graphql/constants.ts
@@ -16676,7 +17632,7 @@ ${text.slice(0, 2e3)}`
16676
17632
 
16677
17633
  // src/code-mode/attachments.ts
16678
17634
  var MAX_ATTACHMENTS_BYTES = 10 * 1024 * 1024;
16679
- var MAX_REDIRECTS = 4;
17635
+ var MAX_REDIRECTS2 = 4;
16680
17636
  var OAI_USERCONTENT_HOST = /^[a-z0-9-]+\.oaiusercontent\.com$/;
16681
17637
  var OAI_BLOB_ACCOUNTS = new Set(
16682
17638
  [
@@ -16707,15 +17663,18 @@ var OAI_BLOB_ACCOUNTS = new Set(
16707
17663
  "westus3"
16708
17664
  ].map((region) => `oaisdmntpr${region}.blob.core.windows.net`)
16709
17665
  );
16710
- function isAllowedAttachmentHost(hostname) {
17666
+ var WIX_AI_ASSISTANT_ATTACHMENT_HOSTS = /* @__PURE__ */ new Set([
17667
+ "wixmp-a85a29aaf55baa3e5171a82d.wixmp.com"
17668
+ ]);
17669
+ function isAllowedAttachmentHost(hostname, extraHosts) {
16711
17670
  const h = hostname.toLowerCase();
16712
- return OAI_USERCONTENT_HOST.test(h) || OAI_BLOB_ACCOUNTS.has(h);
17671
+ return OAI_USERCONTENT_HOST.test(h) || OAI_BLOB_ACCOUNTS.has(h) || !!extraHosts?.has(h);
16713
17672
  }
16714
- function checkAttachmentUrl(url) {
17673
+ function checkAttachmentUrl(url, extraHosts) {
16715
17674
  if (url.protocol !== "https:") return "is not an https URL";
16716
17675
  if (url.port) return "uses a non-default port";
16717
- if (!isAllowedAttachmentHost(url.hostname)) {
16718
- return `host "${url.hostname}" is not a ChatGPT file host`;
17676
+ if (!isAllowedAttachmentHost(url.hostname, extraHosts)) {
17677
+ return `host "${url.hostname}" is not a chat attachment host`;
16719
17678
  }
16720
17679
  return void 0;
16721
17680
  }
@@ -16742,7 +17701,7 @@ function toBase64(bytes) {
16742
17701
  }
16743
17702
  return btoa(binary);
16744
17703
  }
16745
- async function fetchAllowed(start, fetchImpl, label) {
17704
+ async function fetchAllowed(start, fetchImpl, label, extraHosts) {
16746
17705
  let url = start;
16747
17706
  for (let hop = 0; ; hop++) {
16748
17707
  const res = await fetchImpl(url.toString(), { redirect: "manual" });
@@ -16750,7 +17709,7 @@ async function fetchAllowed(start, fetchImpl, label) {
16750
17709
  const isRedirect = res.status >= 300 && res.status < 400 && location;
16751
17710
  if (!isRedirect) return res;
16752
17711
  await res.body?.cancel().catch(() => void 0);
16753
- if (hop >= MAX_REDIRECTS) {
17712
+ if (hop >= MAX_REDIRECTS2) {
16754
17713
  throw new AttachmentError(`${label}: too many redirects.`);
16755
17714
  }
16756
17715
  let next;
@@ -16759,7 +17718,7 @@ async function fetchAllowed(start, fetchImpl, label) {
16759
17718
  } catch {
16760
17719
  throw new AttachmentError(`${label}: redirect to an invalid URL.`);
16761
17720
  }
16762
- const problem = checkAttachmentUrl(next);
17721
+ const problem = checkAttachmentUrl(next, extraHosts);
16763
17722
  if (problem) {
16764
17723
  throw new AttachmentError(
16765
17724
  `${label}: the download redirected to a URL that ${problem}; refused.`
@@ -16797,7 +17756,7 @@ async function readWithin(res, budget, overBudget) {
16797
17756
  }
16798
17757
  return out;
16799
17758
  }
16800
- async function resolveAttachments(attachments, fetchImpl = fetch) {
17759
+ async function resolveAttachments(attachments, fetchImpl = fetch, extraHosts) {
16801
17760
  if (!Array.isArray(attachments) || attachments.length === 0) return [];
16802
17761
  const items = [];
16803
17762
  attachments.forEach((raw, i) => {
@@ -16816,10 +17775,10 @@ async function resolveAttachments(attachments, fetchImpl = fetch) {
16816
17775
  )}). \`attachments\` works only in ChatGPT, which swaps each file for a download URL before the call reaches Wix. Here it arrived as written, and Wix cannot read a path on your machine, so retrying the same value fails the same way. Upload another way. With a shell that reaches the internet, send the file from there (for a site, the StartDeployWebsite recipe's curl drop). Otherwise leave \`attachments\` out and put the files in \`files\`: text as is, and a binary as a \`=== FILE: <path> base64 ===\` entry only when it is a few KB (an icon, a logo), since a larger base64 payload does not survive your own output. A larger image goes in by public URL or through the user (the Media Manager).`
16817
17776
  );
16818
17777
  }
16819
- const problem = checkAttachmentUrl(url);
17778
+ const problem = checkAttachmentUrl(url, extraHosts);
16820
17779
  if (problem) {
16821
17780
  throw new AttachmentError(
16822
- `attachments[${i}].download_url ${problem}. Only platform-resolved attachments are accepted here; for any other URL fetch it in \`code\` via wix.request or put the bytes in the \`files\` bundle.`
17781
+ `attachments[${i}].download_url ${problem}. \`attachments\` takes only files the chat platform attached and resolved; leave it out. For a file at a URL, import it in \`code\` with \`POST https://www.wixapis.com/site-media/v1/files/import\` and \`{ url }\` (Wix fetches it); for a small file you have as text, use the \`files\` bundle.`
16823
17782
  );
16824
17783
  }
16825
17784
  items.push({ a, url });
@@ -16831,7 +17790,7 @@ async function resolveAttachments(attachments, fetchImpl = fetch) {
16831
17790
  const label = `attachments[${i}] (${path})`;
16832
17791
  let res;
16833
17792
  try {
16834
- res = await fetchAllowed(url, fetchImpl, label);
17793
+ res = await fetchAllowed(url, fetchImpl, label, extraHosts);
16835
17794
  } catch (e) {
16836
17795
  if (e instanceof AttachmentError) throw e;
16837
17796
  throw new AttachmentError(
@@ -16916,7 +17875,13 @@ async function unzip(z) {
16916
17875
  let p = u32(end + 16);
16917
17876
  let total = 0;
16918
17877
  const decoder = new TextDecoder();
16919
- for (let i = 0, n = u16(end + 10); i < n; i++) {
17878
+ const n = u16(end + 10);
17879
+ if (n > MAX_SITE_FILES) {
17880
+ throw new Error(
17881
+ `zip holds ${n} entries; a site upload is at most ${MAX_SITE_FILES} files`
17882
+ );
17883
+ }
17884
+ for (let i = 0; i < n; i++) {
16920
17885
  const path = decoder.decode(z.slice(p + 46, p + 46 + u16(p + 28)));
16921
17886
  const method = u16(p + 10);
16922
17887
  const size = u32(p + 24);
@@ -16952,20 +17917,106 @@ async function unzip(z) {
16952
17917
  }
16953
17918
  return out;
16954
17919
  }
17920
+ var CRC_TABLE = (() => {
17921
+ const t = new Uint32Array(256);
17922
+ for (let n = 0; n < 256; n++) {
17923
+ let c = n;
17924
+ for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
17925
+ t[n] = c >>> 0;
17926
+ }
17927
+ return t;
17928
+ })();
17929
+ function crc32(bytes) {
17930
+ let c = 4294967295;
17931
+ for (let i = 0; i < bytes.length; i++)
17932
+ c = CRC_TABLE[(c ^ bytes[i]) & 255] ^ c >>> 8;
17933
+ return (c ^ 4294967295) >>> 0;
17934
+ }
17935
+ function zipStored(files) {
17936
+ const encoder = new TextEncoder();
17937
+ const entries = files.map((f) => ({
17938
+ name: encoder.encode(f.path),
17939
+ bytes: f.bytes,
17940
+ crc: crc32(f.bytes)
17941
+ }));
17942
+ const localSize = entries.reduce(
17943
+ (sum, e) => sum + 30 + e.name.length + e.bytes.length,
17944
+ 0
17945
+ );
17946
+ const centralSize = entries.reduce((sum, e) => sum + 46 + e.name.length, 0);
17947
+ const out = new Uint8Array(localSize + centralSize + 22);
17948
+ const view = new DataView(out.buffer);
17949
+ const UTF8_FLAG = 2048;
17950
+ let p = 0;
17951
+ const offsets = [];
17952
+ for (const e of entries) {
17953
+ offsets.push(p);
17954
+ view.setUint32(p, 67324752, true);
17955
+ view.setUint16(p + 4, 20, true);
17956
+ view.setUint16(p + 6, UTF8_FLAG, true);
17957
+ view.setUint16(p + 8, 0, true);
17958
+ view.setUint32(p + 10, 0, true);
17959
+ view.setUint32(p + 14, e.crc, true);
17960
+ view.setUint32(p + 18, e.bytes.length, true);
17961
+ view.setUint32(p + 22, e.bytes.length, true);
17962
+ view.setUint16(p + 26, e.name.length, true);
17963
+ view.setUint16(p + 28, 0, true);
17964
+ out.set(e.name, p + 30);
17965
+ out.set(e.bytes, p + 30 + e.name.length);
17966
+ p += 30 + e.name.length + e.bytes.length;
17967
+ }
17968
+ const centralStart = p;
17969
+ entries.forEach((e, i) => {
17970
+ view.setUint32(p, 33639248, true);
17971
+ view.setUint16(p + 4, 20, true);
17972
+ view.setUint16(p + 6, 20, true);
17973
+ view.setUint16(p + 8, UTF8_FLAG, true);
17974
+ view.setUint16(p + 10, 0, true);
17975
+ view.setUint32(p + 12, 0, true);
17976
+ view.setUint32(p + 16, e.crc, true);
17977
+ view.setUint32(p + 20, e.bytes.length, true);
17978
+ view.setUint32(p + 24, e.bytes.length, true);
17979
+ view.setUint16(p + 28, e.name.length, true);
17980
+ view.setUint32(p + 42, offsets[i], true);
17981
+ out.set(e.name, p + 46);
17982
+ p += 46 + e.name.length;
17983
+ });
17984
+ view.setUint32(p, 101010256, true);
17985
+ view.setUint16(p + 8, entries.length, true);
17986
+ view.setUint16(p + 10, entries.length, true);
17987
+ view.setUint32(p + 12, p - centralStart, true);
17988
+ view.setUint32(p + 16, centralStart, true);
17989
+ return out;
17990
+ }
16955
17991
  var isZip = (f) => f.bytes.length >= 4 && f.bytes[0] === 80 && f.bytes[1] === 75 && (f.bytes[2] === 3 && f.bytes[3] === 4 || f.bytes[2] === 5 && f.bytes[3] === 6) || /\.zip$/i.test(f.path);
16956
17992
  var isRootIndex = (p) => /^index\.html?$/i.test(p);
16957
- async function normalizeSiteFiles(input) {
16958
- let list = [];
17993
+ async function expandSiteFiles(input) {
17994
+ const list = [];
17995
+ let bytes = 0;
16959
17996
  for (const f of input) {
16960
- if (isZip(f)) list.push(...await unzip(f.bytes));
16961
- else list.push(f);
17997
+ const added = isZip(f) ? await unzip(f.bytes) : [f];
17998
+ list.push(...added);
17999
+ bytes += added.reduce((sum, a) => sum + a.bytes.byteLength, 0);
18000
+ if (list.length > MAX_SITE_FILES) {
18001
+ throw new Error(
18002
+ `Too many files (more than ${MAX_SITE_FILES}); a site upload is at most ${MAX_SITE_FILES}.`
18003
+ );
18004
+ }
18005
+ if (bytes > MAX_UNZIP_BYTES) {
18006
+ throw new Error(
18007
+ `The upload expands past ${MAX_UNZIP_BYTES} bytes; send only the site's files.`
18008
+ );
18009
+ }
16962
18010
  }
16963
- list = list.map((f) => ({
18011
+ return list.map((f) => ({
16964
18012
  ...f,
16965
18013
  path: f.path.replace(/\\/g, "/").replace(/^\.?\//, "")
16966
18014
  })).filter(
16967
18015
  (f) => f.path && !f.path.startsWith("__MACOSX/") && !/(^|\/)\.DS_Store$/.test(f.path) && !f.path.split("/").includes("..")
16968
18016
  );
18017
+ }
18018
+ function staticSiteFiles(expanded) {
18019
+ let list = expanded;
16969
18020
  if (list.length === 0) {
16970
18021
  throw new Error(
16971
18022
  "No files to upload: pass the site in `files` or `attachments`."
@@ -17045,21 +18096,281 @@ function buildFormData(files) {
17045
18096
  return fd;
17046
18097
  }
17047
18098
 
18099
+ // src/api-call/headless-release.ts
18100
+ var BUILD_METADATA_SUFFIX = ".wix/build-metadata.json";
18101
+ function withoutEmptyStatics(files, build) {
18102
+ const dir = build.clientDir;
18103
+ if (dir === void 0) return { files, dropped: [] };
18104
+ const dropped = [];
18105
+ const kept = files.filter((f) => {
18106
+ const path = normalizePath(f.path);
18107
+ if (f.bytes.byteLength === 0 && path.startsWith(dir)) {
18108
+ dropped.push(path.slice(dir.length));
18109
+ return false;
18110
+ }
18111
+ return true;
18112
+ });
18113
+ return { files: kept, dropped };
18114
+ }
18115
+ var normalizePath = (p) => p.replace(/\\/g, "/").replace(/^\.?\//, "");
18116
+ function findHeadlessBuild(files) {
18117
+ const entries = new Map(files.map((f) => [normalizePath(f.path), f.bytes]));
18118
+ const metadataEntry = [...entries.keys()].find(
18119
+ (name) => name === BUILD_METADATA_SUFFIX || name.endsWith(`/${BUILD_METADATA_SUFFIX}`)
18120
+ );
18121
+ if (metadataEntry === void 0) return null;
18122
+ const zipRoot = metadataEntry.slice(
18123
+ 0,
18124
+ metadataEntry.length - BUILD_METADATA_SUFFIX.length
18125
+ );
18126
+ let metadata;
18127
+ try {
18128
+ metadata = JSON.parse(
18129
+ new TextDecoder().decode(entries.get(metadataEntry))
18130
+ );
18131
+ } catch {
18132
+ throw new Error(`${BUILD_METADATA_SUFFIX} is not valid JSON.`);
18133
+ }
18134
+ if (typeof metadata.appManifestPath !== "string") {
18135
+ throw new Error(
18136
+ `${BUILD_METADATA_SUFFIX} has no appManifestPath; send the build as the Wix hosting adapter wrote it (.wix/ and dist/ zipped from the project root).`
18137
+ );
18138
+ }
18139
+ const manifestPath = resolveZipPath(
18140
+ metadata.appManifestPath,
18141
+ zipRoot,
18142
+ entries
18143
+ );
18144
+ if (manifestPath === void 0) {
18145
+ throw new Error(
18146
+ `The build's app manifest (${metadata.appManifestPath}) is not in the upload; zip .wix/ and dist/ together from the project root after \`astro build\`.`
18147
+ );
18148
+ }
18149
+ const builderRoot = metadata.appManifestPath.slice(
18150
+ 0,
18151
+ metadata.appManifestPath.length - (manifestPath.length - zipRoot.length)
18152
+ );
18153
+ const clientDir = metadata.clientDir === void 0 ? void 0 : toZipDir(metadata.clientDir, builderRoot, zipRoot);
18154
+ const serverDir = metadata.serverDir === void 0 ? void 0 : toZipDir(metadata.serverDir, builderRoot, zipRoot);
18155
+ const clientFiles = [];
18156
+ const serverFiles = [];
18157
+ for (const [name, bytes] of entries) {
18158
+ if (name === manifestPath) continue;
18159
+ if (serverDir !== void 0 && name.startsWith(serverDir)) {
18160
+ serverFiles.push({ path: name.slice(serverDir.length), bytes });
18161
+ } else if (clientDir !== void 0 && name.startsWith(clientDir)) {
18162
+ clientFiles.push({ path: name.slice(clientDir.length), bytes });
18163
+ }
18164
+ }
18165
+ if (clientFiles.length + serverFiles.length === 0) {
18166
+ throw new Error(
18167
+ "The build has no client or server output files; run `astro build` first and zip .wix/ and dist/ from the project root."
18168
+ );
18169
+ }
18170
+ if (serverFiles.length > 0 && !serverFiles.some((f) => f.path === "entry.mjs")) {
18171
+ throw new Error(
18172
+ "The server build has no entry.mjs, which the Wix hosting adapter (@wix/astro-wix-hosting-adapter) writes into the server folder on every build. Build with the project's own `astro build` (the adapter in astro.config) and zip .wix/ and dist/ from the project root as the build left them."
18173
+ );
18174
+ }
18175
+ const manifestText = new TextDecoder().decode(entries.get(manifestPath));
18176
+ return {
18177
+ metadata,
18178
+ root: zipRoot,
18179
+ manifestText,
18180
+ clientFiles,
18181
+ serverFiles,
18182
+ clientDir
18183
+ };
18184
+ }
18185
+ var NOT_SOURCE_TOP = /* @__PURE__ */ new Set(["dist", ".wix", ".astro", ".agents"]);
18186
+ var isSourcePath = (relative) => {
18187
+ const segments = relative.split("/");
18188
+ return !NOT_SOURCE_TOP.has(segments[0]) && !segments.some((s) => s === "node_modules" || s.startsWith(".env"));
18189
+ };
18190
+ function buildSourceFiles(files, build) {
18191
+ return files.flatMap((f) => {
18192
+ const path = normalizePath(f.path);
18193
+ if (!path.startsWith(build.root)) return [];
18194
+ const relative = path.slice(build.root.length);
18195
+ return relative && isSourcePath(relative) ? [{ path: relative, bytes: f.bytes }] : [];
18196
+ });
18197
+ }
18198
+ function resolveZipPath(builderPath, zipRoot, entries) {
18199
+ const segments = normalizePath(builderPath).split("/").filter((s) => s.length > 0);
18200
+ for (let k = 0; k < segments.length; k++) {
18201
+ const candidate = zipRoot + segments.slice(k).join("/");
18202
+ if (entries.has(candidate)) return candidate;
18203
+ }
18204
+ return void 0;
18205
+ }
18206
+ function toZipDir(builderDir, builderRoot, zipRoot) {
18207
+ const normalized = normalizePath(builderDir);
18208
+ const normalizedRoot = normalizePath(builderRoot);
18209
+ const relative = builderDir.startsWith(builderRoot) ? builderDir.slice(builderRoot.length) : normalized.startsWith(normalizedRoot) ? normalized.slice(normalizedRoot.length) : normalized;
18210
+ const dir = zipRoot + normalizePath(relative);
18211
+ return dir.endsWith("/") ? dir : `${dir}/`;
18212
+ }
18213
+
18214
+ // src/infra/redact-personal-data.ts
18215
+ var FREE_TEXT_INFO_PARAMS = [
18216
+ "userRequest",
18217
+ "siteDescription",
18218
+ "siteForm",
18219
+ "siteFunctionality",
18220
+ "agentIdentity",
18221
+ "wixAccess",
18222
+ "reason",
18223
+ "whyNotExecuteWixAPI",
18224
+ "editorRequest",
18225
+ "templateRequest"
18226
+ ];
18227
+ var EMAIL = /[A-Z0-9._%+-]+@[A-Z0-9-]+(?:\.[A-Z0-9-]+)*\.[A-Z]{2,}/gi;
18228
+ var NAMED_SECRET = /\b(passwords?|passwd|pwd|passcodes?|secrets?|tokens?|api[ _-]?keys?|access[ _-]?keys?|credentials?)(\s*[:=]\s*|\s+(?:is|was|to)\s+)(?:"[^"]*"|'[^']*'|`[^`]*`|[^\s"'`,;][^,;\n]*?(?=[.!?]\s|[,;\n]|$))/gi;
18229
+ var BEARER = /\bBearer\s+[A-Za-z0-9._~+/=-]+/gi;
18230
+ var JWT = /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g;
18231
+ var KEY_PREFIXES = /\b(?:sk-[A-Za-z0-9_-]{16,}|gh[pousr]_[A-Za-z0-9]{20,}|github_pat_\w{20,}|xox[abprs]-[A-Za-z0-9-]{10,}|AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{35})\b/g;
18232
+ var BACKTICK = "`";
18233
+ var UNIX_ROOT = String.raw`(?<![\w.:/~-])(?:~|/(?:Users|home|root|mnt|tmp|private|var|Volumes|workspace|work))`;
18234
+ var WINDOWS_ROOT = String.raw`(?<!\w)[A-Za-z]:(?=\\\w)`;
18235
+ var PATH_ROOT = `(?:${UNIX_ROOT}|${WINDOWS_ROOT})`;
18236
+ var QUOTED_PATH = new RegExp(
18237
+ String.raw`(?<![\w])(["'${BACKTICK}])${PATH_ROOT}[^"'${BACKTICK}\n]*\1`,
18238
+ "g"
18239
+ );
18240
+ var FOLDER = String.raw`[^\s/\\"'${BACKTICK},;()<>]+(?: [^\s/\\"'${BACKTICK},;()<>]+)*`;
18241
+ var LOCAL_PATH = new RegExp(
18242
+ String.raw`${PATH_ROOT}(?:[/\\]${FOLDER}(?=[/\\]))*[/\\][^\s"'${BACKTICK},;()<>]*`,
18243
+ "g"
18244
+ );
18245
+ var PHONE_CANDIDATE = /(?<![\w-])\+?\(?\d[\d\s().-]{7,}\d(?![\w-])/g;
18246
+ var DATE_START = /^\d{4}-\d{2}-\d{2}/;
18247
+ function redactPhone(match, offset, text) {
18248
+ if (DATE_START.test(match) || /^:\d{2}\b/.test(text.slice(offset + match.length))) {
18249
+ return match;
18250
+ }
18251
+ const digits = match.replace(/\D/g, "").length;
18252
+ return digits >= 9 && digits <= 15 ? "[phone]" : match;
18253
+ }
18254
+ function redactPersonalData(text) {
18255
+ return text.replace(EMAIL, "[email]").replace(BEARER, "Bearer [secret]").replace(JWT, "[secret]").replace(KEY_PREFIXES, "[secret]").replace(NAMED_SECRET, "$1$2[secret]").replace(QUOTED_PATH, "$1[path]$1").replace(LOCAL_PATH, "[path]").replace(PHONE_CANDIDATE, redactPhone);
18256
+ }
18257
+ function redactInfoParams(value) {
18258
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
18259
+ return value;
18260
+ }
18261
+ const record = value;
18262
+ if (!FREE_TEXT_INFO_PARAMS.some((key) => typeof record[key] === "string")) {
18263
+ return value;
18264
+ }
18265
+ const redacted = { ...record };
18266
+ for (const key of FREE_TEXT_INFO_PARAMS) {
18267
+ if (typeof redacted[key] === "string") {
18268
+ redacted[key] = redactPersonalData(redacted[key]);
18269
+ }
18270
+ }
18271
+ return redacted;
18272
+ }
18273
+ var MAX_LISTED_PATHS_CHARS = 2e3;
18274
+ function summarizeFileBundle(bundle) {
18275
+ let sections;
18276
+ try {
18277
+ sections = parseFileBundle(bundle).map((file) => ({
18278
+ path: file.path,
18279
+ bytes: file.bytes.length
18280
+ }));
18281
+ } catch {
18282
+ sections = [];
18283
+ }
18284
+ if (sections.length === 0) {
18285
+ return `${new TextEncoder().encode(bundle).length} bytes, no file sections`;
18286
+ }
18287
+ return formatFileSummary(sections);
18288
+ }
18289
+ function formatFileSummary(files) {
18290
+ const total = files.reduce((sum, file) => sum + file.bytes, 0);
18291
+ let listed = "";
18292
+ let shown = 0;
18293
+ for (const file of files) {
18294
+ const entry = `${shown ? ", " : ""}${file.path} (${file.bytes})`;
18295
+ if (listed.length + entry.length > MAX_LISTED_PATHS_CHARS) break;
18296
+ listed += entry;
18297
+ shown++;
18298
+ }
18299
+ const more = files.length - shown;
18300
+ return `${files.length} file(s), ${total} bytes: ${listed}${more ? `${shown ? ", " : ""}and ${more} more` : ""}`;
18301
+ }
18302
+ function compactFileParams(value) {
18303
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
18304
+ return value;
18305
+ }
18306
+ const record = value;
18307
+ const files = record.files;
18308
+ if (typeof files === "string") {
18309
+ return { ...record, files: summarizeFileBundle(files) };
18310
+ }
18311
+ if (Array.isArray(files) && files.length > 0 && files.every(
18312
+ (entry) => entry && typeof entry === "object" && typeof entry.content === "string"
18313
+ )) {
18314
+ return {
18315
+ ...record,
18316
+ files: formatFileSummary(
18317
+ files.map((entry) => ({
18318
+ path: entry.path ?? "?",
18319
+ bytes: new TextEncoder().encode(entry.content).length
18320
+ }))
18321
+ )
18322
+ };
18323
+ }
18324
+ return value;
18325
+ }
18326
+ function prepareForBi(value) {
18327
+ return compactFileParams(redactInfoParams(value));
18328
+ }
18329
+ function serializeToolParamsForBi(args) {
18330
+ return JSON.stringify(
18331
+ Array.isArray(args) ? args.map(prepareForBi) : prepareForBi(args)
18332
+ );
18333
+ }
18334
+
17048
18335
  // src/api-call/upload-headless-website-files.ts
17049
18336
  var UPLOAD_HEADLESS_WEBSITE_FILES_TOOL_NAME = "UploadHeadlessWebsiteFiles";
17050
18337
  var HEADLESS_BUSINESS = "https://www.wixapis.com/headless-business-setup/v1/headless-business";
18338
+ var MAX_BUNDLE_BYTES = 100 * 1024 * 1024;
18339
+ var MAX_RELEASE_STATIC_FILES = 2500;
18340
+ var MAX_SOURCE_ZIP_BYTES = 64 * 1024 * 1024;
17051
18341
  function dropAttribution(clientName) {
17052
18342
  const slug = (clientName ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
17053
18343
  return `?campaign=mcp&agent=${encodeURIComponent(slug || "wix-mcp")}`;
17054
18344
  }
17055
18345
  var UPLOAD_HEADLESS_WEBSITE_FILES_PARAM_DESCRIPTIONS = {
17056
- files: "The site files as text, so file bytes never pass through a code string. Sections delimited by a line `=== FILE: <path> ===` (text, raw) or `=== FILE: <path> base64 ===` (binary, base64); `<path>` is relative to the site root and must include `index.html`. Use base64 for small binaries (icons, logos, fonts); large images/video belong behind a URL in the HTML, not in the bundle. Optional when `attachments` carries the files.",
17057
- attachments: "ChatGPT only. Files attached in the chat or created in your sandbox \u2014 any type: a zip of the whole site, HTML, CSS, JS, images. Pass each file as you have it, its file id or its /mnt/data path, and ChatGPT swaps it for { download_url, file_id, file_name?, mime_type? } before the call reaches Wix. No other client does this swap, so outside ChatGPT a path or file:// URL here fails at once; leave this out and use `files`, or, with a shell that reaches the internet, drop the files with curl as the StartDeployWebsite recipe shows. A zip is unpacked; a top-level folder wrapping the site is stripped. Prefer this over base64 in `files` whenever you have a file handle. Up to 10 MB per call.",
17058
- siteId: "The metaSiteId of the Wix Headless site to upload into: the one returned by the provision call of the StartDeployWebsite recipe for a new site, or of a site deployed earlier to replace its files. Every upload replaces the whole file set, so send all files; the URL stays the same.",
18346
+ files: "The site files as text, so file bytes never pass through a code string. Sections delimited by a line `=== FILE: <path> ===` (text, raw) or `=== FILE: <path> base64 ===` (binary, base64); `<path>` is relative to the site root; a static site must include `index.html`. Use base64 for small binaries (icons, logos, fonts); large images/video belong behind a URL in the HTML, not in the bundle. Optional when `attachments` carries the files. A Wix Astro build (`.wix/build-metadata.json` and `dist/` as `astro build` with the Wix hosting adapter wrote them, zipped from the project root) needs no `index.html`: it is released to the site's Wix hosting instead of dropped, and the project's other files in the upload are stored as its source when the site's repository accepts it.",
18347
+ attachments: fileParamNote(
18348
+ "for a static site use `files`, or drop the files as the StartDeployWebsite recipe shows; for a Wix Astro build from a shell that reaches the internet, zip it, call with `prepare: true` and its `size` and PUT the zips with curl"
18349
+ ) + " Any type: a zip of the whole site, HTML, CSS, JS, images. A zip is unpacked; a top-level folder wrapping the site is stripped. A Wix Astro build zipped with the whole project (minus `node_modules`) is released with its source stored when the site's repository accepts it. Prefer this over base64 in `files` whenever you have a file handle. Up to 10 MB per call.",
18350
+ prepare: "Phase one of the two-call upload of a Wix Astro build, for a shell that reaches the internet and no file handle (a sandbox whose network is limited to a few hosts cannot upload to the URLs): zip the build first, then `true` with its `size` returns presigned upload URLs for the build zip and, when the site's repository takes it, the project source zip, as `curl` commands to run verbatim, with the exact second call. The two calls: {siteId, prepare: true, size}, then {siteId, uploadKey, sourceUploadId?, expectedHeadRevisionId?, releaseNotes?}. No `files`, `attachments`, `uploadKey` or `sourceUploadId` on the first; `expectedHeadRevisionId` and `releaseNotes` given on the first are echoed into the second. Not for a static site: send its files in `files` or `attachments`, or drop them as the StartDeployWebsite recipe shows.",
18351
+ size: `Phase one, with \`prepare: true\` only, and required there: the build zip's length in bytes (\`wc -c < build.zip\`), 1 to ${MAX_BUNDLE_BYTES} (100 MB). The upload URL takes a body of exactly this length; a zip changed after the prepare needs a new prepare.`,
18352
+ uploadKey: "Phase two of the two-call upload: the `uploadKey` the `prepare` call returned, after the build zip was PUT to its URL. Releases that build to the site: {siteId, uploadKey, sourceUploadId?, expectedHeadRevisionId?, releaseNotes?}. Excludes `files` and `attachments`.",
18353
+ sourceUploadId: "Phase two: the `sourceUploadId` the `prepare` call returned, after the source zip was PUT to its URL. Stores the project source with the release. Needs `uploadKey`; leave it out when the prepare returned no source upload.",
18354
+ expectedHeadRevisionId: "The repository revision your project started from, so the source replaces only that and a commit made since (another chat, a Dev Machine) is never dropped unseen: the revision you downloaded the project at; for a project started here, the `headRevisionId` the `prepare` call returned. Leave it out only for a repository with nothing in it yet. Goes with `uploadKey`, or with `files`/`attachments` holding a build.",
18355
+ releaseNotes: "Optional: one line on what this release changes, at most 200 characters, kept with the deployment of a Wix Astro build, whether it comes in `files`/`attachments` or as the second call with `uploadKey` (given on the `prepare` call it is echoed into the second). A static drop keeps no notes.",
18356
+ siteId: "The metaSiteId of the Wix Headless site to upload into: the one returned by the provision call of the StartDeployWebsite recipe for a new site, or of a site deployed earlier to replace its files. Every upload replaces the whole file set, so send all files; the URL stays the same. A build released here replaces static pages dropped earlier, at the same URL.",
17059
18357
  calledStartDeployWebsite: "Whether you called StartDeployWebsite earlier in this conversation and are following the recipe it returned. Answer truthfully; it is recorded, not checked.",
17060
18358
  agentIdentity: START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS.agentIdentity,
17061
- reason: "One sentence: the user request that led here and what is being published."
18359
+ reason: `One sentence: the user request that led here and what is being published. ${NO_PERSONAL_DATA}`
17062
18360
  };
18361
+ var UPLOAD_RECIPE_SKILL_URL = "https://www.wix.com/skills/wix-manage/references/sites/upload-static-site.md";
18362
+ var DOMAIN_RECIPE_SKILL_URL = "https://www.wix.com/skills/wix-manage/references/domains/domain-search-purchase-and-connect.md";
18363
+ function formatInlineGuide(guide) {
18364
+ if (!guide || !guide.trim()) return "";
18365
+ return [
18366
+ "",
18367
+ "---",
18368
+ "",
18369
+ `Wix's headless guide, the build of a site that needs Wix business features (also at https://www.wix.com/headless/skill.md):`,
18370
+ "",
18371
+ guide.trim()
18372
+ ].join("\n");
18373
+ }
17063
18374
  function formatDropResult(r) {
17064
18375
  const shown = r.files.slice(0, 60);
17065
18376
  return [
@@ -17069,10 +18380,203 @@ function formatDropResult(r) {
17069
18380
  `Dashboard: https://manage.wix.com/dashboard/${r.metaSiteId}`,
17070
18381
  `${r.files.length} file(s): ${shown.join(", ")}${r.files.length > shown.length ? ", \u2026" : ""}`,
17071
18382
  "",
17072
- "Give the user the live URL and the dashboard link.",
17073
- `The upload step is done. For what comes next \u2014 changing the site later, connecting a domain, or adding a Wix backend (forms, bookings, a store) to this same site \u2014 continue with the guide you are following, from where it left off: the StartDeployWebsite recipe (${START_DEPLOY_WEBSITE_RECIPE_URL}) or, for a site built through StartHeadlessBuild, the headless guide (${HEADLESS_GUIDE_URL}).`
18383
+ `Give the user both links, each on its own line and named for what it is: the live site, and the site's dashboard. Then offer a custom domain, bought through Wix or one they already own (${DOMAIN_RECIPE_SKILL_URL}).`,
18384
+ "",
18385
+ `- To change this site later: "Change it later" in ${UPLOAD_RECIPE_SKILL_URL}, with this metaSiteId (${r.metaSiteId}). The same site every time, never a new one.`,
18386
+ `- When the site needs Wix business features (${WIX_BUSINESS_FEATURES}), a drop of static pages is not its build: a full Wix Headless site has those features run by Wix and its pages reading from them. That build is Wix's headless guide, https://www.wix.com/headless/skill.md (what StartHeadlessBuild returns; call it with startingPoint "an existing Wix Headless site", this site is one): it installs the solution on this site, seeds its content through the API, and puts up pages that fetch it, uploaded here again. A site with no business features is done.`
18387
+ ].join("\n");
18388
+ }
18389
+ function curlPut(zipName, put, fallback) {
18390
+ const headers = Object.entries(
18391
+ put.headers && Object.keys(put.headers).length ? put.headers : fallback
18392
+ );
18393
+ return [
18394
+ "curl -X PUT",
18395
+ ...headers.map(([k, v]) => `-H "${k}: ${v}"`),
18396
+ `--data-binary @${zipName} "${put.putUrl}"`
18397
+ ].join(" ");
18398
+ }
18399
+ var BUILD_ZIP_CONTENTS = "Build zip, from the project root after `astro build` with the Wix hosting adapter: `dist/` (with `dist/_wix/app-manifest.json`) and `.wix/build-metadata.json`.";
18400
+ var SOURCE_ZIP_CONTENTS = "Source zip, from the project root: the project minus `node_modules`, `dist`, `.wix`, `.astro`, `.env*` and `.agents`.";
18401
+ function formatPrepareResult(siteId, urls, size, given = {}) {
18402
+ const expiry = urls.expiresAt ? ` They expire at ${urls.expiresAt}.` : "";
18403
+ const secondCall = {
18404
+ siteId,
18405
+ uploadKey: urls.uploadKey
18406
+ };
18407
+ const zipHeaders = {
18408
+ "content-type": "application/zip",
18409
+ ...size ? { "content-length": String(size) } : {}
18410
+ };
18411
+ const lines = [
18412
+ `Upload URLs ready for site ${siteId}.${expiry} Two steps, then call this tool again.`,
18413
+ "",
18414
+ `1. ${BUILD_ZIP_CONTENTS} Its client folder (\`dist/client\`) holds at most ${MAX_RELEASE_STATIC_FILES} files (\`find dist/client -type f | wc -l\`); a larger build fails the release. Upload the zip you measured${size ? ` (${size} bytes)` : ""}; the URL takes a body of exactly that length, so a zip changed since needs a new prepare with its size:`,
18415
+ ` ${curlPut("build.zip", urls, zipHeaders)}`
18416
+ ];
18417
+ if (given.expectedHeadRevisionId)
18418
+ secondCall.expectedHeadRevisionId = given.expectedHeadRevisionId;
18419
+ if (urls.source) {
18420
+ const s = urls.source;
18421
+ secondCall.sourceUploadId = s.uploadId;
18422
+ if (!secondCall.expectedHeadRevisionId && s.headRevisionId)
18423
+ secondCall.expectedHeadRevisionId = s.headRevisionId;
18424
+ lines.push(
18425
+ "",
18426
+ `2. ${SOURCE_ZIP_CONTENTS} At most 64 MiB (\`wc -c < source.zip\`): a larger one fails the whole release. Leave large media and generated files out of it; if it is still larger, skip this upload and leave \`sourceUploadId\` out of the second call, and the site is released without its source. Upload it:`,
18427
+ ` ${curlPut("source.zip", s, { "content-type": "application/zip" })}`,
18428
+ ...s.expiresAt && s.expiresAt !== urls.expiresAt ? [` This URL expires at ${s.expiresAt}.`] : [],
18429
+ ...s.headRevisionId ? [
18430
+ ` The repository's head revision is ${s.headRevisionId}. The second call passes \`expectedHeadRevisionId\`: this head for a project started here; if you downloaded the project from the repository, the revision you downloaded it at instead, so a commit made since is never dropped.`
18431
+ ] : []
18432
+ );
18433
+ } else {
18434
+ lines.push(
18435
+ "",
18436
+ "2. The site's repository takes no source this time: the project's source is not uploaded, and the second call carries no `sourceUploadId`."
18437
+ );
18438
+ }
18439
+ if (given.releaseNotes)
18440
+ secondCall.releaseNotes = releaseNotesForService(given.releaseNotes);
18441
+ lines.push(
18442
+ "",
18443
+ `3. Call UploadHeadlessWebsiteFiles again with ${JSON.stringify(secondCall)} (plus \`calledStartDeployWebsite\`, \`agentIdentity\`, \`reason\`). No \`files\` or \`attachments\` on that call.`
18444
+ );
18445
+ if (secondCall.sourceUploadId) {
18446
+ const withoutSource = { ...secondCall };
18447
+ delete withoutSource.sourceUploadId;
18448
+ lines.push(
18449
+ ` If you skipped the source upload in step 2, call with ${JSON.stringify(withoutSource)} instead: a \`sourceUploadId\` whose zip was never uploaded fails the release.`
18450
+ );
18451
+ }
18452
+ return lines.join("\n");
18453
+ }
18454
+ function formatBundleReleaseResult(siteId, r, notes = []) {
18455
+ return [
18456
+ "Released; the site is live.",
18457
+ `Live URL: ${r.siteUrl ?? "(not returned)"}`,
18458
+ `metaSiteId: ${siteId} \u2014 pass it as \`siteId\` to release this site again, the same way. The URL stays.`,
18459
+ `Dashboard: https://manage.wix.com/dashboard/${siteId}`,
18460
+ `Deployment ${r.deploymentId ?? "(id not returned)"}.${r.revision?.id ? ` Source stored as revision ${r.revision.id}.` : ""}`,
18461
+ ...notes,
18462
+ "",
18463
+ "This release replaced whatever the site served before, at the same URL. A change to the site is a change to the project: edit, build if it is an Astro project, upload again.",
18464
+ "Give the user both links, each on its own line and named for what it is: the live site, and the site's dashboard."
17074
18465
  ].join("\n");
17075
18466
  }
18467
+ var startOver = (retry) => retry === "prepare" ? "call with `prepare: true` and the zip's `size` again, upload the zips to the new URLs, and release with the new `uploadKey`" : "call this tool again with the same files";
18468
+ var apiBody = (err) => err.response.data ? ` (${err.response.data.slice(0, 300)})` : "";
18469
+ var SITE_ACCESS_PERMISSION = "OAUTH_APP.APP_CREATE";
18470
+ function describeSiteAccessDenied(err, siteId, stagedKey) {
18471
+ if (!(err instanceof WixFetchError) || err.status !== 403 || !err.response.data.includes(SITE_ACCESS_PERMISSION)) {
18472
+ return null;
18473
+ }
18474
+ const site = siteId ? `site ${siteId}` : "this site";
18475
+ const outcome = stagedKey ? `Nothing was released; the build you uploaded is still staged under uploadKey ${stagedKey} and counts against the site's unreleased uploads, so do not call \`prepare\` again and do not send the files again. The same call fails the same way until the login changes; once connected as the owning account, release that staged upload by calling with \`uploadKey: "${stagedKey}"\` before preparing a new one.` : "Nothing was uploaded, and the same call fails the same way, so do not retry and do not stage another upload.";
18476
+ return `The connected Wix login has no rights on ${site}: Wix refused it the \`${SITE_ACCESS_PERMISSION}\` permission there. That means the site belongs to another Wix account, or this login's role on the owning account is too low (a Basic Team Member cannot upload). ${outcome} Ask the user which Wix account owns the site. Reconnecting Wix helps only if they pick that account, as its owner or an admin, at the consent screen; otherwise the owner has to give this login a higher role on the account, or move the site to it.${apiBody(err)}`;
18477
+ }
18478
+ function describeBundleUploadUrlError(err, siteId) {
18479
+ if (!(err instanceof WixFetchError)) {
18480
+ return err instanceof Error ? err.message : String(err);
18481
+ }
18482
+ const denied = describeSiteAccessDenied(err, siteId);
18483
+ if (denied) return denied;
18484
+ const body = apiBody(err);
18485
+ switch (err.status) {
18486
+ case 400:
18487
+ return `The upload URL was refused: \`size\` is the build zip's length in bytes, an integer from 1 to ${MAX_BUNDLE_BYTES} (100 MB). A larger build: leave out what the site does not serve (source maps, unused assets).${body}`;
18488
+ case 429:
18489
+ return `Too many unreleased uploads are waiting for this site: release one with its \`uploadKey\`, or wait for a running release to finish, then ask again.${body}`;
18490
+ default:
18491
+ return err.message;
18492
+ }
18493
+ }
18494
+ function deployAuthorization(siteHeaders) {
18495
+ const authorization3 = siteHeaders.Authorization ?? siteHeaders.authorization;
18496
+ return authorization3 ? { deployAuthorization: authorization3 } : {};
18497
+ }
18498
+ var RELEASE_NOTES_MAX_CHARS = 200;
18499
+ function releaseNotesForService(notes) {
18500
+ const chars = [...notes.replace(/\s+/g, " ").trim()];
18501
+ return chars.length <= RELEASE_NOTES_MAX_CHARS ? chars.join("") : `${chars.slice(0, RELEASE_NOTES_MAX_CHARS - 1).join("").trimEnd()}\u2026`;
18502
+ }
18503
+ function describeBundleReleaseError(err, uploadKey, retry = "prepare", siteId) {
18504
+ if (!(err instanceof WixFetchError)) {
18505
+ return err instanceof Error ? err.message : String(err);
18506
+ }
18507
+ const denied = describeSiteAccessDenied(err, siteId, uploadKey);
18508
+ if (denied) return denied;
18509
+ const body = apiBody(err);
18510
+ const code = err.response.data ?? "";
18511
+ const sameCall = `calling with \`uploadKey: "${uploadKey}"\`${retry === "resend" ? " (plus the `expectedHeadRevisionId` and `releaseNotes` you passed, if any; the source is stored with the next release)" : " and the same ids and `releaseNotes`"}`;
18512
+ const afterDrop = "If an earlier call with this `uploadKey` ended in a dropped connection, that release went through: the site is live with this build, so do not upload it again.";
18513
+ if (err.status >= 502 && err.status <= 504 && !code.includes("RELEASE_NOT_HELD") && !code.includes("{")) {
18514
+ return `The connection to Wix dropped while the release ran (HTTP ${err.status}); the release most likely went through. Do not call \`prepare\` again and do not send the files again. Wait two minutes, then check once by ${sameCall}: an answer that nothing is staged for this key, or that the repository moved, means the first release went through and the site is live. Any other answer is about the release itself.${body}`;
18515
+ }
18516
+ switch (err.status) {
18517
+ case 404:
18518
+ return `Nothing uploaded for uploadKey ${uploadKey}: the build zip was not PUT to its URL, the URL expired, or a release of it already went through (a staged zip is deleted once released). ${afterDrop} Otherwise, to try again, ${startOver(retry)}.${body}`;
18519
+ case 409:
18520
+ return `The project's source repository moved since the upload URLs were made; nothing was released by this call. ${afterDrop} Otherwise: ${retry === "prepare" ? "call with `prepare: true` and the zip's `size` again, re-upload the source zip to the new URL, and release with the new `uploadKey`, `sourceUploadId` and `expectedHeadRevisionId`." : "download the project again from the repository, carry your change over, and upload with `expectedHeadRevisionId` set to the revision you downloaded."}${body}`;
18521
+ case 403:
18522
+ return `Wix refused to deploy this build onto the site's project; nothing was released, and the same call will be refused again. Do not call \`prepare\` again. Tell the user the release is blocked on Wix's side and give them the request id from the error.${body}`;
18523
+ case 413:
18524
+ case 428:
18525
+ return `The build's server code is larger than the release can carry from here; nothing was released. Do not call \`prepare\` again. Tell the user the release is blocked on Wix's side and give them the request id from the error.${body}`;
18526
+ case 400:
18527
+ if (code.includes("EXPECTED_HEAD_REQUIRED")) {
18528
+ return `The site's repository already holds work, and the source replaces only what you started from; nothing was released. ${retry === "prepare" ? "Call with `prepare: true` and the zip's `size` again and pass the `headRevisionId` it returns as `expectedHeadRevisionId` (with its new `uploadKey` and `sourceUploadId`)." : "Call again with `expectedHeadRevisionId`: the revision you downloaded the project at, so the source replaces only that. To replace the repository with this upload on purpose, pass its current head."}${body}`;
18529
+ }
18530
+ if (code.includes("BUNDLE_TOO_LARGE")) {
18531
+ return `The build zip is over ${MAX_BUNDLE_BYTES} bytes (100 MB): leave out what the site does not serve (source maps, unused assets), then ${startOver(retry)}.${body}`;
18532
+ }
18533
+ if (code.includes("INVALID_RELEASE")) {
18534
+ return `The release was refused by Wix's deployment service; nothing was released. The staged upload is kept: call again with the same \`uploadKey\` and the field the message names fixed (release notes are cut to ${RELEASE_NOTES_MAX_CHARS} characters here).${body}`;
18535
+ }
18536
+ if (code.includes("INVALID_UPLOAD_KEY")) {
18537
+ return `\`uploadKey\` is the lowercase UUID the \`prepare\` call returned; pass it as it came.${body}`;
18538
+ }
18539
+ return `The uploaded zip was not accepted. Zip \`dist/\` and \`.wix/build-metadata.json\` from the project root after \`astro build\` with the Wix hosting adapter, then ${startOver(retry)}. A static site goes in \`files\` or \`attachments\` instead, or is dropped as the StartDeployWebsite recipe shows.${body}`;
18540
+ default:
18541
+ if (err.status >= 500) {
18542
+ const what = code.includes("RELEASE_NOT_HELD") ? "The release could not hold its upload" : `Wix's release step failed on its side (HTTP ${err.status})`;
18543
+ const limits = retry === "prepare" && !code.includes("RELEASE_NOT_HELD") ? ` A build over Wix's limits fails here with no reason given: more than ${MAX_RELEASE_STATIC_FILES} files in its client folder (\`find dist/client -type f | wc -l\`), or a source zip over 64 MiB (\`wc -c < source.zip\`). If either is over, a retry fails the same way: make the build or the source zip smaller, then call with \`prepare: true\` again.` : "";
18544
+ return `${what}; nothing was released.${limits} The staged upload is kept under uploadKey ${uploadKey}, so do not call \`prepare\` again${limits ? " for the same zips" : ""} and do not send the files again: every upload staged and not released counts against the site, which refuses new ones after a few. Retry once, after a minute, by ${sameCall}. If that fails too, stop and tell the user the release is blocked on Wix's side, with the request id from the error.${body}`;
18545
+ }
18546
+ return err.message;
18547
+ }
18548
+ }
18549
+ function validateBundleArgs(a) {
18550
+ const hasContent = !!a.files?.trim() || !!(a.attachments && a.attachments.length);
18551
+ if (a.prepare) {
18552
+ if (hasContent || a.uploadKey || a.sourceUploadId) {
18553
+ return "`prepare: true` goes with `siteId` and `size`: no `files`, `attachments`, `uploadKey` or `sourceUploadId` on that call. Example: {siteId, prepare: true, size}, then {siteId, uploadKey, sourceUploadId?, expectedHeadRevisionId?, releaseNotes?}.";
18554
+ }
18555
+ if (a.size === void 0) {
18556
+ return "`prepare: true` needs `size`: zip the build first and pass the zip's length in bytes (`wc -c < build.zip`).";
18557
+ }
18558
+ if (!Number.isInteger(a.size) || a.size < 1 || a.size > MAX_BUNDLE_BYTES) {
18559
+ return `\`size\` is the build zip's length in bytes, an integer from 1 to ${MAX_BUNDLE_BYTES} (100 MB); got ${a.size}.`;
18560
+ }
18561
+ return null;
18562
+ }
18563
+ if (a.size !== void 0) {
18564
+ return "`size` goes only with `prepare: true`: it is the length of the build zip that call makes an upload URL for.";
18565
+ }
18566
+ if (a.uploadKey) {
18567
+ if (hasContent) {
18568
+ return "`uploadKey` releases a build zip already uploaded to its URL: pass no `files` or `attachments` with it.";
18569
+ }
18570
+ return null;
18571
+ }
18572
+ if (a.sourceUploadId) {
18573
+ return "`sourceUploadId` goes with the `uploadKey` the `prepare` call returned.";
18574
+ }
18575
+ if (a.expectedHeadRevisionId && !hasContent) {
18576
+ return "`expectedHeadRevisionId` goes with `uploadKey`, or with `files`/`attachments` holding a build.";
18577
+ }
18578
+ return null;
18579
+ }
17076
18580
  async function readJson(res, what) {
17077
18581
  const text = await res.text();
17078
18582
  if (!res.ok)
@@ -17083,6 +18587,65 @@ async function readJson(res, what) {
17083
18587
  throw new Error(`${what} returned a non-JSON body: ${text.slice(0, 200)}`);
17084
18588
  }
17085
18589
  }
18590
+ async function putZip(fetchImpl, put, bytes, what) {
18591
+ const headers = Object.fromEntries(
18592
+ Object.entries(put.headers ?? { "content-type": "application/zip" }).filter(
18593
+ ([k]) => k.toLowerCase() !== "content-length"
18594
+ )
18595
+ );
18596
+ const res = await fetchImpl(put.putUrl, {
18597
+ method: "PUT",
18598
+ headers,
18599
+ body: bytes
18600
+ });
18601
+ if (!res.ok) {
18602
+ const text = await res.text().catch(() => "");
18603
+ throw new Error(
18604
+ `The ${what} upload was refused (${res.status}${text ? `: ${text.slice(0, 200)}` : ""}); call this tool again with the same files.`
18605
+ );
18606
+ }
18607
+ }
18608
+ async function releaseBuildByUrl(o) {
18609
+ const log = o.log ?? (() => {
18610
+ });
18611
+ const headers = { ...o.siteHeaders, "Content-Type": "application/json" };
18612
+ const urls = await readJson(
18613
+ await o.fetchImpl(`${HEADLESS_BUSINESS}/${o.siteId}/bundle-upload-url`, {
18614
+ method: "POST",
18615
+ headers,
18616
+ body: JSON.stringify({ size: o.siteZip.byteLength })
18617
+ }),
18618
+ "bundle-upload-url"
18619
+ );
18620
+ if (!urls.uploadKey || !urls.putUrl) {
18621
+ throw new Error("bundle-upload-url returned no uploadKey or putUrl.");
18622
+ }
18623
+ await putZip(o.fetchImpl, urls, o.siteZip, "site zip");
18624
+ o.onStaged?.(urls.uploadKey);
18625
+ log(
18626
+ `staged ${urls.uploadKey} (${o.siteZip.byteLength} bytes), source ${urls.source ? `offered, ${o.sourceFiles.length} file(s)` : "not offered"}`
18627
+ );
18628
+ const source = urls.source && o.sourceFiles.length > 0 ? urls.source : void 0;
18629
+ if (source) {
18630
+ await putZip(o.fetchImpl, source, zipStored(o.sourceFiles), "source zip");
18631
+ }
18632
+ return await readJson(
18633
+ await o.fetchImpl(`${HEADLESS_BUSINESS}/${o.siteId}/release-bundle`, {
18634
+ method: "POST",
18635
+ headers,
18636
+ body: JSON.stringify({
18637
+ uploadKey: urls.uploadKey,
18638
+ ...deployAuthorization(o.siteHeaders),
18639
+ ...o.releaseNotes ? { releaseNotes: releaseNotesForService(o.releaseNotes) } : {},
18640
+ ...source ? {
18641
+ sourceUploadId: source.uploadId,
18642
+ ...o.expectedHeadRevisionId ? { expectedHeadRevisionId: o.expectedHeadRevisionId } : {}
18643
+ } : {}
18644
+ })
18645
+ }),
18646
+ "release-bundle"
18647
+ );
18648
+ }
17086
18649
  function errorResult2(message) {
17087
18650
  return {
17088
18651
  isError: true,
@@ -17098,6 +18661,18 @@ function addUploadHeadlessWebsiteFilesTool(server, options) {
17098
18661
  const { authStrategy, toolDescriptions } = options;
17099
18662
  const clientName = () => options.clientName ?? server.clientName?.() ?? server.server?.getClientVersion?.()?.name;
17100
18663
  const fetchImpl = options.fetchImpl ?? ((u, i) => fetch(u, i));
18664
+ const guideFetch = options.guideFetch ?? (async (httpClient) => {
18665
+ if (!httpClient) return void 0;
18666
+ return fetchArticleContent(
18667
+ httpClient,
18668
+ HEADLESS_GUIDE_URL,
18669
+ "article",
18670
+ true,
18671
+ options.guide?.skillsPortalUrl,
18672
+ options.guide?.repo,
18673
+ options.guide?.commitHash
18674
+ );
18675
+ });
17101
18676
  const d = UPLOAD_HEADLESS_WEBSITE_FILES_PARAM_DESCRIPTIONS;
17102
18677
  const registered = server.tool(
17103
18678
  UPLOAD_HEADLESS_WEBSITE_FILES_TOOL_NAME,
@@ -17112,6 +18687,12 @@ function addUploadHeadlessWebsiteFilesTool(server, options) {
17112
18687
  mime_type: external_exports.string().optional()
17113
18688
  })
17114
18689
  ).optional().describe(d.attachments),
18690
+ prepare: external_exports.boolean().optional().describe(d.prepare),
18691
+ size: external_exports.number().int().optional().describe(d.size),
18692
+ uploadKey: external_exports.string().min(1).optional().describe(d.uploadKey),
18693
+ sourceUploadId: external_exports.string().min(1).optional().describe(d.sourceUploadId),
18694
+ expectedHeadRevisionId: external_exports.string().min(1).optional().describe(d.expectedHeadRevisionId),
18695
+ releaseNotes: external_exports.string().optional().describe(d.releaseNotes),
17115
18696
  siteId: external_exports.string().uuid("siteId must be a Wix metaSiteId (UUID)").describe(d.siteId),
17116
18697
  calledStartDeployWebsite: external_exports.boolean().describe(d.calledStartDeployWebsite),
17117
18698
  agentIdentity: external_exports.string().min(1).describe(d.agentIdentity),
@@ -17124,31 +18705,185 @@ function addUploadHeadlessWebsiteFilesTool(server, options) {
17124
18705
  const {
17125
18706
  files,
17126
18707
  attachments,
18708
+ prepare,
18709
+ size,
18710
+ uploadKey,
18711
+ sourceUploadId,
18712
+ expectedHeadRevisionId,
18713
+ releaseNotes,
17127
18714
  siteId,
17128
18715
  calledStartDeployWebsite,
17129
18716
  agentIdentity,
17130
18717
  reason
17131
18718
  } = rawArgs;
18719
+ const phase = prepare ? "prepare" : uploadKey ? "release-bundle" : "files";
17132
18720
  logger.log(
17133
- `[UploadHeadlessWebsiteFiles] siteId=${siteId} calledStartDeployWebsite=${calledStartDeployWebsite} agent="${agentIdentity}" files=${files?.length ?? 0} chars attachments=${attachments?.length ?? 0} reason="${reason}"`
18721
+ `[UploadHeadlessWebsiteFiles] siteId=${siteId} phase=${phase} calledStartDeployWebsite=${calledStartDeployWebsite} agent="${redactPersonalData(agentIdentity)}" files=${files?.length ?? 0} chars attachments=${attachments?.length ?? 0} reason="${redactPersonalData(reason)}"`
17134
18722
  );
18723
+ const invalid = validateBundleArgs(rawArgs);
18724
+ if (invalid) return errorResult2(invalid);
18725
+ if (prepare || uploadKey) {
18726
+ const siteHeaders = await authStrategy.getSiteAuthHeaders(siteId);
18727
+ const headers = {
18728
+ ...siteHeaders,
18729
+ "Content-Type": "application/json"
18730
+ };
18731
+ try {
18732
+ extra?.setToolBiParams?.({ siteId });
18733
+ if (prepare) {
18734
+ const urls = await readJson(
18735
+ await fetchImpl(
18736
+ `${HEADLESS_BUSINESS}/${siteId}/bundle-upload-url`,
18737
+ {
18738
+ method: "POST",
18739
+ headers,
18740
+ body: JSON.stringify({ size })
18741
+ }
18742
+ ),
18743
+ "bundle-upload-url"
18744
+ );
18745
+ if (!urls.uploadKey || !urls.putUrl) {
18746
+ throw new Error(
18747
+ "bundle-upload-url returned no uploadKey or putUrl."
18748
+ );
18749
+ }
18750
+ extra?.setToolBiParams?.({
18751
+ siteId,
18752
+ upload: { path: "prepare", sourceOffered: Boolean(urls.source) }
18753
+ });
18754
+ return {
18755
+ content: [
18756
+ {
18757
+ type: "text",
18758
+ text: formatPrepareResult(siteId, urls, size, {
18759
+ expectedHeadRevisionId,
18760
+ releaseNotes
18761
+ })
18762
+ }
18763
+ ]
18764
+ };
18765
+ }
18766
+ const released = await readJson(
18767
+ await fetchImpl(`${HEADLESS_BUSINESS}/${siteId}/release-bundle`, {
18768
+ method: "POST",
18769
+ headers,
18770
+ body: JSON.stringify({
18771
+ uploadKey,
18772
+ ...deployAuthorization(siteHeaders),
18773
+ ...sourceUploadId ? { sourceUploadId } : {},
18774
+ ...expectedHeadRevisionId ? { expectedHeadRevisionId } : {},
18775
+ ...releaseNotes ? { releaseNotes: releaseNotesForService(releaseNotes) } : {}
18776
+ })
18777
+ }),
18778
+ "release-bundle"
18779
+ );
18780
+ extra?.setToolBiParams?.({
18781
+ siteId,
18782
+ upload: {
18783
+ path: "release-bundle",
18784
+ sourceStored: Boolean(released.revision?.id)
18785
+ }
18786
+ });
18787
+ return {
18788
+ content: [
18789
+ {
18790
+ type: "text",
18791
+ text: formatBundleReleaseResult(siteId, released)
18792
+ }
18793
+ ]
18794
+ };
18795
+ } catch (err) {
18796
+ const message = err instanceof Error ? err.message : String(err);
18797
+ logger.log(`[UploadHeadlessWebsiteFiles] ${phase} error: ${message}`);
18798
+ rethrowIfUnauthorized(err);
18799
+ await authStrategy.enforceValidWixToken?.(err);
18800
+ return errorResult2(
18801
+ uploadKey ? describeBundleReleaseError(err, uploadKey, "prepare", siteId) : describeBundleUploadUrlError(err, siteId)
18802
+ );
18803
+ }
18804
+ }
17135
18805
  if (!files?.trim() && !(attachments && attachments.length)) {
17136
18806
  return errorResult2(
17137
- "Nothing to publish: pass the site in `files` (text bundle) or `attachments` (files from this chat or your sandbox)."
18807
+ "Nothing to publish: pass the site in `files` (text bundle) or `attachments` (files from this chat or your sandbox), or, for a Wix Astro build from a shell that reaches the internet, zip it and call with `prepare: true` and its `size`."
17138
18808
  );
17139
18809
  }
17140
- let siteFiles;
18810
+ let siteFiles = [];
18811
+ let uploaded = [];
18812
+ let given = [];
18813
+ let build = null;
17141
18814
  try {
17142
18815
  const fromAttachments = (await resolveAttachments(attachments, fetchImpl)).map((a) => ({ path: a.path, bytes: base64ToBytes(a.content) }));
17143
- siteFiles = await normalizeSiteFiles([
17144
- ...parseFileBundle(files),
17145
- ...fromAttachments
17146
- ]);
18816
+ given = [...parseFileBundle(files), ...fromAttachments];
18817
+ uploaded = await expandSiteFiles(given);
18818
+ build = findHeadlessBuild(uploaded);
18819
+ if (!build) siteFiles = staticSiteFiles(uploaded);
17147
18820
  } catch (e) {
17148
18821
  if (e instanceof AttachmentError || e instanceof Error)
17149
18822
  return errorResult2(e.message);
17150
18823
  throw e;
17151
18824
  }
18825
+ if (!build && expectedHeadRevisionId) {
18826
+ return errorResult2(
18827
+ "`expectedHeadRevisionId` applies to a Wix Astro build, whose source is stored in the site's repository; these files are a static site, which has none. Call again without it."
18828
+ );
18829
+ }
18830
+ if (build) {
18831
+ const siteHeaders = await authStrategy.getSiteAuthHeaders(siteId);
18832
+ let stagedKey;
18833
+ try {
18834
+ extra?.setToolBiParams?.({ siteId });
18835
+ const sourceFiles = buildSourceFiles(uploaded, build);
18836
+ const statics = withoutEmptyStatics(uploaded, build);
18837
+ const released = await releaseBuildByUrl({
18838
+ siteId,
18839
+ // One zip as given goes as is; anything else is zipped from the expanded files.
18840
+ siteZip: given.length === 1 && isZip(given[0]) && statics.dropped.length === 0 ? given[0].bytes : zipStored(statics.files),
18841
+ sourceFiles,
18842
+ expectedHeadRevisionId,
18843
+ releaseNotes,
18844
+ siteHeaders,
18845
+ fetchImpl,
18846
+ onStaged: (key) => {
18847
+ stagedKey = key;
18848
+ },
18849
+ log: (line) => logger.log(`[UploadHeadlessWebsiteFiles] release ${line}`)
18850
+ });
18851
+ const sourceStored = Boolean(released.revision?.id);
18852
+ extra?.setToolBiParams?.({
18853
+ siteId,
18854
+ upload: {
18855
+ path: "build",
18856
+ sourceStored,
18857
+ sourceFiles: sourceFiles.length,
18858
+ emptyStaticsDropped: statics.dropped.length
18859
+ }
18860
+ });
18861
+ const notes = [
18862
+ ...statics.dropped.length ? [
18863
+ `Left out, empty: ${statics.dropped.slice(0, 20).join(", ")} (the hosting serves no file of size 0).`
18864
+ ] : [],
18865
+ ...sourceStored ? [] : [
18866
+ sourceFiles.length ? "The project's source was not stored: the site's repository took none this time." : "The project's source was not stored: the upload held only the build. A later chat can continue this site only from its source; next time zip the whole project from its root, leaving out node_modules, .env files and the caches."
18867
+ ]
18868
+ ];
18869
+ return {
18870
+ content: [
18871
+ {
18872
+ type: "text",
18873
+ text: formatBundleReleaseResult(siteId, released, notes)
18874
+ }
18875
+ ]
18876
+ };
18877
+ } catch (err) {
18878
+ const message = err instanceof Error ? err.message : String(err);
18879
+ logger.log(`[UploadHeadlessWebsiteFiles] release error: ${message}`);
18880
+ rethrowIfUnauthorized(err);
18881
+ await authStrategy.enforceValidWixToken?.(err);
18882
+ return errorResult2(
18883
+ stagedKey ? describeBundleReleaseError(err, stagedKey, "resend", siteId) : describeBundleUploadUrlError(err, siteId)
18884
+ );
18885
+ }
18886
+ }
17152
18887
  const authHeaders = await authStrategy.getAccountAuthHeaders();
17153
18888
  try {
17154
18889
  const metaSiteId = siteId;
@@ -17165,6 +18900,18 @@ function addUploadHeadlessWebsiteFilesTool(server, options) {
17165
18900
  ),
17166
18901
  "drop"
17167
18902
  );
18903
+ extra?.setToolBiParams?.({
18904
+ siteId: metaSiteId,
18905
+ upload: { path: "drop", files: siteFiles.length }
18906
+ });
18907
+ let guide;
18908
+ try {
18909
+ guide = await guideFetch(extra?.httpClient);
18910
+ } catch (e) {
18911
+ logger.log(
18912
+ `[UploadHeadlessWebsiteFiles] guide fetch failed: ${e instanceof Error ? e.message : String(e)}`
18913
+ );
18914
+ }
17168
18915
  return {
17169
18916
  content: [
17170
18917
  {
@@ -17174,7 +18921,7 @@ function addUploadHeadlessWebsiteFilesTool(server, options) {
17174
18921
  siteUrl: dropped.siteUrl,
17175
18922
  uploadId: dropped.uploadId,
17176
18923
  files: siteFiles.map((f) => f.path)
17177
- })
18924
+ }) + (releaseNotes ? "\n\nRelease notes are kept for a Wix Astro build only; a static drop has none, so yours were not stored." : "") + formatInlineGuide(guide)
17178
18925
  }
17179
18926
  ]
17180
18927
  };
@@ -17183,7 +18930,7 @@ function addUploadHeadlessWebsiteFilesTool(server, options) {
17183
18930
  logger.log(`[UploadHeadlessWebsiteFiles] Error: ${message}`);
17184
18931
  rethrowIfUnauthorized(err);
17185
18932
  await authStrategy.enforceValidWixToken?.(err);
17186
- return errorResult2(message);
18933
+ return errorResult2(describeSiteAccessDenied(err, siteId) ?? message);
17187
18934
  }
17188
18935
  }
17189
18936
  );
@@ -17203,7 +18950,8 @@ function addSearchCodeModeTool(server, options) {
17203
18950
  authStrategy,
17204
18951
  disableTools = [],
17205
18952
  toolDescriptions,
17206
- maxResponseSize
18953
+ maxResponseSize,
18954
+ docsSite
17207
18955
  } = options;
17208
18956
  if (disableTools.includes(SEARCH_TOOL_NAME)) {
17209
18957
  return;
@@ -17215,7 +18963,8 @@ function addSearchCodeModeTool(server, options) {
17215
18963
  description,
17216
18964
  maxResponseSize,
17217
18965
  authStrategy,
17218
- codeModeFetch
18966
+ codeModeFetch,
18967
+ docsSite
17219
18968
  );
17220
18969
  }
17221
18970
  function addExecuteCodeModeTool(server, options) {
@@ -17241,6 +18990,7 @@ function addExecuteCodeModeTool(server, options) {
17241
18990
  });
17242
18991
  }
17243
18992
  var SEARCH_MAX_RESPONSE_CHARS = 64e3;
18993
+ var EXECUTE_MAX_RESPONSE_CHARS = 1e5;
17244
18994
  async function resolveLoopbackAuthorization(authStrategy) {
17245
18995
  try {
17246
18996
  return (await authStrategy?.getAccountAuthHeaders())?.Authorization;
@@ -17248,7 +18998,7 @@ async function resolveLoopbackAuthorization(authStrategy) {
17248
18998
  return void 0;
17249
18999
  }
17250
19000
  }
17251
- function registerSearchTool(server, codeModeBaseUrl, description, maxResponseSize, authStrategy, codeModeFetch = defaultCodeModeFetch) {
19001
+ function registerSearchTool(server, codeModeBaseUrl, description, maxResponseSize, authStrategy, codeModeFetch = defaultCodeModeFetch, docsSite) {
17252
19002
  const searchUrl = `${codeModeBaseUrl}/api/code-mode/search`;
17253
19003
  server.tool(
17254
19004
  SEARCH_TOOL_NAME,
@@ -17274,7 +19024,8 @@ function registerSearchTool(server, codeModeBaseUrl, description, maxResponseSiz
17274
19024
  "Content-Type": "application/json",
17275
19025
  ...authorization3 ? { Authorization: authorization3 } : {}
17276
19026
  },
17277
- body: JSON.stringify({ code })
19027
+ // docsSite only when set: without it the request is as before.
19028
+ body: JSON.stringify(docsSite ? { code, docsSite } : { code })
17278
19029
  });
17279
19030
  if (!resp.ok) {
17280
19031
  const errorBody = await resp.text();
@@ -17301,8 +19052,14 @@ function registerSearchTool(server, codeModeBaseUrl, description, maxResponseSiz
17301
19052
  ]
17302
19053
  };
17303
19054
  }
19055
+ const result = isMintlifyDocsSite(docsSite) && data.result !== void 0 ? JSON.parse(
19056
+ (await rewriteDocsLinks(
19057
+ toDevWixHostText(JSON.stringify(data.result)),
19058
+ { label: "search SearchWixAPISpec" }
19059
+ )).text
19060
+ ) : data.result;
17304
19061
  const text = truncateResponse(
17305
- data.result,
19062
+ result,
17306
19063
  maxResponseSize ?? SEARCH_MAX_RESPONSE_CHARS
17307
19064
  );
17308
19065
  return {
@@ -17335,10 +19092,13 @@ async function postExecute(executeUrl, body, authorization3, codeModeFetch = def
17335
19092
  }
17336
19093
  return await resp.json();
17337
19094
  }
17338
- function executeErrorResult(message) {
19095
+ function executeErrorResult(message, failedCalls) {
17339
19096
  return {
17340
19097
  isError: true,
17341
- content: [{ type: "text", text: `Execute error: ${message}` }]
19098
+ content: [{ type: "text", text: `Execute error: ${message}` }],
19099
+ // The failed calls as fields, request ids included. The text carries the
19100
+ // ids too, for clients that don't read structured results.
19101
+ ...failedCalls?.length ? { structuredContent: { failedCalls } } : {}
17342
19102
  };
17343
19103
  }
17344
19104
  function executeTextResult(message) {
@@ -17348,7 +19108,7 @@ function executeTextResult(message) {
17348
19108
  }
17349
19109
  function formatExecuteResult(data, maxResponseSize, reportBi) {
17350
19110
  if (data.error) {
17351
- return executeErrorResult(data.error);
19111
+ return executeErrorResult(data.error, data.failedCalls);
17352
19112
  }
17353
19113
  reportBi?.({
17354
19114
  originalLength: typeof data.result === "string" ? data.result.length : JSON.stringify(data.result)?.length
@@ -17368,7 +19128,13 @@ function formatExecuteResult(data, maxResponseSize, reportBi) {
17368
19128
  }
17369
19129
  return {
17370
19130
  content: [
17371
- { type: "text", text: truncateResponse(data.result, maxResponseSize) }
19131
+ {
19132
+ type: "text",
19133
+ text: truncateResponse(
19134
+ data.result,
19135
+ maxResponseSize ?? EXECUTE_MAX_RESPONSE_CHARS
19136
+ )
19137
+ }
17372
19138
  ]
17373
19139
  };
17374
19140
  }
@@ -17430,7 +19196,9 @@ function addExecuteTool(server, options) {
17430
19196
  mime_type: external_exports.string().optional()
17431
19197
  })
17432
19198
  ).optional().describe(
17433
- 'ChatGPT only. Files attached in the chat or created in your sandbox (any type: a zip of a site, HTML, images, PDFs): pass each file as you have it, its file id or its /mnt/data path, and ChatGPT swaps it for { download_url, file_id, file_name?, mime_type? } before the call reaches Wix. No other client does this swap, so outside ChatGPT a path or file:// URL here fails at once; leave this out and use `files`, or upload from your shell if it reaches the internet. The bytes are fetched here and appended to the `files` global as { path, content, encoding: "base64" } entries (path = file_name), so `wix.multipart()` and `wix.unzip()` work on them unchanged. Prefer this over base64 in `files` whenever you have a file handle. Up to 10 MB per call.'
19199
+ fileParamNote(
19200
+ "use `files`, or upload from your shell if it reaches the internet"
19201
+ ) + ' Any type: a zip of a site, HTML, images, PDFs. The bytes are fetched here and appended to the `files` global as { path, content, encoding: "base64" } entries (path = file_name), so `wix.multipart()` and `wix.unzip()` work on them unchanged. Prefer this over base64 in `files` whenever you have a file handle. Up to 10 MB per call.'
17434
19202
  ),
17435
19203
  // Visitor mode takes an agent-supplied token and pins the site; account/site
17436
19204
  // mode lets the agent pick the site.
@@ -17471,7 +19239,11 @@ function addExecuteTool(server, options) {
17471
19239
  }
17472
19240
  let attachmentFiles = [];
17473
19241
  try {
17474
- attachmentFiles = await resolveAttachments(attachments);
19242
+ attachmentFiles = await resolveAttachments(
19243
+ attachments,
19244
+ void 0,
19245
+ WIX_AI_ASSISTANT_ATTACHMENT_HOSTS
19246
+ );
17475
19247
  } catch (e) {
17476
19248
  if (e instanceof AttachmentError) return executeErrorResult(e.message);
17477
19249
  throw e;
@@ -17503,6 +19275,7 @@ function addExecuteTool(server, options) {
17503
19275
  sessionId,
17504
19276
  toolInvocationId,
17505
19277
  clientName: server.server?.getClientVersion?.()?.name,
19278
+ ...visitor ? { visitorMode: true } : {},
17506
19279
  // A visitor token is a site-scoped bearer token: sent as siteToken
17507
19280
  // (→ Authorization + wix-site-id) and as accountToken only to satisfy
17508
19281
  // the runtime's required field. Account/site mode uses strategy tokens.
@@ -19529,6 +21302,7 @@ function createWixToolkit(options) {
19529
21302
  readmeDocs,
19530
21303
  authStrategy,
19531
21304
  dynamicContextEnabled: true,
21305
+ includeCompanionApp: true,
19532
21306
  // README-free defaults first so consumer overrides still win.
19533
21307
  toolDescriptions: {
19534
21308
  ...descriptionsWithoutReadme,
@@ -19539,6 +21313,7 @@ function createWixToolkit(options) {
19539
21313
  addDocsTools(server, void 0, {
19540
21314
  authStrategy,
19541
21315
  dynamicContextEnabled: true,
21316
+ includeCompanionApp: true,
19542
21317
  toolDescriptions: descriptionsWithoutReadme
19543
21318
  });
19544
21319
  }
@@ -19579,7 +21354,9 @@ function createWixToolkit(options) {
19579
21354
  authStrategy,
19580
21355
  disableTools,
19581
21356
  toolDescriptions: effectiveToolDescriptions,
19582
- maxResponseSize
21357
+ maxResponseSize,
21358
+ // The connection's docs site (`docs.docsSite`) applies to spec search too.
21359
+ docsSite: docs?.docsSite
19583
21360
  });
19584
21361
  }
19585
21362
  if (requestedTools.has("ExecuteWixAPI") && !disableTools?.includes("ExecuteWixAPI")) {
@@ -19596,7 +21373,13 @@ function createWixToolkit(options) {
19596
21373
  addUploadHeadlessWebsiteFilesTool(server, {
19597
21374
  authStrategy,
19598
21375
  toolDescriptions: effectiveToolDescriptions,
19599
- clientName
21376
+ clientName,
21377
+ // The headless guide the drop reply carries is read the way the docs tools read it.
21378
+ guide: {
21379
+ skillsPortalUrl: docs?.skillsPortalUrl,
21380
+ repo: docs?.repo ?? docs?.skillsRepo,
21381
+ commitHash: docs?.commitHash ?? docs?.skillsPr
21382
+ }
19600
21383
  });
19601
21384
  }
19602
21385
  const requestedGraphqlTools = GRAPHQL_TOOL_NAMES.filter(
@@ -19659,7 +21442,7 @@ function createWixToolkit(options) {
19659
21442
  biLogger.updateDefaults({ _uuid: "" });
19660
21443
  panorama.transaction(toolName).start({
19661
21444
  toolInvocationId,
19662
- params: JSON.stringify(mergedArgs)
21445
+ params: serializeToolParamsForBi(mergedArgs)
19663
21446
  });
19664
21447
  }
19665
21448
  const startTime = performance.now();
@@ -19679,7 +21462,8 @@ function createWixToolkit(options) {
19679
21462
  cbResult,
19680
21463
  {
19681
21464
  returnedRows: toolBiParams.returnedRows,
19682
- originalLength: toolBiParams.originalLength
21465
+ originalLength: toolBiParams.originalLength,
21466
+ upload: toolBiParams.upload
19683
21467
  },
19684
21468
  { name: clientName }
19685
21469
  );
@@ -19694,7 +21478,7 @@ function createWixToolkit(options) {
19694
21478
  context: {
19695
21479
  toolName,
19696
21480
  toolInvocationId,
19697
- params: JSON.stringify(mergedArgs),
21481
+ params: serializeToolParamsForBi(mergedArgs),
19698
21482
  error: JSON.stringify(cbResult, null, 2)
19699
21483
  }
19700
21484
  }
@@ -19712,7 +21496,7 @@ function createWixToolkit(options) {
19712
21496
  context: {
19713
21497
  toolName,
19714
21498
  toolInvocationId,
19715
- params: JSON.stringify(mergedArgs),
21499
+ params: serializeToolParamsForBi(mergedArgs),
19716
21500
  error: errorBody
19717
21501
  }
19718
21502
  });
@@ -19740,7 +21524,7 @@ function createWixToolkit(options) {
19740
21524
  errorBody,
19741
21525
  toolInvocationId,
19742
21526
  toolName,
19743
- params: JSON.stringify(mergedArgs),
21527
+ params: serializeToolParamsForBi(mergedArgs),
19744
21528
  origin: nodeEnv,
19745
21529
  sessionId,
19746
21530
  executionResult: toolSucceeded ? "Success" : "Error",
@@ -19788,7 +21572,8 @@ function createWixToolkit(options) {
19788
21572
  const httpClient = new HttpClient();
19789
21573
  return await getDynamicContext(authStrategy, httpClient, defaults2, {
19790
21574
  partnerContextEnabled: true,
19791
- includeAccountContext: true
21575
+ includeAccountContext: true,
21576
+ includeCompanionApp: true
19792
21577
  }) || null;
19793
21578
  } catch {
19794
21579
  return null;