@wix/mcp 1.0.93 → 1.0.95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/build/bin-standalone.js +1200 -455
- package/build/cjs/index.cjs +3237 -1264
- package/build/cjs/toolkit.cjs +2177 -469
- package/build/dts/api-call/headless-release.d.ts +56 -0
- package/build/dts/api-call/headless-release.js +135 -0
- package/build/dts/api-call/index.d.ts +9 -1
- package/build/dts/api-call/index.js +19 -10
- package/build/dts/api-call/site-files.d.ts +42 -0
- package/build/dts/api-call/site-files.js +290 -0
- package/build/dts/api-call/upload-headless-website-files.d.ts +118 -0
- package/build/dts/api-call/upload-headless-website-files.js +624 -0
- package/build/dts/api-call/upload-image-tool.d.ts +2 -0
- package/build/dts/api-call/upload-image-tool.js +8 -4
- package/build/dts/bin.d.ts +1 -1
- package/build/dts/bin.js +4 -5
- package/build/dts/code-mode/attachments.js +1 -1
- package/build/dts/code-mode/index.d.ts +6 -1
- package/build/dts/code-mode/index.js +47 -13
- package/build/dts/code-mode/truncate-response.js +5 -1
- package/build/dts/config/default-config.d.ts +35 -9
- package/build/dts/config/default-config.js +348 -207
- package/build/dts/config/file-params.d.ts +22 -0
- package/build/dts/config/file-params.js +24 -0
- package/build/dts/config/param-descriptions.d.ts +14 -2
- package/build/dts/config/param-descriptions.js +34 -15
- package/build/dts/constants.d.ts +1 -0
- package/build/dts/constants.js +1 -0
- package/build/dts/docs/docs.d.ts +16 -3
- package/build/dts/docs/docs.js +211 -39
- package/build/dts/docs/fetch-article.d.ts +21 -0
- package/build/dts/docs/fetch-article.js +70 -5
- package/build/dts/docs/get-to-know.d.ts +7 -2
- package/build/dts/docs/get-to-know.js +37 -15
- package/build/dts/docs/mintlify-docs.d.ts +121 -0
- package/build/dts/docs/mintlify-docs.js +391 -0
- package/build/dts/docs/semanticSearch.d.ts +2 -1
- package/build/dts/docs/semanticSearch.js +14 -7
- package/build/dts/docs/start-deploy-website.d.ts +7 -6
- package/build/dts/docs/start-deploy-website.js +13 -8
- package/build/dts/docs/start-headless-build.d.ts +17 -0
- package/build/dts/docs/start-headless-build.js +60 -0
- package/build/dts/graphql/index.js +3 -1
- package/build/dts/import-claude-design-tool/index.js +2 -2
- package/build/dts/index.d.ts +3 -2
- package/build/dts/index.js +3 -2
- package/build/dts/infra/redact-personal-data.d.ts +4 -0
- package/build/dts/infra/redact-personal-data.js +79 -0
- package/build/dts/site-widget-tools/constants.d.ts +49 -1
- package/build/dts/site-widget-tools/constants.js +64 -7
- package/build/dts/site-widget-tools/index.d.ts +1 -0
- package/build/dts/site-widget-tools/index.js +1 -0
- package/build/dts/site-widget-tools/pick-images-tool/index.d.ts +14 -0
- package/build/dts/site-widget-tools/pick-images-tool/index.js +138 -0
- package/build/dts/site-widget-tools/search-site-templates-tool/index.js +33 -9
- package/build/dts/site-widget-tools/site-builder-tool/index.js +22 -2
- package/build/dts/site-widget-tools/site-creation-tool/index.d.ts +7 -1
- package/build/dts/site-widget-tools/site-creation-tool/index.js +22 -51
- package/build/dts/site-widget-tools/suggested-domain-tool/index.js +6 -3
- package/build/dts/support/index.d.ts +4 -0
- package/build/dts/support/index.js +13 -7
- package/build/dts/toolkit-entry.d.cts +19 -12
- package/build/dts/toolkit-entry.d.ts +19 -12
- package/build/dts/toolkit.d.ts +2 -2
- package/build/dts/toolkit.js +23 -7
- package/build/dts/wix-mcp-server.d.ts +11 -0
- package/build/dts/wix-mcp-server.js +28 -6
- package/build/dts/wixel/constants.d.ts +8 -1
- package/build/dts/wixel/constants.js +8 -1
- package/build/dts/wixel/index.d.ts +1 -1
- package/build/dts/wixel/index.js +2 -2
- package/build/dts/wixel/wixel-a2a-client.d.ts +83 -0
- package/build/dts/wixel/wixel-a2a-client.js +166 -0
- package/build/dts/wixel/wixel-bi.d.ts +2 -1
- package/build/dts/wixel/wixel-bi.js +2 -1
- package/build/dts/wixel/{external-agent-api.d.ts → wixel-session-entries.d.ts} +11 -33
- package/build/dts/wixel/{external-agent-api.js → wixel-session-entries.js} +33 -54
- package/build/dts/wixel/wixel-session-tools.d.ts +21 -26
- package/build/dts/wixel/wixel-session-tools.js +494 -599
- package/build/esm/index.js +3335 -1376
- package/build/esm/toolkit.js +2273 -565
- package/package.json +4 -4
- package/build/dts/docs/create-site-guide.d.ts +0 -3
- package/build/dts/docs/create-site-guide.js +0 -109
package/build/esm/toolkit.js
CHANGED
|
@@ -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`;
|
|
@@ -11702,10 +11707,16 @@ async function fetchArticle(articleUrl) {
|
|
|
11702
11707
|
}
|
|
11703
11708
|
var DOCS_URL_PREFIX = "https://dev.wix.com/docs/";
|
|
11704
11709
|
var SKILLS_REGISTRY_URL = new RegExp(
|
|
11705
|
-
"^https://(?:dev\\.wix\\.com(?:/docs)?|www\\.wix\\.com(?:/velo/reference)?)/skills(?:[/.?#]|$)"
|
|
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
|
|
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(
|
|
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(
|
|
11875
|
+
content = await fetchSkillsRegistryPage(
|
|
11876
|
+
httpClient,
|
|
11877
|
+
normalizeSkillsUrl(articleUrl)
|
|
11878
|
+
);
|
|
11828
11879
|
} else if (mode === "raw") {
|
|
11829
11880
|
let parsedUrl;
|
|
11830
11881
|
try {
|
|
@@ -12111,56 +12162,96 @@ function rethrowIfUnauthorized(error) {
|
|
|
12111
12162
|
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
12163
|
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
12164
|
|
|
12165
|
+
// src/config/file-params.ts
|
|
12166
|
+
var FILE_PARAM_CLIENTS = "OpenAI clients (ChatGPT, Codex)";
|
|
12167
|
+
function fileParamNote(otherwise) {
|
|
12168
|
+
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}.`;
|
|
12169
|
+
}
|
|
12170
|
+
|
|
12171
|
+
// src/site-widget-tools/constants.ts
|
|
12172
|
+
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";
|
|
12173
|
+
var SITE_CREATION_DEFAULT_ACTIONS = "add products, set up bookings, and adjust your content";
|
|
12174
|
+
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:
|
|
12175
|
+
* E-commerce / Retail: "add products, set up payment methods, and organize collections"
|
|
12176
|
+
* Restaurants / Food / Bakeries: "add menu items, set up online ordering, and collect table reservations"
|
|
12177
|
+
* Services / Fitness / Salons / Consultants: "set up booking slots, add service pricing, and manage appointments"
|
|
12178
|
+
* Creative / Portfolios / Photographers: "upload project galleries, feature client reviews, and customize contact forms"
|
|
12179
|
+
* Events / Weddings / Real Estate: "add event schedules, display listings, and manage RSVP forms"
|
|
12180
|
+
* General / Unspecified: "${SITE_CREATION_DEFAULT_ACTIONS}"
|
|
12181
|
+
If none of these patterns fit the site, infer 2\u20133 actions that suit what that kind of business would manage on its site.`;
|
|
12182
|
+
var SITE_BUILDER_CHOICE = {
|
|
12183
|
+
here: {
|
|
12184
|
+
label: "Build it here with me",
|
|
12185
|
+
pitch: "I build it in code right here, and you change anything by asking me"
|
|
12186
|
+
},
|
|
12187
|
+
editor: {
|
|
12188
|
+
label: "Design it in Wix's editor",
|
|
12189
|
+
pitch: "Wix designs a first version or you start from a template, then you edit it in Wix"
|
|
12190
|
+
},
|
|
12191
|
+
/** The editor's second step, in the widget and its reply. */
|
|
12192
|
+
editorStart: {
|
|
12193
|
+
wix: {
|
|
12194
|
+
label: "Let Wix design it",
|
|
12195
|
+
pitch: "Wix designs a first version from your description for you to edit"
|
|
12196
|
+
},
|
|
12197
|
+
template: {
|
|
12198
|
+
label: "Browse templates",
|
|
12199
|
+
pitch: "start from a ready-made design and edit it in Wix"
|
|
12200
|
+
}
|
|
12201
|
+
},
|
|
12202
|
+
/** Asked in chat when no widget is shown: who builds it. */
|
|
12203
|
+
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?",
|
|
12204
|
+
/** Asked next, only when the user chose Wix's editor. */
|
|
12205
|
+
editorQuestion: "Should Wix design a first version for you to edit, or do you want to start from a template?",
|
|
12206
|
+
/** What the widget sends as the user's message when they pick "Build it here with me". */
|
|
12207
|
+
hereFollowUp: "Build it here with me, in code, so I can keep changing it by asking you."
|
|
12208
|
+
};
|
|
12209
|
+
|
|
12114
12210
|
// src/config/default-config.ts
|
|
12115
12211
|
var AGENT_GOAL = dedent_default`
|
|
12116
|
-
You
|
|
12117
|
-
|
|
12212
|
+
You help the user with their Wix sites and account.
|
|
12213
|
+
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
12214
|
`;
|
|
12215
|
+
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.";
|
|
12216
|
+
var CALL_WIX_SITE_API_NO_EXECUTE_LINE = "If ExecuteWixAPI isn't in your tool list, use this tool.";
|
|
12119
12217
|
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
|
|
12121
|
-
"- METHOD CODE EXAMPLE BASED: BrowseWixRESTDocsMenu() or SearchWixRESTDocumentation() -> find relevant method -> read method article using ReadFullDocsArticle() to get method code examples -> call API
|
|
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
|
|
12218
|
+
"- 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",
|
|
12219
|
+
"- 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",
|
|
12220
|
+
"- 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
12221
|
];
|
|
12222
|
+
function directRouteLines(includeReadme, includeImageUploadGuidance) {
|
|
12223
|
+
return [
|
|
12224
|
+
...includeReadme ? [
|
|
12225
|
+
`- **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. WixSiteBuilder 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.`
|
|
12226
|
+
] : [],
|
|
12227
|
+
"- **Listing, showing or finding their Wix sites**: call ListWixSites directly.",
|
|
12228
|
+
...includeImageUploadGuidance ? [
|
|
12229
|
+
"- **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."
|
|
12230
|
+
] : []
|
|
12231
|
+
];
|
|
12232
|
+
}
|
|
12124
12233
|
function buildSystemReminderGuidelines(includeReadme, includeImageUploadGuidance) {
|
|
12125
|
-
const exception = includeReadme ? "**Exception:** " : "";
|
|
12126
12234
|
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
|
-
"**Exception \u2014 creating a new Wix site/website:** For a normal creation request \u2014 and only then \u2014 skip WixREADME and the docs search, and route straight to the Wix site-creation tools available to you; their descriptions carry the routing rules. Everything else starts with WixREADME as usual.",
|
|
12131
|
-
"If the user explicitly asks for a headless site, Wix Classic Editor site, or manual account/API-based site creation, call CreateWixBusinessGuide right away \u2014 these don't need the site's purpose first.",
|
|
12132
|
-
"If the user \u2014 or you, earlier in this conversation \u2014 already have a finished page or site in any form (HTML/CSS/JS files, a static build, a zip, another builder's export, a design you can export to HTML) and want it live on Wix, call StartDeployWebsite right away \u2014 not a site-creation tool, not the docs.",
|
|
12133
|
-
"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 normal site creation through ManageWixSite, CallWixSiteAPI, or ExecuteWixAPI."
|
|
12134
|
-
);
|
|
12135
|
-
}
|
|
12136
12235
|
lines.push(
|
|
12137
|
-
|
|
12236
|
+
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:"
|
|
12138
12237
|
);
|
|
12139
|
-
|
|
12140
|
-
lines.push(
|
|
12141
|
-
`${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.`
|
|
12142
|
-
);
|
|
12143
|
-
}
|
|
12144
|
-
if (includeReadme) {
|
|
12145
|
-
lines.push(
|
|
12146
|
-
"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."
|
|
12147
|
-
);
|
|
12148
|
-
}
|
|
12238
|
+
lines.push(...directRouteLines(includeReadme, includeImageUploadGuidance));
|
|
12149
12239
|
lines.push(
|
|
12150
|
-
`
|
|
12151
|
-
"
|
|
12240
|
+
`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"}.`,
|
|
12241
|
+
"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."
|
|
12152
12242
|
);
|
|
12153
12243
|
return lines.join("\n");
|
|
12154
12244
|
}
|
|
12155
12245
|
function buildSystemReminderFlows(includeReadme) {
|
|
12156
12246
|
const lines = ["Wix MCP Site Management Flows"];
|
|
12247
|
+
const noSkill = "no Wix skill covers the user's prompt/task -> ";
|
|
12157
12248
|
if (includeReadme) {
|
|
12158
12249
|
lines.push(
|
|
12159
12250
|
"With WixREADME tool:",
|
|
12160
|
-
"-
|
|
12251
|
+
"- 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",
|
|
12161
12252
|
DISCOVERY_FLOWS[0],
|
|
12162
|
-
|
|
12163
|
-
|
|
12253
|
+
`- 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`,
|
|
12254
|
+
`- 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`,
|
|
12164
12255
|
"Without WixREADME tool:"
|
|
12165
12256
|
);
|
|
12166
12257
|
}
|
|
@@ -12178,9 +12269,8 @@ function buildSystemReminder(includeReadme, includeImageUploadGuidance = true) {
|
|
|
12178
12269
|
);
|
|
12179
12270
|
const flows = buildSystemReminderFlows(includeReadme);
|
|
12180
12271
|
return dedent_default`
|
|
12181
|
-
<
|
|
12182
|
-
|
|
12183
|
-
A FAILURE TO DO SO WILL RESULT IN ERRORS AND CRITICAL ISSUES.
|
|
12272
|
+
<wix-usage-guide>
|
|
12273
|
+
How to work with Wix through these tools. Following these steps avoids failed or incorrect API calls.
|
|
12184
12274
|
<goal>
|
|
12185
12275
|
${AGENT_GOAL}
|
|
12186
12276
|
</goal>
|
|
@@ -12190,49 +12280,27 @@ function buildSystemReminder(includeReadme, includeImageUploadGuidance = true) {
|
|
|
12190
12280
|
<flow-description>
|
|
12191
12281
|
${flows}
|
|
12192
12282
|
</flow-description>
|
|
12193
|
-
</
|
|
12283
|
+
</wix-usage-guide>
|
|
12194
12284
|
`;
|
|
12195
12285
|
}
|
|
12196
12286
|
var SYSTEM_REMINDER = buildSystemReminder(true);
|
|
12287
|
+
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.";
|
|
12197
12288
|
var SITE_CREATION_COPY = {
|
|
12198
12289
|
templatePick: {
|
|
12199
12290
|
nonWidget: "After the user selects a template, call `CreateSiteFromTemplate`.",
|
|
12200
12291
|
widget: "The gallery creates the site when the user picks one."
|
|
12201
|
-
},
|
|
12202
|
-
unstatedMethod: {
|
|
12203
|
-
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",
|
|
12204
|
-
widget: "you MUST call `WixSiteCreation` and let them choose"
|
|
12205
|
-
},
|
|
12206
|
-
normalCreationRoute: {
|
|
12207
|
-
nonWidget: "route those through `WixSiteBuilder` or `SearchSiteTemplates`",
|
|
12208
|
-
widget: "route those through `WixSiteCreation`"
|
|
12209
12292
|
}
|
|
12210
12293
|
};
|
|
12211
|
-
var
|
|
12212
|
-
First decide which of two different things is being asked for:
|
|
12213
|
-
- The site is code the user or you control — hand-written HTML/CSS/JS, a static build, a zip, another AI builder's export, or a page or design you made earlier in this conversation — and it should go live on Wix. This is also the route whenever the user wants to keep working on the site here, with you: a Wix Headless site is a regular coded site, so you keep full control of the design and can change and redeploy it from this conversation at any time, and Wix business capabilities can be added to it as a backend when they are needed. Call StartDeployWebsite: it returns the recipe to host those exact files as a Wix Headless site (updates reuse the same site and URL; the drop page is only its fallback). Do not offer AI or templates for this and do not call any site-creation tool.
|
|
12214
|
-
- Wix should create the site — generated with AI from a description, or started from a template — and the user will design and edit it in Wix rather than through you. That is what the site-creation tools do; they never take files you already have.
|
|
12215
|
-
|
|
12216
|
-
For a new site 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.
|
|
12217
|
-
|
|
12218
|
-
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.
|
|
12219
|
-
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.
|
|
12220
|
-
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.
|
|
12221
|
-
With the purpose and the method known, that's enough to begin — route and refine the specifics with the user afterward.
|
|
12222
|
-
|
|
12223
|
-
Preferred question when the user has given no site context at all:
|
|
12224
|
-
Happy to help. To give you the right starting point, tell me a bit more about what you have in mind:
|
|
12225
|
-
- What's the site for? (e.g. an online clothing store, a yoga studio that takes bookings, a restaurant)
|
|
12226
|
-
- What's the vibe? (e.g. sleek and modern, cozy and rustic, bold and energetic)
|
|
12227
|
-
Give me a quick summary and I'll take it from there
|
|
12228
|
-
|
|
12229
|
-
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.`;
|
|
12294
|
+
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.`;
|
|
12230
12295
|
var toWidget = (text) => Object.values(SITE_CREATION_COPY).reduce(
|
|
12231
12296
|
(acc, { nonWidget, widget }) => acc.replaceAll(nonWidget, widget),
|
|
12232
12297
|
text
|
|
12233
12298
|
);
|
|
12299
|
+
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";
|
|
12234
12300
|
var EXECUTE_WIX_API_INTRO = dedent_default`
|
|
12235
|
-
|
|
12301
|
+
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.
|
|
12302
|
+
One function holds the whole task: chained calls, pagination, loops, branching, and the reads that feed its writes.
|
|
12303
|
+
Putting a site live or changing a headless site's files starts with StartDeployWebsite; building a new site in code with StartHeadlessBuild.
|
|
12236
12304
|
|
|
12237
12305
|
CODE CONTRACT:
|
|
12238
12306
|
- Pass an async function expression: \`async function() { ... }\` or \`async () => { ... }\`. Put every declaration, \`await\`, and \`return\` inside it, and do not invoke it yourself.
|
|
@@ -12317,7 +12385,7 @@ var ACCOUNT_SITE_EXECUTE_SCOPE = dedent_default`
|
|
|
12317
12385
|
declare const files: Array<{ path: string; content: string; encoding?: "base64" }>;
|
|
12318
12386
|
\`\`\`
|
|
12319
12387
|
|
|
12320
|
-
FILE UPLOADS: files never go inline in \`code\`; two params feed the \`files\` global. \`attachments
|
|
12388
|
+
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):
|
|
12321
12389
|
\`\`\`javascript
|
|
12322
12390
|
// files param:
|
|
12323
12391
|
// === FILE: index.html ===
|
|
@@ -12332,6 +12400,7 @@ var ACCOUNT_SITE_EXECUTE_SCOPE = dedent_default`
|
|
|
12332
12400
|
return res.data;
|
|
12333
12401
|
}
|
|
12334
12402
|
\`\`\`
|
|
12403
|
+
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.
|
|
12335
12404
|
A site zip passed through \`attachments\` is one base64 entry; unzip it and upload the entries:
|
|
12336
12405
|
\`\`\`javascript
|
|
12337
12406
|
// attachments: [site.zip] → files = [{ path: "site.zip", content: "<base64>", encoding: "base64" }]
|
|
@@ -12455,22 +12524,37 @@ var executeWixApiDescription = dedent_default`
|
|
|
12455
12524
|
|
|
12456
12525
|
${EXECUTE_WIX_API_MULTI_SITE_EXAMPLE}
|
|
12457
12526
|
`;
|
|
12458
|
-
function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true) {
|
|
12459
|
-
const
|
|
12527
|
+
function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true, { usageGuideOnce = false } = {}) {
|
|
12528
|
+
const usageGuide = buildSystemReminder(
|
|
12460
12529
|
includeReadme,
|
|
12461
12530
|
includeImageUploadGuidance
|
|
12462
12531
|
);
|
|
12463
|
-
|
|
12532
|
+
const reminder = includeReadme && usageGuideOnce ? "" : usageGuide;
|
|
12533
|
+
const descriptions = {
|
|
12534
|
+
// Named in SERVER_INSTRUCTIONS: when this tool's name or what it is for changes, update them to match.
|
|
12464
12535
|
WixREADME: dedent_default`
|
|
12465
12536
|
# Tool: WixREADME
|
|
12466
|
-
|
|
12467
|
-
|
|
12468
|
-
|
|
12469
|
-
${
|
|
12537
|
+
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.
|
|
12538
|
+
A Wix task includes reading or changing a site's or account's data: ${WIX_API_DATA}.
|
|
12539
|
+
These requests skip it and go straight to their own tool:
|
|
12540
|
+
${directRouteLines(includeReadme, includeImageUploadGuidance).join("\n")}
|
|
12541
|
+
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.
|
|
12542
|
+
It returns, in full:
|
|
12543
|
+
- **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.
|
|
12544
|
+
- **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.
|
|
12545
|
+
- **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.
|
|
12546
|
+
- **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.
|
|
12547
|
+
- **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.
|
|
12548
|
+
- **When a call fails**: the error says what was wrong and carries a Wix request id; fix that, never resend unchanged or guess.
|
|
12549
|
+
- **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>\`.
|
|
12550
|
+
- **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).
|
|
12551
|
+
- **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
|
|
12552
|
+
- **wix-headless-templates**: the code wix-headless-kit builds from, one folder per Wix Business Solution. https://www.wix.com/skills/headless-templates/
|
|
12553
|
+
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.
|
|
12470
12554
|
`,
|
|
12471
12555
|
SearchWixRESTDocumentation: dedent_default`
|
|
12472
12556
|
**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.
|
|
12473
|
-
It covers all Wix REST domains equally
|
|
12557
|
+
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.
|
|
12474
12558
|
**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.
|
|
12475
12559
|
If you can't find what you need, rephrase your search term or use a bigger maxResults value.
|
|
12476
12560
|
${reminder}
|
|
@@ -12478,7 +12562,7 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
|
|
|
12478
12562
|
ReadFullDocsArticle: dedent_default`
|
|
12479
12563
|
Fetches the full Wix docs article or method article with code examples for using the method.
|
|
12480
12564
|
Docs articles looks like this: https://dev.wix.com/docs/... and they can either be general docs articles or method articles.
|
|
12481
|
-
Also reads Wix skills pages — https://www.wix.com/skills/<skill>, https://dev.wix.com/docs/skills/<skill>, and the files they link (e.g. .../<skill>/references/<file>.md) — exactly as served.
|
|
12565
|
+
Also reads Wix skills pages — https://www.wix.com/skills/<skill>, https://dev.wix.com/docs/skills/<skill>, and the files they link (e.g. .../<skill>/references/<file>.md) — and Wix Headless agent pages under https://www.wix.com/headless/ (e.g. https://www.wix.com/headless/skill.md), exactly as served.
|
|
12482
12566
|
Take the URL from the user's message or an earlier tool result. To reach an article you have no URL for, search or browse the docs first.
|
|
12483
12567
|
For REST docs, use the URL as-is. For SDK docs, the URL SHOULD include ?apiView=SDK.
|
|
12484
12568
|
${reminder}
|
|
@@ -12492,6 +12576,7 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
|
|
|
12492
12576
|
`,
|
|
12493
12577
|
CallWixSiteAPI: dedent_default`
|
|
12494
12578
|
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.
|
|
12579
|
+
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}
|
|
12495
12580
|
Site-level: this tool reads and writes inside the single site named by the required \`siteId\`.
|
|
12496
12581
|
**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.
|
|
12497
12582
|
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"}]).
|
|
@@ -12506,6 +12591,7 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
|
|
|
12506
12591
|
Docs urls like https://dev.wix.com/docs/... are not api urls, if you want to read the docs, use the "ReadFullDocsArticle" tool
|
|
12507
12592
|
${reminder}
|
|
12508
12593
|
`,
|
|
12594
|
+
// Named in SERVER_INSTRUCTIONS: when this tool's name or what it is for changes, update them to match.
|
|
12509
12595
|
ListWixSites: dedent_default`
|
|
12510
12596
|
**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." : ""}
|
|
12511
12597
|
|
|
@@ -12526,13 +12612,14 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
|
|
|
12526
12612
|
${reminder}
|
|
12527
12613
|
`,
|
|
12528
12614
|
ManageWixSite: dedent_default`
|
|
12529
|
-
Use account level API in order to
|
|
12615
|
+
Use account level API in order to update a site.
|
|
12530
12616
|
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\`.
|
|
12531
12617
|
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"}]).
|
|
12532
12618
|
The API endpoint url param MUST ALWAYS be taken from the conversation context.
|
|
12533
12619
|
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.
|
|
12534
12620
|
${reminder}
|
|
12535
12621
|
`,
|
|
12622
|
+
// Named in SERVER_INSTRUCTIONS: when this tool's name or what it is for changes, update them to match.
|
|
12536
12623
|
UploadImageToWixSite: dedent_default`
|
|
12537
12624
|
Upload one or more images to a Wix site's Media Manager. Returns wixstatic.com URL and media ID.
|
|
12538
12625
|
Do NOT use ExecuteWixAPI or code execution for image uploads — use this tool directly.
|
|
@@ -12542,34 +12629,39 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
|
|
|
12542
12629
|
NEVER call this tool with a missing, empty, or guessed siteId — ask first instead.
|
|
12543
12630
|
|
|
12544
12631
|
Parameters — provide image input via image and/or imageUrls, OR imageBase64:
|
|
12545
|
-
• image (array):
|
|
12632
|
+
• 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.
|
|
12546
12633
|
• 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.
|
|
12547
12634
|
• imageBase64 (string): base64-encoded image + mimeType. One image at a time. Do NOT combine with image/imageUrls.
|
|
12548
12635
|
|
|
12549
12636
|
Delivering the bytes — this is where uploads most often fail:
|
|
12550
|
-
•
|
|
12637
|
+
• 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.
|
|
12551
12638
|
• 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.
|
|
12552
12639
|
`,
|
|
12553
|
-
|
|
12554
|
-
|
|
12555
|
-
|
|
12640
|
+
// Registered for Claude clients only, beside UploadImageToWixSite.
|
|
12641
|
+
PickImagesForWixMedia: dedent_default`
|
|
12642
|
+
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.
|
|
12643
|
+
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.
|
|
12644
|
+
`,
|
|
12645
|
+
ReportIssueToWix: dedent_default`
|
|
12646
|
+
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.
|
|
12556
12647
|
|
|
12557
12648
|
When to offer it (user-approved only — NEVER auto-send):
|
|
12558
|
-
- The user
|
|
12559
|
-
- The user complains
|
|
12560
|
-
-
|
|
12561
|
-
|
|
12562
|
-
When you offer, invite the user to add anything in their own words — whatever they give you goes into the
|
|
12563
|
-
|
|
12564
|
-
What to send —
|
|
12565
|
-
|
|
12566
|
-
|
|
12567
|
-
|
|
12568
|
-
|
|
12569
|
-
|
|
12570
|
-
|
|
12571
|
-
|
|
12572
|
-
|
|
12649
|
+
- The user asks to report something to Wix ("tell Wix that...", "report this", "send feedback"). Then confirm the wording and send.
|
|
12650
|
+
- 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.
|
|
12651
|
+
- 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?"
|
|
12652
|
+
|
|
12653
|
+
When you offer, invite the user to add anything in their own words — whatever they give you goes into the report verbatim.
|
|
12654
|
+
|
|
12655
|
+
What to send — that one issue, not the session:
|
|
12656
|
+
- What was being attempted, in a sentence.
|
|
12657
|
+
- 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).
|
|
12658
|
+
- What you expected instead, and the workaround if you found one.
|
|
12659
|
+
- Where the fault likely lives: API behavior / API schema / API reference docs / docs articles or examples / Wix tooling / unsure.
|
|
12660
|
+
- Only the context needed to reproduce it: agent/model, site id, the Wix area or API.
|
|
12661
|
+
No session summary or play-by-play, and nothing unrelated to the issue. Several distinct issues are separate reports, each approved by the user.
|
|
12662
|
+
|
|
12663
|
+
**IMPORTANT**: This tool actually sends the report to Wix. Confirm the final wording with the user and call it only after an explicit yes.
|
|
12664
|
+
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.
|
|
12573
12665
|
`,
|
|
12574
12666
|
BrowseWixRESTDocsMenu: dedent_default`
|
|
12575
12667
|
Browse the Wix REST API documentation menu hierarchy.
|
|
@@ -12591,28 +12683,11 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
|
|
|
12591
12683
|
${reminder}
|
|
12592
12684
|
`,
|
|
12593
12685
|
WixSiteCreation: dedent_default`
|
|
12594
|
-
|
|
12595
|
-
|
|
12596
|
-
Opens the in-chat Wix site creation chooser as an interactive widget where the user chooses between "Build with AI" and "Pick a template."
|
|
12597
|
-
|
|
12598
|
-
**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." : ""}
|
|
12599
|
-
|
|
12600
|
-
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.
|
|
12601
|
-
Do not guess, and never default to AI — even after the user answers a clarifying question about the site's purpose or features.
|
|
12602
|
-
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.
|
|
12603
|
-
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.
|
|
12604
|
-
After calling, reply with the message it returns, which presents both options and the next steps.
|
|
12605
|
-
|
|
12606
|
-
Do not call this if the user explicitly asks to build with AI; call \`WixSiteBuilder\` instead.
|
|
12607
|
-
Do not call this if the user asks for templates or Wix Studio; call \`SearchSiteTemplates\` instead.
|
|
12608
|
-
|
|
12609
|
-
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).
|
|
12610
|
-
|
|
12611
|
-
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.
|
|
12686
|
+
A new Wix site is written in code and uploaded: call StartDeployWebsite for the upload guide, or StartHeadlessBuild when the site needs Wix business features. WixSiteBuilder or SearchSiteTemplates only when the user asked for the Wix Harmony Editor, a template, Wix Studio or the Classic Editor.
|
|
12612
12687
|
`,
|
|
12613
12688
|
"import-claude-design-from-url": dedent_default`
|
|
12614
|
-
Import a design created in Claude's design tool into Wix.
|
|
12615
|
-
NOT for
|
|
12689
|
+
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.
|
|
12690
|
+
NOT for claude.ai share or artifact links, GitHub, Drive, chatgpt.com or any other URL, and not for files: those go to StartDeployWebsite, which returns the recipe to publish the files yourself.
|
|
12616
12691
|
`,
|
|
12617
12692
|
RunWixCliCommand: dedent_default`
|
|
12618
12693
|
Executes Wix CLI commands for Wix app development and management.
|
|
@@ -12638,6 +12713,13 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
|
|
|
12638
12713
|
`,
|
|
12639
12714
|
SearchWixWDSDocumentation: dedent_default`
|
|
12640
12715
|
Searches the Wix Design System (WDS) documentation.
|
|
12716
|
+
`,
|
|
12717
|
+
SearchWixHelpCenter: dedent_default`
|
|
12718
|
+
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.
|
|
12719
|
+
${HELP_CENTER_IS_FOR_PEOPLE}
|
|
12720
|
+
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.
|
|
12721
|
+
Not for building with code: for Wix APIs, SDK, CLI, apps, or Headless, use the matching developer documentation search.
|
|
12722
|
+
Results are excerpts; read a full article by passing its support.wix.com link to \`ReadFullDocsArticle\`.
|
|
12641
12723
|
`,
|
|
12642
12724
|
SearchWixSDKDocumentation: dedent_default`
|
|
12643
12725
|
Searches the Wix JavaScript SDK documentation.
|
|
@@ -12648,7 +12730,7 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
|
|
|
12648
12730
|
`,
|
|
12649
12731
|
SearchDevelopmentDocumentation: dedent_default`
|
|
12650
12732
|
Broad Wix development documentation (platform concepts, developer guides, getting started). **Last resort — development tasks only.**
|
|
12651
|
-
Skip it entirely for general, business, support, or Wix-editor questions
|
|
12733
|
+
Skip it entirely for general, business, support, or Wix-editor questions; for those, use \`SearchWixHelpCenter\`.
|
|
12652
12734
|
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.
|
|
12653
12735
|
`,
|
|
12654
12736
|
SearchWixHeadlessDocumentation: dedent_default`
|
|
@@ -12660,7 +12742,8 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
|
|
|
12660
12742
|
// `sdk` is simply absent on methods the SDK docs don't list.
|
|
12661
12743
|
SearchWixAPISpec: dedent_default`
|
|
12662
12744
|
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.
|
|
12663
|
-
|
|
12745
|
+
The spec covers every Wix product's methods and their data: ${WIX_API_DATA}.
|
|
12746
|
+
**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.
|
|
12664
12747
|
**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.
|
|
12665
12748
|
**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.**
|
|
12666
12749
|
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]\`.
|
|
@@ -13041,31 +13124,22 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
|
|
|
13041
13124
|
\`\`\`
|
|
13042
13125
|
`,
|
|
13043
13126
|
ExecuteWixAPI: executeWixApiDescription,
|
|
13127
|
+
// Named in SERVER_INSTRUCTIONS: when this tool's name or what it is for changes, update them to match.
|
|
13044
13128
|
WixSiteBuilder: dedent_default`
|
|
13045
|
-
|
|
13046
|
-
|
|
13047
|
-
|
|
13048
|
-
|
|
13049
|
-
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.
|
|
13050
|
-
Never default to this tool: if the user hasn't explicitly named AI, don't call this tool — ${SITE_CREATION_COPY.unstatedMethod.nonWidget}.
|
|
13051
|
-
|
|
13052
|
-
For templates or Wix Studio, call \`SearchSiteTemplates\`; for a specific template by \`metaSiteId\` or \`templateId\`, call \`CreateSiteFromTemplate\`; for a headless or classic Wix Editor site, or manual/account-API creation, call \`CreateWixBusinessGuide\`.
|
|
13053
|
-
|
|
13054
|
-
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.)
|
|
13129
|
+
A secondary site-creation tool, only for a user who asked for the Wix Harmony Editor or for Wix to design the site. 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.
|
|
13130
|
+
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.
|
|
13131
|
+
Call it when the user asked for the Wix Harmony Editor, or for Wix to design the site, in their own words or as their answer to your question. ${EDITOR_TAIL}
|
|
13055
13132
|
`,
|
|
13133
|
+
// Named in SERVER_INSTRUCTIONS: when this tool's name or what it is for changes, update them to match.
|
|
13056
13134
|
SearchSiteTemplates: dedent_default`
|
|
13057
|
-
|
|
13058
|
-
|
|
13059
|
-
|
|
13060
|
-
|
|
13061
|
-
Call this ONLY when the user's own words ask for a template or for Wix Studio (for example "start from a template", "show me some templates", "build it in Wix Studio") and you can already tell what the site is for. If they mention Wix Studio, search Studio templates; otherwise search Harmony templates.
|
|
13062
|
-
${SITE_CREATION_COPY.templatePick.nonWidget}
|
|
13063
|
-
|
|
13064
|
-
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}.`,
|
|
13135
|
+
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.
|
|
13136
|
+
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.
|
|
13137
|
+
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. ${EDITOR_TAIL} ${SITE_CREATION_COPY.templatePick.nonWidget}
|
|
13138
|
+
`,
|
|
13065
13139
|
CreateSiteFromTemplate: dedent_default`
|
|
13066
13140
|
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.
|
|
13067
13141
|
|
|
13068
|
-
Call this when the user provides a specific template by \`metaSiteId\` or \`templateId\`. Use this only when the user gives a valid \`metaSiteId\` or \`templateId\` directly. To browse or choose a template first, call \`SearchSiteTemplates
|
|
13142
|
+
Call this when the user provides a specific template by \`metaSiteId\` or \`templateId\`. Use this only when the user gives a valid \`metaSiteId\` or \`templateId\` directly. To browse or choose a template first, call \`SearchSiteTemplates\` (\`builder: classic\` for a Classic Editor site).
|
|
13069
13143
|
|
|
13070
13144
|
`,
|
|
13071
13145
|
SearchWixGraphQLOperations: dedent_default`
|
|
@@ -13094,113 +13168,212 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
|
|
|
13094
13168
|
Results: each root field fails independently — \`errors[].path\` names the failing field and \`errors[].extensions.message\` the cause (e.g. PERMISSION_DENIED); \`data\` still holds the fields that succeeded. Mutations return the created/updated entity — read the IDs from it and do not re-query to verify. Large results are truncated: narrow the selection or page size instead of re-running.
|
|
13095
13169
|
Set \`hasMutations: true\` for any document containing a mutation root field.
|
|
13096
13170
|
`,
|
|
13171
|
+
UploadHeadlessWebsiteFiles: dedent_default`
|
|
13172
|
+
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.
|
|
13173
|
+
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.
|
|
13174
|
+
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 (WixSiteBuilder, 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).
|
|
13175
|
+
`,
|
|
13176
|
+
// Named in SERVER_INSTRUCTIONS: when this tool's name or what it is for changes, update them to match.
|
|
13097
13177
|
StartDeployWebsite: dedent_default`
|
|
13098
|
-
|
|
13099
|
-
|
|
13100
|
-
|
|
13101
|
-
|
|
13102
|
-
NOT for: a screenshot, mockup or reference image of a site the user WANTS (nothing exists to deploy yet — that is site creation: WixSiteBuilder / templates); a Claude Design share 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).
|
|
13178
|
+
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.
|
|
13179
|
+
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.
|
|
13180
|
+
Returns the guide; follow it and do the work yourself. Upload with UploadHeadlessWebsiteFiles once the site exists; the drop page is only the fallback.
|
|
13181
|
+
NOT for: a user who asked for Wix's editor, the Wix Harmony Editor, a template, Wix Studio or the Classic Editor (WixSiteBuilder, 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
13182
|
`,
|
|
13104
|
-
|
|
13105
|
-
|
|
13106
|
-
|
|
13107
|
-
|
|
13108
|
-
|
|
13109
|
-
|
|
13110
|
-
|
|
13111
|
-
This tool provides complete documentation including:
|
|
13112
|
-
- Template selection (empty sites or designed templates)
|
|
13113
|
-
- Wix Editor vs Wix Studio options
|
|
13114
|
-
- Regular vs Headless site creation (CLI-based; returns setup instructions)
|
|
13115
|
-
- API endpoint details and parameters
|
|
13116
|
-
- Post-creation steps (OAuth for headless, publishing for regular sites)
|
|
13117
|
-
- App installation instructions
|
|
13118
|
-
|
|
13119
|
-
## After Reading This Documentation
|
|
13120
|
-
|
|
13121
|
-
**After reading this documentation:**
|
|
13122
|
-
1. Use ManageWixSite for account-level site creation API calls
|
|
13123
|
-
2. Use CallWixSiteAPI / ExecuteWixAPI for site-level follow-up API calls (like install apps if needed)`
|
|
13183
|
+
// Named in SERVER_INSTRUCTIONS: when this tool's name or what it is for changes, update them to match.
|
|
13184
|
+
StartHeadlessBuild: dedent_default`
|
|
13185
|
+
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.
|
|
13186
|
+
Call it right away: it only returns the guide, and the site's details are not needed first.
|
|
13187
|
+
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 (WixSiteBuilder, SearchSiteTemplates); a Claude Design URL (import-claude-design-from-url).
|
|
13188
|
+
`
|
|
13124
13189
|
};
|
|
13190
|
+
descriptions.SupportAndFeedback = descriptions.ReportIssueToWix;
|
|
13191
|
+
return descriptions;
|
|
13125
13192
|
}
|
|
13126
|
-
var defaultToolDescriptions = buildToolDescriptions(true);
|
|
13193
|
+
var defaultToolDescriptions = buildToolDescriptions(true, true, { usageGuideOnce: true });
|
|
13127
13194
|
var executeCodeModeToolDescriptions = {
|
|
13128
13195
|
ExecuteWixAPI: executeWixApiDescription
|
|
13129
13196
|
};
|
|
13197
|
+
var WIX_SKILLS_README_SECTION = dedent_default`
|
|
13198
|
+
## Wix skills
|
|
13199
|
+
|
|
13200
|
+
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:
|
|
13201
|
+
|
|
13202
|
+
- **wix-headless-kit**: builds a Wix Headless site on shipped, verified code. In the Wix plugin. Its contents are below.
|
|
13203
|
+
- **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.
|
|
13204
|
+
- **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.
|
|
13205
|
+
|
|
13206
|
+
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.
|
|
13207
|
+
|
|
13208
|
+
### How to read a skill
|
|
13209
|
+
|
|
13210
|
+
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.
|
|
13211
|
+
|
|
13212
|
+
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.
|
|
13213
|
+
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\`).
|
|
13214
|
+
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:
|
|
13215
|
+
- **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.
|
|
13216
|
+
- **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.
|
|
13217
|
+
- **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.
|
|
13218
|
+
- **Every file in the skill**: \`https://www.wix.com/skills/<skill>.manifest.json\` lists each path with its size, under a \`base\` URL.
|
|
13219
|
+
- **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\`.
|
|
13220
|
+
- **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.
|
|
13221
|
+
- **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.
|
|
13222
|
+
- **Not served**: a whole skill as one page (\`<skill>-full\`) returns 404; read the skill's files instead.
|
|
13223
|
+
|
|
13224
|
+
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:
|
|
13225
|
+
- on disk: \`<wix-manage folder>/references/sites/upload-static-site.md\`;
|
|
13226
|
+
- in the registry: https://www.wix.com/skills/manage/references/sites/upload-static-site.md;
|
|
13227
|
+
- in the docs: https://dev.wix.com/docs/api-reference/account-level/sites/skills/upload-a-website-or-html-files.
|
|
13228
|
+
|
|
13229
|
+
### wix-manage
|
|
13230
|
+
|
|
13231
|
+
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.
|
|
13232
|
+
|
|
13233
|
+
Ways to find and read a recipe, in this order:
|
|
13234
|
+
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.
|
|
13235
|
+
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.
|
|
13236
|
+
|
|
13237
|
+
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.
|
|
13238
|
+
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.
|
|
13239
|
+
- Accessibility: https://dev.wix.com/docs/api-reference/site/accessibility/skills.md
|
|
13240
|
+
- Analytics: https://dev.wix.com/docs/api-reference/business-management/analytics/skills.md
|
|
13241
|
+
- App Installation: https://dev.wix.com/docs/api-reference/business-management/app-installation/skills.md
|
|
13242
|
+
- App tools: https://dev.wix.com/docs/api-reference/app-management/app-tools/skills.md
|
|
13243
|
+
- Automations: https://dev.wix.com/docs/api-reference/business-management/automations/skills.md
|
|
13244
|
+
- Blog: https://dev.wix.com/docs/api-reference/business-solutions/blog/skills.md
|
|
13245
|
+
- Bookings: https://dev.wix.com/docs/api-reference/business-solutions/bookings/skills.md
|
|
13246
|
+
- Calendar: https://dev.wix.com/docs/api-reference/business-management/calendar/skills.md
|
|
13247
|
+
- CMS: https://dev.wix.com/docs/api-reference/business-solutions/cms/skills.md
|
|
13248
|
+
- Contacts: https://dev.wix.com/docs/api-reference/crm/members-contacts/contacts/skills.md
|
|
13249
|
+
- Domains: https://dev.wix.com/docs/api-reference/account-level/domains/skills.md
|
|
13250
|
+
- eCommerce: https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/skills.md
|
|
13251
|
+
- Events: https://dev.wix.com/docs/api-reference/business-solutions/events/skills.md
|
|
13252
|
+
- Forms: https://dev.wix.com/docs/api-reference/crm/forms/skills.md
|
|
13253
|
+
- Get Paid: https://dev.wix.com/docs/api-reference/business-management/get-paid/skills.md
|
|
13254
|
+
- Google Ads: https://dev.wix.com/docs/api-reference/business-management/marketing/ads/google-ads/skills.md
|
|
13255
|
+
- Google Business Profile: https://dev.wix.com/docs/api-reference/business-management/google-business-profile/skills.md
|
|
13256
|
+
- Google Search Console: https://dev.wix.com/docs/api-reference/business-management/seo/google-search-console/skills.md
|
|
13257
|
+
- Headless: https://dev.wix.com/docs/api-reference/business-management/headless/skills.md
|
|
13258
|
+
- Marketing: https://dev.wix.com/docs/api-reference/business-management/marketing/skills.md
|
|
13259
|
+
- Media: https://dev.wix.com/docs/api-reference/assets/media/skills.md
|
|
13260
|
+
- Pricing Plans: https://dev.wix.com/docs/api-reference/business-solutions/pricing-plans/skills.md
|
|
13261
|
+
- Restaurants: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/skills.md
|
|
13262
|
+
- Rich Content: https://dev.wix.com/docs/api-reference/assets/rich-content/skills.md
|
|
13263
|
+
- SEO: https://dev.wix.com/docs/api-reference/business-management/seo/skills.md
|
|
13264
|
+
- SEO redirects: https://dev.wix.com/docs/api-reference/business-management/seo/redirects/skills.md
|
|
13265
|
+
- Site Properties: https://dev.wix.com/docs/api-reference/business-management/site-properties/skills.md
|
|
13266
|
+
- Sites: https://dev.wix.com/docs/api-reference/account-level/sites/skills.md
|
|
13267
|
+
- Stores: https://dev.wix.com/docs/api-reference/business-solutions/stores/skills.md
|
|
13268
|
+
- Wix MCP: https://dev.wix.com/docs/api-reference/articles/ai-tools/wix-mcp/skills.md
|
|
13269
|
+
|
|
13270
|
+
### wix-headless-kit
|
|
13271
|
+
|
|
13272
|
+
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.
|
|
13273
|
+
|
|
13274
|
+
Paths are inside the skill folder; online, each is \`https://www.wix.com/skills/headless-kit/<path>\`.
|
|
13275
|
+
- 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.
|
|
13276
|
+
- \`SKILL.md\`: the steps of a build and the table of Business Solutions the kit ships.
|
|
13277
|
+
- \`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.
|
|
13278
|
+
- \`guides/capabilities.md\`: which Wix product owns which feature, and what the kit ships for it.
|
|
13279
|
+
- \`guides/existing-site.md\`: a new frontend for a Wix site that already has its content.
|
|
13280
|
+
- \`guides/migration.md\`: a site moving to code after "Go Headless" in its dashboard.
|
|
13281
|
+
- \`guides/reference-mode.md\`: a stack that cannot run the shipped React code (a static site, a server-rendered app in another language).
|
|
13282
|
+
- \`guides/feedback.md\`: sending the user's building-with-Wix feedback to Wix.
|
|
13283
|
+
- \`install/\`: the bootstrap, setup, deploy and templates scripts, and \`install/pins.json\` with the pinned CLI and templates versions.
|
|
13284
|
+
|
|
13285
|
+
### wix-headless-templates
|
|
13286
|
+
|
|
13287
|
+
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.
|
|
13288
|
+
|
|
13289
|
+
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:
|
|
13290
|
+
- \`<solution>/INSTRUCTIONS.md\`: the playbook, covering what ships, what you build, and each page's contract.
|
|
13291
|
+
- \`<solution>/app/\`: the typed React core: the data layer under \`wix/<solution>/\`, hooks and components.
|
|
13292
|
+
- \`<solution>/app-astro/\`: the Astro page skeletons for Wix-managed Astro sites (every solution but \`forms\`).
|
|
13293
|
+
- \`<solution>/project/\`: the whole runnable project for that solution.
|
|
13294
|
+
- \`<solution>/rest/\`: the same data layer as plain REST calls, for stacks that cannot run the React core.
|
|
13295
|
+
- \`<solution>/seed/SEED.md\`: how to seed that solution's content, with the script that creates it in the right order.
|
|
13296
|
+
|
|
13297
|
+
Shared by every solution:
|
|
13298
|
+
- \`shared/DESIGN.md\`: the design floor when the brief gives no design direction.
|
|
13299
|
+
- \`shared/CONTENT.md\`: the copy floor for every page written.
|
|
13300
|
+
- \`shared/SUPPLIED-CONTENT.md\`: content the user brings (a CSV, a menu, a folder of photos) as the seed plan.
|
|
13301
|
+
- \`shared/CUSTOM_OPERATIONS.md\`: operations beyond what the shipped code covers.
|
|
13302
|
+
- \`shared/rest/client.ts\`: the REST client the \`rest/\` layers use.
|
|
13303
|
+
|
|
13304
|
+
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.
|
|
13305
|
+
`;
|
|
13130
13306
|
var recipesIndexDoc = {
|
|
13131
13307
|
url: "https://dev.wix.com/docs/api-reference/articles/ai-tools/wix-mcp/skills/wix-management-recipes-index",
|
|
13132
13308
|
action: "include"
|
|
13133
13309
|
};
|
|
13134
13310
|
var README_INTRO = dedent_default`
|
|
13135
|
-
|
|
13311
|
+
# How to work with Wix through these tools
|
|
13136
13312
|
|
|
13137
|
-
|
|
13313
|
+
## Source of truth
|
|
13138
13314
|
|
|
13139
|
-
|
|
13140
|
-
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.
|
|
13141
|
-
</source-of-truth>
|
|
13315
|
+
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.
|
|
13142
13316
|
|
|
13143
|
-
|
|
13144
|
-
<goal>
|
|
13317
|
+
## Goal
|
|
13145
13318
|
`;
|
|
13146
|
-
var README_FLOWS_HEADING =
|
|
13147
|
-
|
|
13319
|
+
var README_FLOWS_HEADING = "## Wix MCP Flows";
|
|
13320
|
+
var README_FLOWS_SITE = dedent_default`
|
|
13321
|
+
### The site
|
|
13148
13322
|
|
|
13149
|
-
|
|
13323
|
+
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.
|
|
13150
13324
|
`;
|
|
13151
|
-
var
|
|
13152
|
-
|
|
13325
|
+
var README_FIND_SOURCES = dedent_default`
|
|
13326
|
+
### Find the method
|
|
13153
13327
|
|
|
13154
|
-
|
|
13155
|
-
|
|
13156
|
-
|
|
13157
|
-
|
|
13158
|
-
- SearchWixRESTDocumentation or BrowseWixRESTDocsMenu → ReadFullDocsArticle (no usable example) → SearchWixAPISpec for request schemas → ExecuteWixAPI
|
|
13328
|
+
A task may need several API methods. For each, take the first source that gives it:
|
|
13329
|
+
- A previous tool result in this conversation (not memory) already shows its request: go straight to the call.
|
|
13330
|
+
- 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).
|
|
13331
|
+
- 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.
|
|
13159
13332
|
|
|
13160
|
-
|
|
13333
|
+
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.
|
|
13334
|
+
`;
|
|
13335
|
+
var README_FLOWS_BODY = dedent_default`
|
|
13336
|
+
### The exact shape
|
|
13161
13337
|
|
|
13162
|
-
|
|
13163
|
-
- SearchWixRESTDocumentation — semantic search over the Wix REST docs by keywords or natural-language queries.
|
|
13164
|
-
- BrowseWixRESTDocsMenu — navigate the Wix REST docs by menu hierarchy; alternative to keyword search.
|
|
13165
|
-
- ReadFullDocsArticle — fetch a full docs article by its docs URL; returns prose, fields, and code examples.
|
|
13166
|
-
- SearchWixAPISpec — JS-sandboxed query over the REST API spec; returns request schema, response schema, and the executable \`method.publicUrl\`.
|
|
13167
|
-
- 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.
|
|
13338
|
+
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.
|
|
13168
13339
|
|
|
13169
|
-
|
|
13340
|
+
None of this comes from memory.
|
|
13170
13341
|
|
|
13171
|
-
|
|
13172
|
-
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.
|
|
13173
|
-
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.
|
|
13174
|
-
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.
|
|
13342
|
+
### The call, or the code that makes it
|
|
13175
13343
|
|
|
13176
|
-
|
|
13177
|
-
- **Request** (URL, HTTP method, body fields) — skip the probe if a code example already shows them; otherwise SearchWixAPISpec returns publicUrl + exact request shape.
|
|
13178
|
-
- **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.
|
|
13179
|
-
- **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.
|
|
13180
|
-
- Never guess any of this from memory.
|
|
13344
|
+
**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.
|
|
13181
13345
|
|
|
13182
|
-
|
|
13346
|
+
**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).
|
|
13183
13347
|
|
|
13184
|
-
|
|
13348
|
+
### When a call fails
|
|
13185
13349
|
|
|
13186
|
-
|
|
13187
|
-
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.
|
|
13188
|
-
</other-uses>
|
|
13189
|
-
</agent-mandatory-instructions>
|
|
13350
|
+
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.
|
|
13190
13351
|
`;
|
|
13191
|
-
|
|
13192
|
-
|
|
13193
|
-
|
|
13194
|
-
|
|
13195
|
-
|
|
13196
|
-
|
|
13197
|
-
|
|
13198
|
-
|
|
13199
|
-
|
|
13200
|
-
|
|
13201
|
-
|
|
13202
|
-
|
|
13203
|
-
|
|
13352
|
+
function buildReadmeDocs({
|
|
13353
|
+
recipesIndex = true
|
|
13354
|
+
} = {}) {
|
|
13355
|
+
return [
|
|
13356
|
+
{
|
|
13357
|
+
action: "text",
|
|
13358
|
+
content: [
|
|
13359
|
+
README_INTRO,
|
|
13360
|
+
"",
|
|
13361
|
+
AGENT_GOAL,
|
|
13362
|
+
"",
|
|
13363
|
+
README_FLOWS_HEADING,
|
|
13364
|
+
"",
|
|
13365
|
+
README_FLOWS_SITE,
|
|
13366
|
+
"",
|
|
13367
|
+
README_FIND_SOURCES,
|
|
13368
|
+
"",
|
|
13369
|
+
README_FLOWS_BODY
|
|
13370
|
+
].join("\n")
|
|
13371
|
+
},
|
|
13372
|
+
{ action: "text", content: WIX_SKILLS_README_SECTION },
|
|
13373
|
+
...recipesIndex ? [recipesIndexDoc] : []
|
|
13374
|
+
];
|
|
13375
|
+
}
|
|
13376
|
+
var defaultReadmeDocs = buildReadmeDocs();
|
|
13204
13377
|
function mergeConfig(base, overrides) {
|
|
13205
13378
|
return {
|
|
13206
13379
|
toolDescriptions: {
|
|
@@ -13242,8 +13415,7 @@ var widgetSiteCreationConfig = {
|
|
|
13242
13415
|
"WixSiteBuilder",
|
|
13243
13416
|
"WixSiteCreation",
|
|
13244
13417
|
"SearchSiteTemplates",
|
|
13245
|
-
"CreateSiteFromTemplate"
|
|
13246
|
-
"CreateWixBusinessGuide"
|
|
13418
|
+
"CreateSiteFromTemplate"
|
|
13247
13419
|
].map((tool) => [tool, toWidget(defaultToolDescriptions[tool])])
|
|
13248
13420
|
)
|
|
13249
13421
|
};
|
|
@@ -13387,6 +13559,9 @@ function renderApps(apps, knownApps) {
|
|
|
13387
13559
|
if (app.catalogVersion) {
|
|
13388
13560
|
line += ` \u2014 Catalog app version: **${app.catalogVersion}**`;
|
|
13389
13561
|
}
|
|
13562
|
+
if (app.invoicesVersion) {
|
|
13563
|
+
line += ` \u2014 Invoices app version: **${app.invoicesVersion}**`;
|
|
13564
|
+
}
|
|
13390
13565
|
lines.push(line);
|
|
13391
13566
|
}
|
|
13392
13567
|
return lines.join("\n");
|
|
@@ -13407,6 +13582,11 @@ function renderCustomAppsWithSkillsTools(apps) {
|
|
|
13407
13582
|
}
|
|
13408
13583
|
return lines.join("\n");
|
|
13409
13584
|
}
|
|
13585
|
+
function renderCompanionApp(installedApps) {
|
|
13586
|
+
const appId = installedApps.find((app) => app.companionApp === true)?.appId;
|
|
13587
|
+
if (!appId || !GUID_PATTERN.test(appId)) return "";
|
|
13588
|
+
return `The site's code lives in its companion app (ID: \`${appId}\`).`;
|
|
13589
|
+
}
|
|
13410
13590
|
function renderProperties(props) {
|
|
13411
13591
|
const sections = [];
|
|
13412
13592
|
const regionalFormat = [];
|
|
@@ -13433,7 +13613,7 @@ ${contact.join("\n")}`);
|
|
|
13433
13613
|
}
|
|
13434
13614
|
return sections.join("\n\n");
|
|
13435
13615
|
}
|
|
13436
|
-
function renderSite(site, index, knownApps) {
|
|
13616
|
+
function renderSite(site, index, knownApps, includeCompanionApp) {
|
|
13437
13617
|
const lines = [];
|
|
13438
13618
|
const heading = site.displayName ?? `Site ${index}`;
|
|
13439
13619
|
lines.push(`## ${index}. ${heading}`);
|
|
@@ -13478,6 +13658,13 @@ function renderSite(site, index, knownApps) {
|
|
|
13478
13658
|
lines.push("");
|
|
13479
13659
|
lines.push(customAppsWithSkillsToolsContent);
|
|
13480
13660
|
}
|
|
13661
|
+
const companionAppContent = includeCompanionApp ? renderCompanionApp(site.installedApps ?? []) : "";
|
|
13662
|
+
if (companionAppContent) {
|
|
13663
|
+
lines.push("");
|
|
13664
|
+
lines.push("### Site code (companion app)");
|
|
13665
|
+
lines.push("");
|
|
13666
|
+
lines.push(companionAppContent);
|
|
13667
|
+
}
|
|
13481
13668
|
lines.push("");
|
|
13482
13669
|
lines.push("---");
|
|
13483
13670
|
lines.push("");
|
|
@@ -13497,6 +13684,7 @@ function enrichDynamicContext(data, knownApps = {}, options = {}) {
|
|
|
13497
13684
|
const response = snakeToCamel(parsed);
|
|
13498
13685
|
const includePartnerContext = options.includePartnerContext ?? false;
|
|
13499
13686
|
const includeAccountContext = options.includeAccountContext ?? false;
|
|
13687
|
+
const includeCompanionApp = options.includeCompanionApp ?? false;
|
|
13500
13688
|
const sections = [];
|
|
13501
13689
|
const allSites = response.sites ?? [];
|
|
13502
13690
|
const maxSites = options.maxSites ?? Infinity;
|
|
@@ -13549,9 +13737,18 @@ function enrichDynamicContext(data, knownApps = {}, options = {}) {
|
|
|
13549
13737
|
);
|
|
13550
13738
|
sections.push("");
|
|
13551
13739
|
}
|
|
13740
|
+
const hasInvoicesVersion = sitesToRender.some(
|
|
13741
|
+
(site) => site.installedApps?.some((app) => app.appId && knownApps[app.appId] && app.invoicesVersion)
|
|
13742
|
+
);
|
|
13743
|
+
if (hasInvoicesVersion) {
|
|
13744
|
+
sections.push(
|
|
13745
|
+
"> **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."
|
|
13746
|
+
);
|
|
13747
|
+
sections.push("");
|
|
13748
|
+
}
|
|
13552
13749
|
if (sitesToRender.length) {
|
|
13553
13750
|
for (let i = 0; i < sitesToRender.length; i++) {
|
|
13554
|
-
sections.push(renderSite(sitesToRender[i], i + 1, knownApps));
|
|
13751
|
+
sections.push(renderSite(sitesToRender[i], i + 1, knownApps, includeCompanionApp));
|
|
13555
13752
|
}
|
|
13556
13753
|
}
|
|
13557
13754
|
if (!response.account && !siteCount && (!includePartnerContext || !hasPartnerContext(response.partnerContext))) {
|
|
@@ -13559,9 +13756,11 @@ function enrichDynamicContext(data, knownApps = {}, options = {}) {
|
|
|
13559
13756
|
}
|
|
13560
13757
|
return sections.join("\n");
|
|
13561
13758
|
}
|
|
13759
|
+
var GUID_PATTERN;
|
|
13562
13760
|
var init_enrich = __esm2({
|
|
13563
13761
|
"src/enrich.ts"() {
|
|
13564
13762
|
"use strict";
|
|
13763
|
+
GUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
13565
13764
|
}
|
|
13566
13765
|
});
|
|
13567
13766
|
init_enrich();
|
|
@@ -13624,6 +13823,7 @@ async function getDynamicContext(auth, httpClient, filter, options) {
|
|
|
13624
13823
|
} : options;
|
|
13625
13824
|
const includePartnerContext = contextOptions?.partnerContextEnabled ?? true;
|
|
13626
13825
|
const includeAccountContext = contextOptions?.includeAccountContext ?? true;
|
|
13826
|
+
const includeCompanionApp = contextOptions?.includeCompanionApp ?? false;
|
|
13627
13827
|
try {
|
|
13628
13828
|
const accountHeaders = await auth.getAccountAuthHeaders();
|
|
13629
13829
|
const noFilter = !filter?.siteId && !filter?.siteName;
|
|
@@ -13677,6 +13877,7 @@ async function getDynamicContext(auth, httpClient, filter, options) {
|
|
|
13677
13877
|
const context = enrichDynamicContext2(response.data, known_apps_default, {
|
|
13678
13878
|
includePartnerContext,
|
|
13679
13879
|
includeAccountContext,
|
|
13880
|
+
includeCompanionApp,
|
|
13680
13881
|
// Cap the account-wide view to the first N sites; a targeted fetch returns one site.
|
|
13681
13882
|
...noFilter ? { maxSites: MAX_SITES_FOR_DYNAMIC_CONTEXT, totalSiteCount } : {}
|
|
13682
13883
|
});
|
|
@@ -13730,6 +13931,21 @@ function isApiUrlAllowed(url) {
|
|
|
13730
13931
|
}
|
|
13731
13932
|
|
|
13732
13933
|
// src/config/param-descriptions.ts
|
|
13934
|
+
var REPORT_ISSUE_TO_WIX_PARAMS = {
|
|
13935
|
+
message: [
|
|
13936
|
+
"The report of one issue, sent to Wix as free text. Only this issue \u2014 no session summary, play-by-play or unrelated problems:",
|
|
13937
|
+
"- What was being attempted, in a sentence.",
|
|
13938
|
+
"- The tool or endpoint, the HTTP status and the error message.",
|
|
13939
|
+
"- What you expected instead, and the workaround if any.",
|
|
13940
|
+
"- Where the fault likely lives (API behavior / API schema / docs / Wix tooling / unsure), and whether you confirmed it.",
|
|
13941
|
+
"- The context needed to reproduce it: agent/model, site id, the Wix area or API.",
|
|
13942
|
+
"- The user's own words, verbatim, if they added any.",
|
|
13943
|
+
"NEVER include secrets \u2014 no bearer tokens, API keys, or credentials \u2014 and no personal data beyond what the report needs."
|
|
13944
|
+
].join("\n"),
|
|
13945
|
+
requestId: "Optional request id (x-wix-request-id) of the failed Wix API call this report is about."
|
|
13946
|
+
};
|
|
13947
|
+
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.";
|
|
13948
|
+
var WHY_NOT_EXECUTE_NO_EXECUTE = "if it isn't in your tool list, say so.";
|
|
13733
13949
|
var paramDescriptions = {
|
|
13734
13950
|
CallWixSiteAPI: {
|
|
13735
13951
|
siteId: "The id of the site selected using site selection tool",
|
|
@@ -13746,6 +13962,7 @@ var paramDescriptions = {
|
|
|
13746
13962
|
WQL_FILTER_RULE
|
|
13747
13963
|
].join("\n"),
|
|
13748
13964
|
reason: "One sentence explaining the original user request and why you are calling this API to complete it.",
|
|
13965
|
+
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}`,
|
|
13749
13966
|
sourceDocUrl: [
|
|
13750
13967
|
"The URL of the documentation or recipe where you found this API endpoint.",
|
|
13751
13968
|
"MAKE SURE THE ENDPOINT URL IS REALLY THERE AND YOUR ARE NOT GUESSING IT !!!",
|
|
@@ -13781,18 +13998,9 @@ var paramDescriptions = {
|
|
|
13781
13998
|
'Use ["other"] ONLY IF YOU HAVE A VERY GOOD REASON TO DO SO'
|
|
13782
13999
|
].join("\n")
|
|
13783
14000
|
},
|
|
13784
|
-
|
|
13785
|
-
|
|
13786
|
-
|
|
13787
|
-
"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.",
|
|
13788
|
-
`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.`,
|
|
13789
|
-
'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.',
|
|
13790
|
-
"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.",
|
|
13791
|
-
"Write it as a scannable few paragraphs or a short bulleted list \u2014 thorough on the friction, distilled rather than a transcript dump.",
|
|
13792
|
-
"NEVER include secrets \u2014 no bearer tokens, API keys, or credentials \u2014 and no personal data beyond what the feedback needs."
|
|
13793
|
-
].join("\n"),
|
|
13794
|
-
requestId: "Optional request id (x-wix-request-id) returned from a failed Wix API call this feedback relates to."
|
|
13795
|
-
},
|
|
14001
|
+
ReportIssueToWix: REPORT_ISSUE_TO_WIX_PARAMS,
|
|
14002
|
+
// The tool's former name, kept for importers that read it by that key.
|
|
14003
|
+
SupportAndFeedback: REPORT_ISSUE_TO_WIX_PARAMS,
|
|
13796
14004
|
SearchWixWDSDocumentation: {
|
|
13797
14005
|
searchTerm: "The search term to search for in the Wix Design System Documentation",
|
|
13798
14006
|
maxResults: "The maximum number of results to return, default is 10, max is 15"
|
|
@@ -13831,8 +14039,13 @@ var paramDescriptions = {
|
|
|
13831
14039
|
maxResults: "The maximum number of results to return, default is 10, max is 15",
|
|
13832
14040
|
reason: "One sentence describing the original user request and the task you are trying to accomplish with this search."
|
|
13833
14041
|
},
|
|
14042
|
+
SearchWixHelpCenter: {
|
|
14043
|
+
searchTerm: 'What the site owner wants to do or fix, in plain words (e.g. "connect a domain bought elsewhere", "cancel a Premium plan")',
|
|
14044
|
+
maxResults: "The maximum number of articles to return (1\u201315, default 15). Each article is returned with up to 60 lines.",
|
|
14045
|
+
reason: "One sentence describing the original user request and the task you are trying to accomplish with this search."
|
|
14046
|
+
},
|
|
13834
14047
|
ReadFullDocsArticle: {
|
|
13835
|
-
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/...) are read as-is, including their referenced files."
|
|
14048
|
+
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."
|
|
13836
14049
|
},
|
|
13837
14050
|
ReadFullDocsMethodSchema: {
|
|
13838
14051
|
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).",
|
|
@@ -13848,7 +14061,7 @@ var paramDescriptions = {
|
|
|
13848
14061
|
},
|
|
13849
14062
|
UploadImageToWixSite: {
|
|
13850
14063
|
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.",
|
|
13851
|
-
image: "
|
|
14064
|
+
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.`,
|
|
13852
14065
|
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.",
|
|
13853
14066
|
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.',
|
|
13854
14067
|
mimeType: 'MIME type of the image (e.g. "image/jpeg", "image/png", "image/webp"). Required when imageBase64 does not include a data URL prefix.',
|
|
@@ -13872,7 +14085,7 @@ var paramDescriptions = {
|
|
|
13872
14085
|
operationName: "Operation to run when the document defines more than one named operation.",
|
|
13873
14086
|
siteId: "The Wix site to run against. Required for site-level operations (anything reading or writing inside a site \u2014 products, orders, contacts, bookings, CMS, blog, members ...). Resolve it with GetSiteContext or ListWixSites; never pass a guessed value.",
|
|
13874
14087
|
scope: '"site" (default when siteId is given) runs with a site-scoped credential; "account" runs account-level operations (sites, account settings) and ignores siteId.',
|
|
13875
|
-
hasMutations: "
|
|
14088
|
+
hasMutations: "True if the document contains a mutation root field in any of its operations; false if it contains only queries. A transient server error is retried only when this is false.",
|
|
13876
14089
|
reason: "One sentence explaining the original user request and why you are running this GraphQL operation to complete it."
|
|
13877
14090
|
},
|
|
13878
14091
|
RunWixCliCommand: {
|
|
@@ -13885,10 +14098,315 @@ var paramDescriptions = {
|
|
|
13885
14098
|
};
|
|
13886
14099
|
var desc = (toolName) => paramDescriptions[toolName];
|
|
13887
14100
|
|
|
14101
|
+
// src/docs/mintlify-docs.ts
|
|
14102
|
+
var MINTLIFY_LINKS_DOCS_SITE = "mintlify-links";
|
|
14103
|
+
var MINTLIFY_DOCS_SITE = "mintlify";
|
|
14104
|
+
var MINTLIFY_HOST = "https://wix-internal.mintlify.site";
|
|
14105
|
+
var MINTLIFY_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
14106
|
+
"wix-internal.mintlify.site",
|
|
14107
|
+
"wix-internal.mintlify.app"
|
|
14108
|
+
]);
|
|
14109
|
+
var DEV_WIX_DOCS = "https://dev.wix.com/docs";
|
|
14110
|
+
var PORTAL_SLUG_TO_TAB = {
|
|
14111
|
+
rest: "api-reference",
|
|
14112
|
+
"mintlify-sandbox": "sandbox"
|
|
14113
|
+
};
|
|
14114
|
+
var TAB_TO_PORTAL_SLUG = {
|
|
14115
|
+
sandbox: "mintlify-sandbox"
|
|
14116
|
+
};
|
|
14117
|
+
var MINTLIFY_TABS = /* @__PURE__ */ new Set(["api-reference", "sandbox", "kb"]);
|
|
14118
|
+
var NON_DOCS_SLUGS = /* @__PURE__ */ new Set(["skills", "kb-only", "api"]);
|
|
14119
|
+
var CHECK_TTL_MS = 10 * 60 * 1e3;
|
|
14120
|
+
var MAX_CHECK_ENTRIES = 5e3;
|
|
14121
|
+
var CHECK_TIMEOUT_MS = 5e3;
|
|
14122
|
+
var MAX_REDIRECTS = 3;
|
|
14123
|
+
var CHECK_CONCURRENCY = 8;
|
|
14124
|
+
function isMintlifyDocsSite(docsSite) {
|
|
14125
|
+
return docsSite === MINTLIFY_LINKS_DOCS_SITE || docsSite === MINTLIFY_DOCS_SITE;
|
|
14126
|
+
}
|
|
14127
|
+
var MINTLIFY_DOCUMENT_TYPES = {
|
|
14128
|
+
REST: ["MINTLIFY_REST"],
|
|
14129
|
+
// includes the skills, as REST + SKILLS does today
|
|
14130
|
+
SDK: ["MINTLIFY_SDK"]
|
|
14131
|
+
};
|
|
14132
|
+
function mintlifyDocumentTypes(docsSite, toolName) {
|
|
14133
|
+
return docsSite === MINTLIFY_DOCS_SITE ? MINTLIFY_DOCUMENT_TYPES[toolName] : void 0;
|
|
14134
|
+
}
|
|
14135
|
+
function isMintlifyUrl(url) {
|
|
14136
|
+
try {
|
|
14137
|
+
return MINTLIFY_HOSTNAMES.has(new URL(url).hostname);
|
|
14138
|
+
} catch {
|
|
14139
|
+
return false;
|
|
14140
|
+
}
|
|
14141
|
+
}
|
|
14142
|
+
var MINTLIFY_URL_PREFIXES = [...MINTLIFY_HOSTNAMES].map(
|
|
14143
|
+
(host) => `https://${host}/`
|
|
14144
|
+
);
|
|
14145
|
+
function cleanPath(pathname) {
|
|
14146
|
+
const path = pathname.replace(/\.md$/, "").replace(/\/+$/, "");
|
|
14147
|
+
return path === "" ? "/" : path;
|
|
14148
|
+
}
|
|
14149
|
+
function flavourOf(url) {
|
|
14150
|
+
return url.searchParams.get("apiView")?.toUpperCase() === "SDK" ? "sdk" : "rest";
|
|
14151
|
+
}
|
|
14152
|
+
function toMintlifyCandidate(rawUrl) {
|
|
14153
|
+
let url;
|
|
14154
|
+
try {
|
|
14155
|
+
url = new URL(rawUrl);
|
|
14156
|
+
} catch {
|
|
14157
|
+
return null;
|
|
14158
|
+
}
|
|
14159
|
+
if (MINTLIFY_HOSTNAMES.has(url.hostname)) {
|
|
14160
|
+
const path2 = cleanPath(url.pathname);
|
|
14161
|
+
return { url: `${MINTLIFY_HOST}${path2}`, path: path2, flavour: flavourOf(url) };
|
|
14162
|
+
}
|
|
14163
|
+
if (url.hostname !== "dev.wix.com" || !url.pathname.startsWith("/docs/")) {
|
|
14164
|
+
return null;
|
|
14165
|
+
}
|
|
14166
|
+
const segments = cleanPath(url.pathname.slice("/docs".length)).split("/").filter(Boolean);
|
|
14167
|
+
if (segments.length === 0 || NON_DOCS_SLUGS.has(segments[0])) {
|
|
14168
|
+
return null;
|
|
14169
|
+
}
|
|
14170
|
+
segments[0] = PORTAL_SLUG_TO_TAB[segments[0]] ?? segments[0];
|
|
14171
|
+
const path = `/${segments.join("/")}`;
|
|
14172
|
+
return { url: `${MINTLIFY_HOST}${path}`, path, flavour: flavourOf(url) };
|
|
14173
|
+
}
|
|
14174
|
+
function toDevWixUrl(rawUrl) {
|
|
14175
|
+
const url = new URL(rawUrl);
|
|
14176
|
+
const segments = cleanPath(url.pathname).split("/").filter(Boolean);
|
|
14177
|
+
if (segments.length > 0) {
|
|
14178
|
+
segments[0] = TAB_TO_PORTAL_SLUG[segments[0]] ?? segments[0];
|
|
14179
|
+
}
|
|
14180
|
+
const query = flavourOf(url) === "sdk" ? "?apiView=SDK" : "";
|
|
14181
|
+
return `${DEV_WIX_DOCS}/${segments.join("/")}${query}`;
|
|
14182
|
+
}
|
|
14183
|
+
var checkCache = /* @__PURE__ */ new Map();
|
|
14184
|
+
function rememberCheck(path, result) {
|
|
14185
|
+
const now = Date.now();
|
|
14186
|
+
checkCache.delete(path);
|
|
14187
|
+
if (checkCache.size >= MAX_CHECK_ENTRIES) {
|
|
14188
|
+
for (const [key, entry] of checkCache) {
|
|
14189
|
+
if (entry.expires <= now) checkCache.delete(key);
|
|
14190
|
+
}
|
|
14191
|
+
}
|
|
14192
|
+
while (checkCache.size >= MAX_CHECK_ENTRIES) {
|
|
14193
|
+
const oldest = checkCache.keys().next().value;
|
|
14194
|
+
if (oldest === void 0) break;
|
|
14195
|
+
checkCache.delete(oldest);
|
|
14196
|
+
}
|
|
14197
|
+
checkCache.set(path, { result, expires: now + CHECK_TTL_MS });
|
|
14198
|
+
}
|
|
14199
|
+
async function headOnce(url) {
|
|
14200
|
+
return fetch(url, {
|
|
14201
|
+
method: "HEAD",
|
|
14202
|
+
redirect: "manual",
|
|
14203
|
+
signal: AbortSignal.timeout(CHECK_TIMEOUT_MS)
|
|
14204
|
+
});
|
|
14205
|
+
}
|
|
14206
|
+
async function checkMintlifyPage(path) {
|
|
14207
|
+
const cached = checkCache.get(path);
|
|
14208
|
+
if (cached && cached.expires > Date.now()) {
|
|
14209
|
+
return cached.result;
|
|
14210
|
+
}
|
|
14211
|
+
if (cached) checkCache.delete(path);
|
|
14212
|
+
let result;
|
|
14213
|
+
try {
|
|
14214
|
+
let current = path;
|
|
14215
|
+
let hops = 0;
|
|
14216
|
+
for (; ; ) {
|
|
14217
|
+
const response = await headOnce(`${MINTLIFY_HOST}${current}`);
|
|
14218
|
+
if (response.status === 200) {
|
|
14219
|
+
result = {
|
|
14220
|
+
outcome: hops === 0 ? "identity" : "redirect",
|
|
14221
|
+
path: current
|
|
14222
|
+
};
|
|
14223
|
+
break;
|
|
14224
|
+
}
|
|
14225
|
+
const location = response.headers.get("location");
|
|
14226
|
+
if (response.status >= 300 && response.status < 400 && location && hops < MAX_REDIRECTS) {
|
|
14227
|
+
current = cleanPath(new URL(location, MINTLIFY_HOST).pathname);
|
|
14228
|
+
hops++;
|
|
14229
|
+
continue;
|
|
14230
|
+
}
|
|
14231
|
+
result = { outcome: response.status === 404 ? "missing" : "error" };
|
|
14232
|
+
break;
|
|
14233
|
+
}
|
|
14234
|
+
} catch (error) {
|
|
14235
|
+
logger.error(`[mintlify] page check failed for ${path}: ${error}`);
|
|
14236
|
+
result = { outcome: "error" };
|
|
14237
|
+
}
|
|
14238
|
+
rememberCheck(path, result);
|
|
14239
|
+
return result;
|
|
14240
|
+
}
|
|
14241
|
+
async function resolveDocsLink(rawUrl, { verify = true } = {}) {
|
|
14242
|
+
const candidate = toMintlifyCandidate(rawUrl);
|
|
14243
|
+
if (!candidate) {
|
|
14244
|
+
return { outcome: "not-docs", url: rawUrl };
|
|
14245
|
+
}
|
|
14246
|
+
const tab = candidate.path.split("/")[1];
|
|
14247
|
+
if (!MINTLIFY_TABS.has(tab)) {
|
|
14248
|
+
return { outcome: "other-portal", url: rawUrl };
|
|
14249
|
+
}
|
|
14250
|
+
const query = candidate.flavour === "sdk" ? "?apiView=SDK" : "";
|
|
14251
|
+
if (!verify) {
|
|
14252
|
+
return { outcome: "identity", url: `${candidate.url}${query}` };
|
|
14253
|
+
}
|
|
14254
|
+
const check = await checkMintlifyPage(candidate.path);
|
|
14255
|
+
if (check.path) {
|
|
14256
|
+
return {
|
|
14257
|
+
outcome: check.outcome,
|
|
14258
|
+
url: `${MINTLIFY_HOST}${check.path}${query}`
|
|
14259
|
+
};
|
|
14260
|
+
}
|
|
14261
|
+
return { outcome: check.outcome, url: rawUrl };
|
|
14262
|
+
}
|
|
14263
|
+
var DOCS_LINK_PATTERN = /https:\/\/(?:dev\.wix\.com\/docs|wix-internal\.mintlify\.(?:site|app))\/[^\s)\]"'<>`]*/g;
|
|
14264
|
+
function trimTrailingPunctuation(url) {
|
|
14265
|
+
return url.replace(/[.,;:!?*]+$/, "");
|
|
14266
|
+
}
|
|
14267
|
+
var MINTLIFY_LINK_PATTERN = /https:\/\/wix-internal\.mintlify\.(?:site|app)\/[^\s)\]"'<>`]*/g;
|
|
14268
|
+
function toDevWixHostText(text) {
|
|
14269
|
+
return text.replace(MINTLIFY_LINK_PATTERN, (match) => {
|
|
14270
|
+
const link = trimTrailingPunctuation(match);
|
|
14271
|
+
let url;
|
|
14272
|
+
try {
|
|
14273
|
+
url = new URL(link);
|
|
14274
|
+
} catch {
|
|
14275
|
+
return match;
|
|
14276
|
+
}
|
|
14277
|
+
if (!MINTLIFY_TABS.has(url.pathname.split("/")[1] ?? "")) return match;
|
|
14278
|
+
return toDevWixUrl(link) + url.hash + match.slice(link.length);
|
|
14279
|
+
});
|
|
14280
|
+
}
|
|
14281
|
+
async function mapWithConcurrency(items, limit, fn) {
|
|
14282
|
+
const results = new Array(items.length);
|
|
14283
|
+
let next = 0;
|
|
14284
|
+
const workers = Array.from(
|
|
14285
|
+
{ length: Math.min(limit, items.length) },
|
|
14286
|
+
async () => {
|
|
14287
|
+
while (next < items.length) {
|
|
14288
|
+
const index = next++;
|
|
14289
|
+
results[index] = await fn(items[index]);
|
|
14290
|
+
}
|
|
14291
|
+
}
|
|
14292
|
+
);
|
|
14293
|
+
await Promise.all(workers);
|
|
14294
|
+
return results;
|
|
14295
|
+
}
|
|
14296
|
+
async function rewriteDocsLinks(text, { verify = true, label = "text" } = {}) {
|
|
14297
|
+
const found = [
|
|
14298
|
+
...new Set(
|
|
14299
|
+
(text.match(DOCS_LINK_PATTERN) ?? []).map(trimTrailingPunctuation)
|
|
14300
|
+
)
|
|
14301
|
+
];
|
|
14302
|
+
const stats = {
|
|
14303
|
+
links: found.length,
|
|
14304
|
+
byOutcome: {},
|
|
14305
|
+
misses: []
|
|
14306
|
+
};
|
|
14307
|
+
if (found.length === 0) {
|
|
14308
|
+
return { text, stats };
|
|
14309
|
+
}
|
|
14310
|
+
const resolved = await mapWithConcurrency(
|
|
14311
|
+
found,
|
|
14312
|
+
CHECK_CONCURRENCY,
|
|
14313
|
+
(url) => resolveDocsLink(url, { verify })
|
|
14314
|
+
);
|
|
14315
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
14316
|
+
found.forEach((url, i) => {
|
|
14317
|
+
const { outcome, url: target } = resolved[i];
|
|
14318
|
+
stats.byOutcome[outcome] = (stats.byOutcome[outcome] ?? 0) + 1;
|
|
14319
|
+
if (outcome === "identity" || outcome === "redirect") {
|
|
14320
|
+
if (target !== url) replacements.set(url, target);
|
|
14321
|
+
} else if (outcome !== "not-docs") {
|
|
14322
|
+
stats.misses.push(`${outcome} ${url}`);
|
|
14323
|
+
}
|
|
14324
|
+
});
|
|
14325
|
+
const rewritten = text.replace(DOCS_LINK_PATTERN, (match) => {
|
|
14326
|
+
const url = trimTrailingPunctuation(match);
|
|
14327
|
+
const target = replacements.get(url);
|
|
14328
|
+
return target ? target + match.slice(url.length) : match;
|
|
14329
|
+
});
|
|
14330
|
+
logger.log(
|
|
14331
|
+
`[mintlify] ${label}: ${stats.links} links ${JSON.stringify(stats.byOutcome)}` + (stats.misses.length ? ` misses: ${stats.misses.slice(0, 10).join(" | ")}` : "")
|
|
14332
|
+
);
|
|
14333
|
+
return { text: rewritten, stats };
|
|
14334
|
+
}
|
|
14335
|
+
var INDEX_BANNER = /^> ## Documentation Index\n(?:>.*\n)*\n?/;
|
|
14336
|
+
var INLINED_SNIPPET = /^export const \w+ = [\s\S]*?^};\n/gm;
|
|
14337
|
+
var REST_TAB = /<Tab title="REST">([\s\S]*?)<\/Tab>/;
|
|
14338
|
+
var SDK_TAB = /<Tab title="SDK">([\s\S]*?)<\/Tab>/;
|
|
14339
|
+
function cleanMintlifyMarkdown(markdown, flavour) {
|
|
14340
|
+
const text = markdown.replace(INLINED_SNIPPET, "");
|
|
14341
|
+
const rest = text.match(REST_TAB);
|
|
14342
|
+
const sdk = text.match(SDK_TAB);
|
|
14343
|
+
if (!rest || !sdk || (text.match(/<Tab title=/g) ?? []).length !== 2) {
|
|
14344
|
+
return text;
|
|
14345
|
+
}
|
|
14346
|
+
const start = text.lastIndexOf("<Tabs>", rest.index);
|
|
14347
|
+
const end = text.indexOf("</Tabs>", sdk.index);
|
|
14348
|
+
if (start === -1 || end === -1) {
|
|
14349
|
+
return text;
|
|
14350
|
+
}
|
|
14351
|
+
const kept = (flavour === "sdk" ? sdk : rest)[1];
|
|
14352
|
+
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._";
|
|
14353
|
+
return `${text.slice(0, start)}${note}
|
|
14354
|
+
|
|
14355
|
+
${kept.trim()}
|
|
14356
|
+
${text.slice(end + "</Tabs>".length)}`;
|
|
14357
|
+
}
|
|
14358
|
+
var DOCS_URL_IN_TEXT = /https:\/\/dev\.wix\.com\/docs\/(?=api-reference\b|\.\.\.)/g;
|
|
14359
|
+
function toMintlifyHostText(text) {
|
|
14360
|
+
return text.replace(DOCS_URL_IN_TEXT, `${MINTLIFY_HOST}/`);
|
|
14361
|
+
}
|
|
14362
|
+
var SITE_RELATIVE_LINK = /(\]\(|href=["'])\/(?!\/)/g;
|
|
14363
|
+
function absolutizeSiteLinks(markdown) {
|
|
14364
|
+
return markdown.replace(
|
|
14365
|
+
SITE_RELATIVE_LINK,
|
|
14366
|
+
(_, open) => `${open}${MINTLIFY_HOST}/`
|
|
14367
|
+
);
|
|
14368
|
+
}
|
|
14369
|
+
async function readMintlifyMarkdown(rawUrl) {
|
|
14370
|
+
const resolved = await resolveDocsLink(rawUrl);
|
|
14371
|
+
if (resolved.outcome !== "identity" && resolved.outcome !== "redirect") {
|
|
14372
|
+
return null;
|
|
14373
|
+
}
|
|
14374
|
+
const pageUrl = new URL(resolved.url);
|
|
14375
|
+
const response = await fetch(`${MINTLIFY_HOST}${pageUrl.pathname}.md`, {
|
|
14376
|
+
headers: { accept: "text/markdown" },
|
|
14377
|
+
redirect: "follow",
|
|
14378
|
+
signal: AbortSignal.timeout(15e3)
|
|
14379
|
+
});
|
|
14380
|
+
if (response.status === 404) {
|
|
14381
|
+
return null;
|
|
14382
|
+
}
|
|
14383
|
+
if (!response.ok) {
|
|
14384
|
+
throw new Error(
|
|
14385
|
+
`Mintlify returned ${response.status} for ${pageUrl.pathname}.md`
|
|
14386
|
+
);
|
|
14387
|
+
}
|
|
14388
|
+
const flavour = toMintlifyCandidate(rawUrl)?.flavour ?? "rest";
|
|
14389
|
+
const cleaned = cleanMintlifyMarkdown(
|
|
14390
|
+
(await response.text()).replace(INDEX_BANNER, ""),
|
|
14391
|
+
flavour
|
|
14392
|
+
);
|
|
14393
|
+
const { text: rewritten } = await rewriteDocsLinks(cleaned, {
|
|
14394
|
+
label: `read ${pageUrl.pathname}`
|
|
14395
|
+
});
|
|
14396
|
+
const markdown = absolutizeSiteLinks(rewritten);
|
|
14397
|
+
logger.log(
|
|
14398
|
+
`[mintlify] read ${pageUrl.pathname}.md (${markdown.length} chars) for ${rawUrl}`
|
|
14399
|
+
);
|
|
14400
|
+
return { url: `${MINTLIFY_HOST}${pageUrl.pathname}`, markdown };
|
|
14401
|
+
}
|
|
14402
|
+
|
|
13888
14403
|
// src/docs/get-to-know.ts
|
|
14404
|
+
var AX_SKILLS_REPO = "wix-private/ax-skills";
|
|
14405
|
+
var AX_SKILLS_INDEX_PATH = "skills/wix-manage/SKILL.md";
|
|
13889
14406
|
function addGetSiteContextTool({
|
|
13890
14407
|
server,
|
|
13891
14408
|
authStrategy,
|
|
14409
|
+
includeCompanionApp = false,
|
|
13892
14410
|
toolDescriptions
|
|
13893
14411
|
}) {
|
|
13894
14412
|
const d = desc("GetSiteContext");
|
|
@@ -13918,7 +14436,8 @@ function addGetSiteContextTool({
|
|
|
13918
14436
|
try {
|
|
13919
14437
|
context = await getDynamicContext(authStrategy, httpClient, filter, {
|
|
13920
14438
|
partnerContextEnabled: false,
|
|
13921
|
-
includeAccountContext: false
|
|
14439
|
+
includeAccountContext: false,
|
|
14440
|
+
includeCompanionApp
|
|
13922
14441
|
});
|
|
13923
14442
|
} catch (err) {
|
|
13924
14443
|
rethrowIfUnauthorized(err);
|
|
@@ -13954,15 +14473,17 @@ async function addGetToKnowTools({
|
|
|
13954
14473
|
dynamicContextEnabled,
|
|
13955
14474
|
repo,
|
|
13956
14475
|
commitHash,
|
|
13957
|
-
skillsTags
|
|
14476
|
+
skillsTags,
|
|
14477
|
+
readmeRecipesIndexEnabled = true,
|
|
14478
|
+
docsSite
|
|
13958
14479
|
}) {
|
|
13959
14480
|
if (disableTools.includes("WixREADME")) {
|
|
13960
14481
|
return;
|
|
13961
14482
|
}
|
|
13962
14483
|
const baseDocs = readmeDocs ?? defaultReadmeDocs;
|
|
13963
|
-
const
|
|
13964
|
-
|
|
13965
|
-
);
|
|
14484
|
+
const isRecipesIndex = (doc) => doc.url?.replace(/\.md$/, "") === RECIPES_INDEX_URL;
|
|
14485
|
+
const baseDocsWithoutRecipes = baseDocs.filter((doc) => !isRecipesIndex(doc));
|
|
14486
|
+
const recipesIndexDocs = readmeRecipesIndexEnabled ? baseDocs.filter(isRecipesIndex) : [];
|
|
13966
14487
|
server.tool(
|
|
13967
14488
|
"WixREADME",
|
|
13968
14489
|
toolDescriptions?.["WixREADME"] ?? defaultToolDescriptions.WixREADME,
|
|
@@ -14034,6 +14555,18 @@ ${responseText}` : responseText;
|
|
|
14034
14555
|
if (!url) {
|
|
14035
14556
|
throw new Error('URL is required for "include" action');
|
|
14036
14557
|
}
|
|
14558
|
+
const readsAxSkills = isRecipesIndex({ action, url }) && !skillsPortalUrl && (repo ? repo === AX_SKILLS_REPO : !commitHash);
|
|
14559
|
+
if (readsAxSkills) {
|
|
14560
|
+
return {
|
|
14561
|
+
type: "text",
|
|
14562
|
+
text: await fetchSourceContent(
|
|
14563
|
+
httpClient,
|
|
14564
|
+
AX_SKILLS_REPO,
|
|
14565
|
+
AX_SKILLS_INDEX_PATH,
|
|
14566
|
+
repo === AX_SKILLS_REPO ? commitHash : void 0
|
|
14567
|
+
)
|
|
14568
|
+
};
|
|
14569
|
+
}
|
|
14037
14570
|
const fetchedContent = await fetchArticleContent(
|
|
14038
14571
|
httpClient,
|
|
14039
14572
|
url,
|
|
@@ -14076,9 +14609,9 @@ Failed to fetch content: ${errorMsg}`
|
|
|
14076
14609
|
}).catch((err) => {
|
|
14077
14610
|
console.error("[WixREADME] Failed to fetch skills by portal:", err);
|
|
14078
14611
|
return null;
|
|
14079
|
-
}) : fetchDoc({ action: "include", url: RECIPES_INDEX_URL }).then(
|
|
14612
|
+
}) : readmeRecipesIndexEnabled ? fetchDoc({ action: "include", url: RECIPES_INDEX_URL }).then(
|
|
14080
14613
|
(doc) => doc?.text ?? null
|
|
14081
|
-
);
|
|
14614
|
+
) : Promise.resolve(null);
|
|
14082
14615
|
const filter = {};
|
|
14083
14616
|
if (siteId) filter.siteId = siteId;
|
|
14084
14617
|
else if (siteName) filter.siteName = siteName;
|
|
@@ -14098,7 +14631,7 @@ Failed to fetch content: ${errorMsg}`
|
|
|
14098
14631
|
const recipesIntro = dedent_default`
|
|
14099
14632
|
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.
|
|
14100
14633
|
|
|
14101
|
-
|
|
14634
|
+
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.
|
|
14102
14635
|
`;
|
|
14103
14636
|
const recipesContents = skillsContent !== null ? [
|
|
14104
14637
|
await fetchDoc({
|
|
@@ -14108,9 +14641,7 @@ Failed to fetch content: ${errorMsg}`
|
|
|
14108
14641
|
|
|
14109
14642
|
${skillsContent}`
|
|
14110
14643
|
})
|
|
14111
|
-
] : await Promise.all(
|
|
14112
|
-
baseDocs.filter((doc) => doc.url === RECIPES_INDEX_URL).map(fetchDoc)
|
|
14113
|
-
);
|
|
14644
|
+
] : await Promise.all(recipesIndexDocs.map(fetchDoc));
|
|
14114
14645
|
const recipeNotes = !skillsContent?.includes("Dashboard Navigation") ? [] : [
|
|
14115
14646
|
await fetchDoc({
|
|
14116
14647
|
action: "text",
|
|
@@ -14128,7 +14659,8 @@ ${skillsContent}`
|
|
|
14128
14659
|
).map((item) => item.text),
|
|
14129
14660
|
...dynamicContext ? [dynamicContext] : []
|
|
14130
14661
|
];
|
|
14131
|
-
const
|
|
14662
|
+
const joined = parts.join("\n\n");
|
|
14663
|
+
const text = isMintlifyDocsSite(docsSite) ? (await rewriteDocsLinks(joined, { label: "readme WixREADME" })).text : joined;
|
|
14132
14664
|
return {
|
|
14133
14665
|
content: [{ type: "text", text }]
|
|
14134
14666
|
};
|
|
@@ -14136,129 +14668,15 @@ ${skillsContent}`
|
|
|
14136
14668
|
);
|
|
14137
14669
|
}
|
|
14138
14670
|
|
|
14139
|
-
// src/docs/create-site-guide.ts
|
|
14140
|
-
function isHeadlessRequest(prompt) {
|
|
14141
|
-
return !!prompt && /headless/i.test(prompt);
|
|
14142
|
-
}
|
|
14143
|
-
var HEADLESS_NON_CLI_INSTRUCTIONS = `# Creating a Wix Headless Site
|
|
14144
|
-
|
|
14145
|
-
Wix Headless sites require the **Wix CLI** and cannot be created from the Wix dashboard or via REST API alone. Let the user know they need to set up a local development environment with the Wix CLI before proceeding.
|
|
14146
|
-
|
|
14147
|
-
## Prerequisites
|
|
14148
|
-
- Node.js 18+
|
|
14149
|
-
- A terminal / command-line environment
|
|
14150
|
-
|
|
14151
|
-
## Steps to Get Started
|
|
14152
|
-
|
|
14153
|
-
1. **Install and authenticate the Wix CLI:**
|
|
14154
|
-
\`\`\`
|
|
14155
|
-
npx @wix/cli login
|
|
14156
|
-
\`\`\`
|
|
14157
|
-
2. **Follow the quick-start guide:** https://dev.wix.com/docs/go-headless
|
|
14158
|
-
3. **For a complete orchestrated setup** (commerce, CMS, bookings, identity), use the Wix Headless skill: https://www.wix-headless.dev/skill.md`;
|
|
14159
|
-
var HEADLESS_CLI_INSTRUCTIONS = `# Creating a Wix Headless Site (CLI Flow)
|
|
14160
|
-
|
|
14161
|
-
Follow these steps using the \`RunWixCliCommand\` tool to scaffold and configure a Wix Headless site.
|
|
14162
|
-
|
|
14163
|
-
## Before You Start
|
|
14164
|
-
Ask the user:
|
|
14165
|
-
1. What is the brand/site name?
|
|
14166
|
-
2. Which local directory should the project be created in?
|
|
14167
|
-
|
|
14168
|
-
## Steps
|
|
14169
|
-
|
|
14170
|
-
### Step 1 \u2014 Authenticate (if not already done)
|
|
14171
|
-
Run: \`npx @wix/cli login\`
|
|
14172
|
-
Skip if the user is already authenticated.
|
|
14173
|
-
|
|
14174
|
-
### Step 2 \u2014 Scaffold the project
|
|
14175
|
-
Use a Wix headless starter template. Ask the user which starter they prefer, or use the default Next.js starter. Follow the scaffolding instructions at: https://dev.wix.com/docs/go-headless
|
|
14176
|
-
|
|
14177
|
-
### Step 3 \u2014 Install dependencies
|
|
14178
|
-
Run: \`npm install\` in the project directory.
|
|
14179
|
-
|
|
14180
|
-
### Step 4 \u2014 Pull environment variables
|
|
14181
|
-
Run: \`npx @wix/cli env pull\`
|
|
14182
|
-
This writes the \`.env.local\` file with the site's environment variables.
|
|
14183
|
-
|
|
14184
|
-
### Step 5 \u2014 Build and verify
|
|
14185
|
-
Run: \`npx @wix/cli build\`
|
|
14186
|
-
|
|
14187
|
-
### Step 6 \u2014 Release (optional)
|
|
14188
|
-
Run: \`npx @wix/cli release\` to deploy.
|
|
14189
|
-
|
|
14190
|
-
## Full Orchestration
|
|
14191
|
-
For a complete multi-wave headless setup (commerce, CMS, bookings, design system, content seeding), refer to the Wix Headless skill: https://www.wix-headless.dev/skill.md
|
|
14192
|
-
|
|
14193
|
-
Use \`RunWixCliCommand\` for each CLI step above.`;
|
|
14194
|
-
var CREATE_SITE_DOCS = [
|
|
14195
|
-
{
|
|
14196
|
-
url: "https://dev.wix.com/docs/kb-only/MCP_REST_RECIPES_KB_ID/TRAIN_create_a_new_site_from_template",
|
|
14197
|
-
title: "How to Create a New Wix Site",
|
|
14198
|
-
nextSteps: "\n\n**Next Steps:** Use the ManageWixSite tool to make the API call for creating the Wix site."
|
|
14199
|
-
},
|
|
14200
|
-
{
|
|
14201
|
-
url: "https://dev.wix.com/docs/kb-only/MCP_REST_RECIPES_KB_ID/TRAIN_wix.devcenter.apps.installer.v1.AppsInstallerService.InstallApp",
|
|
14202
|
-
title: "How to Install App on a Wix Site",
|
|
14203
|
-
nextSteps: "\n\n**Next Steps:** Use the CallWixSiteAPI tool to make the API call for installing apps on the Wix site."
|
|
14204
|
-
}
|
|
14205
|
-
];
|
|
14206
|
-
async function addCreateWixBusinessGuideTools(server, disableTools = [], toolDescriptions, cliEnabled = false) {
|
|
14207
|
-
if (disableTools.includes("CreateWixBusinessGuide")) {
|
|
14208
|
-
return;
|
|
14209
|
-
}
|
|
14210
|
-
server.tool(
|
|
14211
|
-
"CreateWixBusinessGuide",
|
|
14212
|
-
toolDescriptions?.["CreateWixBusinessGuide"] ?? defaultToolDescriptions.CreateWixBusinessGuide,
|
|
14213
|
-
{
|
|
14214
|
-
userPrompt: external_exports.string().optional().describe("The user prompt that triggered this tool")
|
|
14215
|
-
},
|
|
14216
|
-
{ readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
14217
|
-
async (args, { httpClient }) => {
|
|
14218
|
-
if (isHeadlessRequest(args.userPrompt)) {
|
|
14219
|
-
const isCli = typeof cliEnabled === "function" ? cliEnabled() : cliEnabled;
|
|
14220
|
-
const text = isCli ? HEADLESS_CLI_INSTRUCTIONS : HEADLESS_NON_CLI_INSTRUCTIONS;
|
|
14221
|
-
return { content: [{ type: "text", text }] };
|
|
14222
|
-
}
|
|
14223
|
-
try {
|
|
14224
|
-
const contents = await Promise.all(
|
|
14225
|
-
CREATE_SITE_DOCS.map(async ({ url, title, nextSteps }) => {
|
|
14226
|
-
const articleContent = await fetchArticleContent(
|
|
14227
|
-
httpClient,
|
|
14228
|
-
url,
|
|
14229
|
-
"article",
|
|
14230
|
-
true
|
|
14231
|
-
);
|
|
14232
|
-
const fullContent = `# ${title}
|
|
14233
|
-
|
|
14234
|
-
${articleContent}${nextSteps}`;
|
|
14235
|
-
return { type: "text", text: fullContent };
|
|
14236
|
-
})
|
|
14237
|
-
);
|
|
14238
|
-
return { content: contents };
|
|
14239
|
-
} catch (error) {
|
|
14240
|
-
return {
|
|
14241
|
-
isError: true,
|
|
14242
|
-
content: [
|
|
14243
|
-
{
|
|
14244
|
-
type: "text",
|
|
14245
|
-
text: `Error fetching create site documentation: ${error.message}`
|
|
14246
|
-
}
|
|
14247
|
-
]
|
|
14248
|
-
};
|
|
14249
|
-
}
|
|
14250
|
-
}
|
|
14251
|
-
);
|
|
14252
|
-
}
|
|
14253
|
-
|
|
14254
14671
|
// src/docs/start-deploy-website.ts
|
|
14672
|
+
var NO_PERSONAL_DATA = "No personal data: no names, emails, phones, passwords, keys or local paths.";
|
|
14255
14673
|
var START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS = {
|
|
14256
|
-
userRequest:
|
|
14257
|
-
siteDescription:
|
|
14258
|
-
siteForm:
|
|
14259
|
-
siteFunctionality:
|
|
14260
|
-
agentIdentity:
|
|
14261
|
-
wixAccess:
|
|
14674
|
+
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}`,
|
|
14675
|
+
siteDescription: `What the site is: its purpose and who it is for. One or two sentences. ${NO_PERSONAL_DATA}`,
|
|
14676
|
+
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}`,
|
|
14677
|
+
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}`,
|
|
14678
|
+
agentIdentity: `Which agent or product you are, which model if you know it, and the harness you run in. ${NO_PERSONAL_DATA}`,
|
|
14679
|
+
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}`
|
|
14262
14680
|
};
|
|
14263
14681
|
var START_DEPLOY_WEBSITE_PARAMS = Object.keys(
|
|
14264
14682
|
START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS
|
|
@@ -14266,10 +14684,11 @@ var START_DEPLOY_WEBSITE_PARAMS = Object.keys(
|
|
|
14266
14684
|
var START_DEPLOY_WEBSITE_TOOL_NAME = "StartDeployWebsite";
|
|
14267
14685
|
var START_DEPLOY_WEBSITE_RECIPE_URL = "https://dev.wix.com/docs/api-reference/account-level/sites/skills/upload-a-website-or-html-files.md";
|
|
14268
14686
|
var DROP_PAGE_URL = "https://www.wix.com/headless/drop?utm_campaign=mcp";
|
|
14687
|
+
var WRITE_THE_CODE_LINE = "When the user brought no files, write the site's code yourself, then upload it as the guide shows.";
|
|
14269
14688
|
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.`;
|
|
14270
14689
|
function startDeployWebsiteResponse(recipe) {
|
|
14271
14690
|
return [
|
|
14272
|
-
`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
|
|
14691
|
+
`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}`,
|
|
14273
14692
|
FALLBACK_LINE,
|
|
14274
14693
|
"---",
|
|
14275
14694
|
recipe.trim()
|
|
@@ -14277,7 +14696,7 @@ function startDeployWebsiteResponse(recipe) {
|
|
|
14277
14696
|
}
|
|
14278
14697
|
var START_DEPLOY_WEBSITE_POINTER_RESPONSE = [
|
|
14279
14698
|
`Read and follow this recipe end to end: ${START_DEPLOY_WEBSITE_RECIPE_URL}`,
|
|
14280
|
-
|
|
14699
|
+
`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}`,
|
|
14281
14700
|
FALLBACK_LINE
|
|
14282
14701
|
].join("\n\n");
|
|
14283
14702
|
function addStartDeployWebsiteTool(server, disableTools = [], toolDescriptions, fetchOptions = {}) {
|
|
@@ -14322,7 +14741,73 @@ function addStartDeployWebsiteTool(server, disableTools = [], toolDescriptions,
|
|
|
14322
14741
|
);
|
|
14323
14742
|
}
|
|
14324
14743
|
|
|
14325
|
-
// src/docs/
|
|
14744
|
+
// src/docs/start-headless-build.ts
|
|
14745
|
+
var START_HEADLESS_BUILD_TOOL_NAME = "StartHeadlessBuild";
|
|
14746
|
+
var HEADLESS_GUIDE_URL = "https://www.wix.com/skills/headless-cold-start/headless-kit.md";
|
|
14747
|
+
var START_HEADLESS_BUILD_PARAM_DESCRIPTIONS = {
|
|
14748
|
+
userRequest: START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS.userRequest,
|
|
14749
|
+
siteDescription: START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS.siteDescription,
|
|
14750
|
+
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}`,
|
|
14751
|
+
siteFunctionality: START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS.siteFunctionality,
|
|
14752
|
+
agentIdentity: START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS.agentIdentity,
|
|
14753
|
+
wixAccess: START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS.wixAccess
|
|
14754
|
+
};
|
|
14755
|
+
var BUILT_HERE = "The site is built here, in code: don't offer Wix's editor or a template instead.";
|
|
14756
|
+
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}`;
|
|
14757
|
+
function startHeadlessBuildResponse(guide) {
|
|
14758
|
+
if (guide && guide.trim()) {
|
|
14759
|
+
return [PREFIX2, "---", guide.trim()].join("\n\n");
|
|
14760
|
+
}
|
|
14761
|
+
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}`;
|
|
14762
|
+
}
|
|
14763
|
+
function addStartHeadlessBuildTool(server, disableTools = [], toolDescriptions, fetchOptions = {}) {
|
|
14764
|
+
if (disableTools.includes(START_HEADLESS_BUILD_TOOL_NAME)) {
|
|
14765
|
+
return;
|
|
14766
|
+
}
|
|
14767
|
+
const d = START_HEADLESS_BUILD_PARAM_DESCRIPTIONS;
|
|
14768
|
+
server.tool(
|
|
14769
|
+
START_HEADLESS_BUILD_TOOL_NAME,
|
|
14770
|
+
toolDescriptions?.[START_HEADLESS_BUILD_TOOL_NAME] ?? defaultToolDescriptions[START_HEADLESS_BUILD_TOOL_NAME],
|
|
14771
|
+
{
|
|
14772
|
+
userRequest: external_exports.string().min(1).describe(d.userRequest),
|
|
14773
|
+
siteDescription: external_exports.string().min(1).describe(d.siteDescription),
|
|
14774
|
+
startingPoint: external_exports.string().optional().describe(d.startingPoint),
|
|
14775
|
+
siteFunctionality: external_exports.string().min(1).describe(d.siteFunctionality),
|
|
14776
|
+
agentIdentity: external_exports.string().min(1).describe(d.agentIdentity),
|
|
14777
|
+
wixAccess: external_exports.string().min(1).describe(d.wixAccess)
|
|
14778
|
+
},
|
|
14779
|
+
{ readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
14780
|
+
async (_args, { httpClient }) => {
|
|
14781
|
+
let guide;
|
|
14782
|
+
try {
|
|
14783
|
+
guide = await fetchArticleContent(
|
|
14784
|
+
httpClient,
|
|
14785
|
+
HEADLESS_GUIDE_URL,
|
|
14786
|
+
"article",
|
|
14787
|
+
true,
|
|
14788
|
+
fetchOptions.skillsPortalUrl,
|
|
14789
|
+
fetchOptions.repo,
|
|
14790
|
+
fetchOptions.commitHash
|
|
14791
|
+
);
|
|
14792
|
+
} catch (error) {
|
|
14793
|
+
console.warn(
|
|
14794
|
+
"[StartHeadlessBuild] guide fetch failed, returning the pointer:",
|
|
14795
|
+
error instanceof Error ? error.message : error
|
|
14796
|
+
);
|
|
14797
|
+
}
|
|
14798
|
+
return {
|
|
14799
|
+
content: [
|
|
14800
|
+
{
|
|
14801
|
+
type: "text",
|
|
14802
|
+
text: startHeadlessBuildResponse(guide)
|
|
14803
|
+
}
|
|
14804
|
+
]
|
|
14805
|
+
};
|
|
14806
|
+
}
|
|
14807
|
+
);
|
|
14808
|
+
}
|
|
14809
|
+
|
|
14810
|
+
// src/docs/docs.ts
|
|
14326
14811
|
var REST_SEARCH_API_GROUNDING_REMINDER = dedent_default`
|
|
14327
14812
|
# Before calling a Wix API
|
|
14328
14813
|
|
|
@@ -14365,12 +14850,13 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14365
14850
|
"WIX_HEADLESS",
|
|
14366
14851
|
"BUSINESS_SOLUTIONS",
|
|
14367
14852
|
"CLI",
|
|
14368
|
-
"OVERVIEW"
|
|
14853
|
+
"OVERVIEW",
|
|
14854
|
+
"HELP_CENTER"
|
|
14369
14855
|
], options = {}) => {
|
|
14370
14856
|
const {
|
|
14371
14857
|
getToKnowWixEnabled = false,
|
|
14372
|
-
createWixBusinessGuideEnabled = true,
|
|
14373
14858
|
startDeployWebsiteEnabled = true,
|
|
14859
|
+
startHeadlessBuildEnabled = true,
|
|
14374
14860
|
disableTools,
|
|
14375
14861
|
toolDescriptions,
|
|
14376
14862
|
readmeDocs,
|
|
@@ -14381,15 +14867,47 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14381
14867
|
skillsPr,
|
|
14382
14868
|
commitHash,
|
|
14383
14869
|
skillsTags,
|
|
14870
|
+
readmeRecipesIndexEnabled,
|
|
14384
14871
|
dynamicContextEnabled,
|
|
14385
|
-
|
|
14872
|
+
includeCompanionApp,
|
|
14386
14873
|
usePlatformizedSearch = true,
|
|
14387
|
-
usePlatformizedBrowse = true
|
|
14874
|
+
usePlatformizedBrowse = true,
|
|
14875
|
+
docsSite
|
|
14388
14876
|
} = options;
|
|
14389
14877
|
const effectiveRepo = repo ?? skillsRepo;
|
|
14390
14878
|
const effectiveCommitHash = commitHash ?? skillsPr;
|
|
14391
|
-
const
|
|
14879
|
+
const mintlifyDocs = isMintlifyDocsSite(docsSite);
|
|
14880
|
+
const runSearch = mintlifyDocs ? async (params) => {
|
|
14881
|
+
const documentTypes = mintlifyDocumentTypes(docsSite, params.toolName);
|
|
14882
|
+
const result = await runSemanticSearchAndFormat(
|
|
14883
|
+
documentTypes ? { ...params, documentTypes } : params
|
|
14884
|
+
);
|
|
14885
|
+
return (await rewriteDocsLinks(result, { label: `search ${params.toolName}` })).text;
|
|
14886
|
+
} : runSemanticSearchAndFormat;
|
|
14887
|
+
const docsUrlPrefixes = mintlifyDocs ? ["https://dev.wix.com/docs/", ...MINTLIFY_URL_PREFIXES] : ["https://dev.wix.com/docs/"];
|
|
14888
|
+
const toBackendUrl = (url) => mintlifyDocs && isMintlifyUrl(url) ? toDevWixUrl(url) : url;
|
|
14889
|
+
const sourceNote = (source) => mintlifyDocs ? [{ type: "text", text: `[docs source] ${source}` }] : [];
|
|
14890
|
+
const docsText = (text) => mintlifyDocs ? toMintlifyHostText(text) : text;
|
|
14891
|
+
const desc2 = (toolName) => {
|
|
14892
|
+
const descriptions = desc(toolName);
|
|
14893
|
+
if (!mintlifyDocs) return descriptions;
|
|
14894
|
+
return Object.fromEntries(
|
|
14895
|
+
Object.entries(descriptions).map(([param, text]) => [
|
|
14896
|
+
param,
|
|
14897
|
+
docsText(text)
|
|
14898
|
+
])
|
|
14899
|
+
);
|
|
14900
|
+
};
|
|
14901
|
+
const getDescription = (toolName, defaultDesc) => docsText(toolDescriptions?.[toolName] ?? defaultDesc);
|
|
14392
14902
|
const fetchMenuTextForUrl = async (httpClient, url) => {
|
|
14903
|
+
const text = await fetchMenuTextForBackendUrl(
|
|
14904
|
+
httpClient,
|
|
14905
|
+
toBackendUrl(url)
|
|
14906
|
+
);
|
|
14907
|
+
if (!text || !mintlifyDocs) return text;
|
|
14908
|
+
return (await rewriteDocsLinks(text, { verify: false, label: "menu" })).text;
|
|
14909
|
+
};
|
|
14910
|
+
const fetchMenuTextForBackendUrl = async (httpClient, url) => {
|
|
14393
14911
|
if (usePlatformizedBrowse) {
|
|
14394
14912
|
try {
|
|
14395
14913
|
return await fetchPlatformizedMenu(url);
|
|
@@ -14406,10 +14924,25 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14406
14924
|
}
|
|
14407
14925
|
return menuContent.length > MENU_CONTENT_THRESHOLD ? summarizeLargeMenu(menuContent, url) : normalizeMenuContent(menuContent);
|
|
14408
14926
|
};
|
|
14927
|
+
if (startDeployWebsiteEnabled) {
|
|
14928
|
+
addStartDeployWebsiteTool(server, disableTools || [], toolDescriptions, {
|
|
14929
|
+
skillsPortalUrl,
|
|
14930
|
+
repo: effectiveRepo,
|
|
14931
|
+
commitHash: effectiveCommitHash
|
|
14932
|
+
});
|
|
14933
|
+
}
|
|
14934
|
+
if (startHeadlessBuildEnabled) {
|
|
14935
|
+
addStartHeadlessBuildTool(server, disableTools || [], toolDescriptions, {
|
|
14936
|
+
skillsPortalUrl,
|
|
14937
|
+
repo: effectiveRepo,
|
|
14938
|
+
commitHash: effectiveCommitHash
|
|
14939
|
+
});
|
|
14940
|
+
}
|
|
14409
14941
|
if (dynamicContextEnabled && authStrategy && !disableTools?.includes("GetSiteContext")) {
|
|
14410
14942
|
addGetSiteContextTool({
|
|
14411
14943
|
server,
|
|
14412
14944
|
authStrategy,
|
|
14945
|
+
includeCompanionApp,
|
|
14413
14946
|
toolDescriptions,
|
|
14414
14947
|
disableTools: disableTools || []
|
|
14415
14948
|
});
|
|
@@ -14425,22 +14958,9 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14425
14958
|
dynamicContextEnabled,
|
|
14426
14959
|
repo: effectiveRepo,
|
|
14427
14960
|
commitHash: effectiveCommitHash,
|
|
14428
|
-
skillsTags
|
|
14429
|
-
|
|
14430
|
-
|
|
14431
|
-
if (createWixBusinessGuideEnabled && !disableTools?.includes("CreateWixBusinessGuide")) {
|
|
14432
|
-
addCreateWixBusinessGuideTools(
|
|
14433
|
-
server,
|
|
14434
|
-
disableTools || [],
|
|
14435
|
-
toolDescriptions,
|
|
14436
|
-
cliEnabled
|
|
14437
|
-
);
|
|
14438
|
-
}
|
|
14439
|
-
if (startDeployWebsiteEnabled) {
|
|
14440
|
-
addStartDeployWebsiteTool(server, disableTools || [], toolDescriptions, {
|
|
14441
|
-
skillsPortalUrl,
|
|
14442
|
-
repo: effectiveRepo,
|
|
14443
|
-
commitHash: effectiveCommitHash
|
|
14961
|
+
skillsTags,
|
|
14962
|
+
readmeRecipesIndexEnabled,
|
|
14963
|
+
docsSite
|
|
14444
14964
|
});
|
|
14445
14965
|
}
|
|
14446
14966
|
if (allowedTools.includes("WDS") && !disableTools?.includes("SearchWixWDSDocumentation")) {
|
|
@@ -14451,7 +14971,7 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14451
14971
|
defaultToolDescriptions.SearchWixWDSDocumentation ?? ""
|
|
14452
14972
|
),
|
|
14453
14973
|
(() => {
|
|
14454
|
-
const d =
|
|
14974
|
+
const d = desc2("SearchWixWDSDocumentation");
|
|
14455
14975
|
return {
|
|
14456
14976
|
searchTerm: external_exports.string().describe(d.searchTerm),
|
|
14457
14977
|
maxResults: external_exports.number().describe(d.maxResults).min(1).max(15).optional().default(15)
|
|
@@ -14461,7 +14981,7 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14461
14981
|
async ({ searchTerm, maxResults }, { panorama }) => {
|
|
14462
14982
|
try {
|
|
14463
14983
|
logger.log(`Searching for ${searchTerm} in Wix WDS`);
|
|
14464
|
-
const result = await
|
|
14984
|
+
const result = await runSearch({
|
|
14465
14985
|
toolName: "WDS",
|
|
14466
14986
|
toolParams: {
|
|
14467
14987
|
searchTerm
|
|
@@ -14504,7 +15024,7 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14504
15024
|
defaultToolDescriptions.SearchWixRESTDocumentation
|
|
14505
15025
|
),
|
|
14506
15026
|
(() => {
|
|
14507
|
-
const d =
|
|
15027
|
+
const d = desc2("SearchWixRESTDocumentation");
|
|
14508
15028
|
return {
|
|
14509
15029
|
searchTerm: external_exports.string().describe(d.searchTerm),
|
|
14510
15030
|
maxResults: external_exports.number().describe(d.maxResults).min(1).max(17).optional().default(17),
|
|
@@ -14517,7 +15037,7 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14517
15037
|
logger.log(
|
|
14518
15038
|
`[SearchWixRESTDocumentation] searchTerm="${searchTerm}", reason="${reason}"`
|
|
14519
15039
|
);
|
|
14520
|
-
const result = await
|
|
15040
|
+
const result = await runSearch({
|
|
14521
15041
|
toolName: "REST",
|
|
14522
15042
|
toolParams: {
|
|
14523
15043
|
searchTerm
|
|
@@ -14568,7 +15088,7 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14568
15088
|
defaultToolDescriptions.WixBusinessFlowsDocumentation ?? ""
|
|
14569
15089
|
),
|
|
14570
15090
|
(() => {
|
|
14571
|
-
const d =
|
|
15091
|
+
const d = desc2("WixBusinessFlowsDocumentation");
|
|
14572
15092
|
return {
|
|
14573
15093
|
searchTerm: external_exports.string().describe(d.searchTerm),
|
|
14574
15094
|
maxResults: external_exports.number().describe(d.maxResults).min(1).max(15).optional().default(15)
|
|
@@ -14578,7 +15098,7 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14578
15098
|
async ({ searchTerm, maxResults }, { panorama }) => {
|
|
14579
15099
|
try {
|
|
14580
15100
|
logger.log(`Searching for ${searchTerm} in Wix Sample Flows`);
|
|
14581
|
-
const result = await
|
|
15101
|
+
const result = await runSearch({
|
|
14582
15102
|
toolName: "BUSINESS_SOLUTIONS",
|
|
14583
15103
|
toolParams: {
|
|
14584
15104
|
searchTerm: "**RECIPE**: Business Recipes - full setup flow for::: " + searchTerm
|
|
@@ -14624,7 +15144,7 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14624
15144
|
defaultToolDescriptions.SearchWixSDKDocumentation ?? ""
|
|
14625
15145
|
),
|
|
14626
15146
|
(() => {
|
|
14627
|
-
const d =
|
|
15147
|
+
const d = desc2("SearchWixSDKDocumentation");
|
|
14628
15148
|
return {
|
|
14629
15149
|
searchTerm: external_exports.string().describe(d.searchTerm),
|
|
14630
15150
|
maxResults: external_exports.number().describe(d.maxResults).min(1).max(15).optional().default(15)
|
|
@@ -14635,7 +15155,7 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14635
15155
|
try {
|
|
14636
15156
|
logger.log(`Searching for ${searchTerm} in Wix SDK`);
|
|
14637
15157
|
panorama.transaction("SearchWixSDKDocumentation").start({ maxResults });
|
|
14638
|
-
const result = await
|
|
15158
|
+
const result = await runSearch({
|
|
14639
15159
|
toolName: "SDK",
|
|
14640
15160
|
toolParams: {
|
|
14641
15161
|
searchTerm
|
|
@@ -14683,7 +15203,7 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14683
15203
|
defaultToolDescriptions.SearchBuildAppsDocumentation ?? ""
|
|
14684
15204
|
),
|
|
14685
15205
|
(() => {
|
|
14686
|
-
const d =
|
|
15206
|
+
const d = desc2("SearchBuildAppsDocumentation");
|
|
14687
15207
|
return {
|
|
14688
15208
|
searchTerm: external_exports.string().describe(d.searchTerm),
|
|
14689
15209
|
maxResults: external_exports.number().describe(d.maxResults).min(1).max(15).optional().default(15)
|
|
@@ -14693,7 +15213,7 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14693
15213
|
async ({ searchTerm, maxResults }, { panorama }) => {
|
|
14694
15214
|
try {
|
|
14695
15215
|
logger.log(`Searching for ${searchTerm} in Build Apps`);
|
|
14696
|
-
const result = await
|
|
15216
|
+
const result = await runSearch({
|
|
14697
15217
|
toolName: "BUILD_APPS",
|
|
14698
15218
|
toolParams: {
|
|
14699
15219
|
searchTerm: `wix cli docs for ${searchTerm}`
|
|
@@ -14735,7 +15255,7 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14735
15255
|
defaultToolDescriptions.SearchDevelopmentDocumentation ?? ""
|
|
14736
15256
|
),
|
|
14737
15257
|
(() => {
|
|
14738
|
-
const d =
|
|
15258
|
+
const d = desc2("SearchDevelopmentDocumentation");
|
|
14739
15259
|
return {
|
|
14740
15260
|
searchTerm: external_exports.string().describe(d.searchTerm),
|
|
14741
15261
|
maxResults: external_exports.number().describe(d.maxResults).min(1).max(15).optional().default(15)
|
|
@@ -14745,7 +15265,7 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14745
15265
|
async ({ searchTerm, maxResults }, { panorama }) => {
|
|
14746
15266
|
try {
|
|
14747
15267
|
logger.log(`Searching for ${searchTerm} in Wix Overview`);
|
|
14748
|
-
const result = await
|
|
15268
|
+
const result = await runSearch({
|
|
14749
15269
|
toolName: "OVERVIEW",
|
|
14750
15270
|
toolParams: {
|
|
14751
15271
|
searchTerm
|
|
@@ -14787,7 +15307,7 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14787
15307
|
defaultToolDescriptions.SearchWixHeadlessDocumentation ?? ""
|
|
14788
15308
|
),
|
|
14789
15309
|
(() => {
|
|
14790
|
-
const d =
|
|
15310
|
+
const d = desc2("SearchWixHeadlessDocumentation");
|
|
14791
15311
|
return {
|
|
14792
15312
|
searchTerm: external_exports.string().describe(d.searchTerm),
|
|
14793
15313
|
maxResults: external_exports.number().describe(d.maxResults).min(1).max(17).optional().default(17)
|
|
@@ -14797,7 +15317,7 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14797
15317
|
async ({ searchTerm, maxResults }, { panorama }) => {
|
|
14798
15318
|
try {
|
|
14799
15319
|
logger.log(`Searching for ${searchTerm} in Headless`);
|
|
14800
|
-
const result = await
|
|
15320
|
+
const result = await runSearch({
|
|
14801
15321
|
toolName: "WIX_HEADLESS",
|
|
14802
15322
|
toolParams: {
|
|
14803
15323
|
searchTerm
|
|
@@ -14842,7 +15362,7 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14842
15362
|
defaultToolDescriptions.SearchWixVeloDocumentation ?? ""
|
|
14843
15363
|
),
|
|
14844
15364
|
(() => {
|
|
14845
|
-
const d =
|
|
15365
|
+
const d = desc2("SearchWixVeloDocumentation");
|
|
14846
15366
|
return {
|
|
14847
15367
|
searchTerm: external_exports.string().describe(d.searchTerm),
|
|
14848
15368
|
maxResults: external_exports.number().describe(d.maxResults).min(1).max(15).optional().default(15)
|
|
@@ -14853,7 +15373,7 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14853
15373
|
try {
|
|
14854
15374
|
logger.log(`Searching for ${searchTerm} in Velo`);
|
|
14855
15375
|
panorama.transaction("SearchWixVeloDocumentation").start({ maxResults });
|
|
14856
|
-
const result = await
|
|
15376
|
+
const result = await runSearch({
|
|
14857
15377
|
toolName: "VELO",
|
|
14858
15378
|
toolParams: {
|
|
14859
15379
|
searchTerm
|
|
@@ -14891,6 +15411,72 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14891
15411
|
}
|
|
14892
15412
|
);
|
|
14893
15413
|
}
|
|
15414
|
+
if (allowedTools.includes("HELP_CENTER") && !disableTools?.includes("SearchWixHelpCenter")) {
|
|
15415
|
+
server.tool(
|
|
15416
|
+
"SearchWixHelpCenter",
|
|
15417
|
+
getDescription(
|
|
15418
|
+
"SearchWixHelpCenter",
|
|
15419
|
+
defaultToolDescriptions.SearchWixHelpCenter ?? ""
|
|
15420
|
+
),
|
|
15421
|
+
(() => {
|
|
15422
|
+
const d = desc2("SearchWixHelpCenter");
|
|
15423
|
+
return {
|
|
15424
|
+
searchTerm: external_exports.string().describe(d.searchTerm),
|
|
15425
|
+
maxResults: external_exports.number().describe(d.maxResults).min(1).max(15).optional().default(15),
|
|
15426
|
+
reason: external_exports.string().describe(d.reason)
|
|
15427
|
+
};
|
|
15428
|
+
})(),
|
|
15429
|
+
{ readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
15430
|
+
async ({ searchTerm, maxResults, reason }, { panorama }) => {
|
|
15431
|
+
try {
|
|
15432
|
+
logger.log(
|
|
15433
|
+
`[SearchWixHelpCenter] searchTerm="${searchTerm}", reason="${reason}"`
|
|
15434
|
+
);
|
|
15435
|
+
const result = await runSemanticSearchAndFormat({
|
|
15436
|
+
toolName: "HELP_CENTER",
|
|
15437
|
+
toolParams: {
|
|
15438
|
+
searchTerm
|
|
15439
|
+
},
|
|
15440
|
+
maxResults: Math.max(1, Math.min(maxResults ?? 15, 15)),
|
|
15441
|
+
linesInEachResult: 60,
|
|
15442
|
+
usePlatformizedSearch
|
|
15443
|
+
});
|
|
15444
|
+
return {
|
|
15445
|
+
content: [
|
|
15446
|
+
{
|
|
15447
|
+
type: "text",
|
|
15448
|
+
text: `${HELP_CENTER_IS_FOR_PEOPLE}
|
|
15449
|
+
|
|
15450
|
+
${result}`
|
|
15451
|
+
}
|
|
15452
|
+
]
|
|
15453
|
+
};
|
|
15454
|
+
} catch (error) {
|
|
15455
|
+
panorama.errorMonitor().reportError(error);
|
|
15456
|
+
captureException(error, {
|
|
15457
|
+
tags: {
|
|
15458
|
+
componentId: "SearchWixHelpCenter",
|
|
15459
|
+
toolName: "SearchWixHelpCenter"
|
|
15460
|
+
}
|
|
15461
|
+
});
|
|
15462
|
+
logger.error(
|
|
15463
|
+
`Error searching for ${searchTerm} in the Wix Help Center: ${error}`
|
|
15464
|
+
);
|
|
15465
|
+
return {
|
|
15466
|
+
isError: true,
|
|
15467
|
+
content: [
|
|
15468
|
+
{
|
|
15469
|
+
type: "text",
|
|
15470
|
+
text: `Error: ${error.message}
|
|
15471
|
+
|
|
15472
|
+
${HELP_CENTER_IS_FOR_PEOPLE}`
|
|
15473
|
+
}
|
|
15474
|
+
]
|
|
15475
|
+
};
|
|
15476
|
+
}
|
|
15477
|
+
}
|
|
15478
|
+
);
|
|
15479
|
+
}
|
|
14894
15480
|
if (allowedTools.includes("CLI") && !disableTools?.includes("SearchWixCLIDocumentation")) {
|
|
14895
15481
|
server.tool(
|
|
14896
15482
|
"SearchWixCLIDocumentation",
|
|
@@ -14899,7 +15485,7 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14899
15485
|
defaultToolDescriptions.SearchWixCLIDocumentation ?? ""
|
|
14900
15486
|
),
|
|
14901
15487
|
(() => {
|
|
14902
|
-
const d =
|
|
15488
|
+
const d = desc2("SearchWixCLIDocumentation");
|
|
14903
15489
|
return {
|
|
14904
15490
|
searchTerm: external_exports.string().describe(d.searchTerm),
|
|
14905
15491
|
maxResults: external_exports.number().describe(d.maxResults).min(1).max(15).optional().default(15),
|
|
@@ -14912,7 +15498,7 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14912
15498
|
logger.log(
|
|
14913
15499
|
`[SearchWixCLIDocumentation] searchTerm="${searchTerm}", reason="${reason}"`
|
|
14914
15500
|
);
|
|
14915
|
-
const result = await
|
|
15501
|
+
const result = await runSearch({
|
|
14916
15502
|
toolName: "CLI",
|
|
14917
15503
|
toolParams: {
|
|
14918
15504
|
searchTerm
|
|
@@ -14956,14 +15542,39 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14956
15542
|
defaultToolDescriptions.ReadFullDocsArticle
|
|
14957
15543
|
),
|
|
14958
15544
|
(() => {
|
|
14959
|
-
const d =
|
|
15545
|
+
const d = desc2("ReadFullDocsArticle");
|
|
14960
15546
|
return {
|
|
14961
15547
|
articleUrl: external_exports.string().url().describe(d.articleUrl)
|
|
14962
15548
|
};
|
|
14963
15549
|
})(),
|
|
14964
15550
|
{ readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
14965
15551
|
async ({ articleUrl }, { httpClient }) => {
|
|
14966
|
-
const
|
|
15552
|
+
const source = parseSourceContentUrl(articleUrl);
|
|
15553
|
+
if (source) {
|
|
15554
|
+
try {
|
|
15555
|
+
const text = await fetchSourceContent(
|
|
15556
|
+
httpClient,
|
|
15557
|
+
source.repo,
|
|
15558
|
+
source.path,
|
|
15559
|
+
source.commitHash
|
|
15560
|
+
);
|
|
15561
|
+
return { content: [{ type: "text", text }] };
|
|
15562
|
+
} catch (error) {
|
|
15563
|
+
logger.error(
|
|
15564
|
+
`Error fetching source content for ${articleUrl}: ${error}`
|
|
15565
|
+
);
|
|
15566
|
+
return {
|
|
15567
|
+
isError: true,
|
|
15568
|
+
content: [
|
|
15569
|
+
{
|
|
15570
|
+
type: "text",
|
|
15571
|
+
text: formatDocsReadError("source content", error)
|
|
15572
|
+
}
|
|
15573
|
+
]
|
|
15574
|
+
};
|
|
15575
|
+
}
|
|
15576
|
+
}
|
|
15577
|
+
const validPrefixes = [...docsUrlPrefixes, "https://support.wix.com/"];
|
|
14967
15578
|
if (skillsPortalUrl) validPrefixes.push(skillsPortalUrl);
|
|
14968
15579
|
const isSkillsPage = isSkillsRegistryUrl(articleUrl);
|
|
14969
15580
|
if (!isSkillsPage && !validPrefixes.some((p) => articleUrl.startsWith(p))) {
|
|
@@ -14972,7 +15583,7 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14972
15583
|
content: [
|
|
14973
15584
|
{
|
|
14974
15585
|
type: "text",
|
|
14975
|
-
text: `Invalid article URL: must start with https://dev.wix.com/docs/, or be a Wix skills page (https://www.wix.com/skills
|
|
15586
|
+
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}"`
|
|
14976
15587
|
}
|
|
14977
15588
|
]
|
|
14978
15589
|
};
|
|
@@ -14980,6 +15591,27 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14980
15591
|
if (!isSkillsPage && articleUrl.startsWith("https://dev.wix.com/docs/")) {
|
|
14981
15592
|
articleUrl = articleUrl.replace(/\.md$/, "");
|
|
14982
15593
|
}
|
|
15594
|
+
if (mintlifyDocs && !isSkillsPage) {
|
|
15595
|
+
try {
|
|
15596
|
+
const page = await readMintlifyMarkdown(articleUrl);
|
|
15597
|
+
if (page) {
|
|
15598
|
+
return {
|
|
15599
|
+
content: [
|
|
15600
|
+
{
|
|
15601
|
+
type: "text",
|
|
15602
|
+
text: isQueryOrSearchEndpoint(page.url) ? [page.markdown, ARTICLE_CAPABILITY_NOTE].join("\n\n") : page.markdown
|
|
15603
|
+
},
|
|
15604
|
+
...sourceNote(`Mintlify site: ${page.url}.md`)
|
|
15605
|
+
]
|
|
15606
|
+
};
|
|
15607
|
+
}
|
|
15608
|
+
} catch (error) {
|
|
15609
|
+
logger.error(
|
|
15610
|
+
`[mintlify] reading ${articleUrl} from Mintlify failed, falling back: ${error}`
|
|
15611
|
+
);
|
|
15612
|
+
}
|
|
15613
|
+
articleUrl = toBackendUrl(articleUrl);
|
|
15614
|
+
}
|
|
14983
15615
|
try {
|
|
14984
15616
|
const articleContent = await fetchArticleContent(
|
|
14985
15617
|
httpClient,
|
|
@@ -15000,7 +15632,10 @@ var addDocsTools = (server, allowedTools = [
|
|
|
15000
15632
|
// The article's own URL, never its body — a filter/sort reference page links query
|
|
15001
15633
|
// methods in its prose but isn't one.
|
|
15002
15634
|
text: isQueryOrSearchEndpoint(articleUrl) ? [articleContent, ARTICLE_CAPABILITY_NOTE].join("\n\n") : articleContent
|
|
15003
|
-
}
|
|
15635
|
+
},
|
|
15636
|
+
...sourceNote(
|
|
15637
|
+
isSkillsPage ? `skills registry: ${articleUrl}` : `today's backend (dev.wix.com/rawdocs, ingvar), not on the Mintlify site: ${articleUrl}`
|
|
15638
|
+
)
|
|
15004
15639
|
]
|
|
15005
15640
|
};
|
|
15006
15641
|
} catch (error) {
|
|
@@ -15046,7 +15681,7 @@ ${text}`
|
|
|
15046
15681
|
defaultToolDescriptions.ReadFullDocsMethodSchema
|
|
15047
15682
|
),
|
|
15048
15683
|
(() => {
|
|
15049
|
-
const d =
|
|
15684
|
+
const d = desc2("ReadFullDocsMethodSchema");
|
|
15050
15685
|
return {
|
|
15051
15686
|
articleUrl: external_exports.string().url().describe(d.articleUrl),
|
|
15052
15687
|
reason: external_exports.string().describe(d.reason)
|
|
@@ -15054,7 +15689,7 @@ ${text}`
|
|
|
15054
15689
|
})(),
|
|
15055
15690
|
{ readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
15056
15691
|
async ({ articleUrl, reason }, { httpClient }) => {
|
|
15057
|
-
const validPrefixes = [
|
|
15692
|
+
const validPrefixes = [...docsUrlPrefixes];
|
|
15058
15693
|
if (skillsPortalUrl) validPrefixes.push(skillsPortalUrl);
|
|
15059
15694
|
if (!validPrefixes.some((p) => articleUrl.startsWith(p))) {
|
|
15060
15695
|
return {
|
|
@@ -15070,6 +15705,7 @@ ${text}`
|
|
|
15070
15705
|
if (articleUrl.startsWith("https://dev.wix.com/docs/")) {
|
|
15071
15706
|
articleUrl = articleUrl.replace(/\.md$/, "");
|
|
15072
15707
|
}
|
|
15708
|
+
articleUrl = toBackendUrl(articleUrl);
|
|
15073
15709
|
try {
|
|
15074
15710
|
logger.log(
|
|
15075
15711
|
`[ReadFullDocsMethodSchema] url="${articleUrl}", reason="${reason}"`
|
|
@@ -15087,12 +15723,18 @@ ${text}`
|
|
|
15087
15723
|
if (!articleContent) {
|
|
15088
15724
|
throw new Error("Method schema is empty");
|
|
15089
15725
|
}
|
|
15726
|
+
const schemaText = mintlifyDocs ? (await rewriteDocsLinks(articleContent, {
|
|
15727
|
+
label: "schema ReadFullDocsMethodSchema"
|
|
15728
|
+
})).text : articleContent;
|
|
15090
15729
|
return {
|
|
15091
15730
|
content: [
|
|
15092
15731
|
{
|
|
15093
15732
|
type: "text",
|
|
15094
|
-
text:
|
|
15095
|
-
}
|
|
15733
|
+
text: schemaText
|
|
15734
|
+
},
|
|
15735
|
+
...sourceNote(
|
|
15736
|
+
`PAPI, through today's backend (dev.wix.com/rawdocs, ingvar): ${articleUrl}`
|
|
15737
|
+
)
|
|
15096
15738
|
]
|
|
15097
15739
|
};
|
|
15098
15740
|
} catch (error) {
|
|
@@ -15138,7 +15780,7 @@ ${text}`
|
|
|
15138
15780
|
defaultToolDescriptions.BrowseWixRESTDocsMenu
|
|
15139
15781
|
),
|
|
15140
15782
|
(() => {
|
|
15141
|
-
const d =
|
|
15783
|
+
const d = desc2("BrowseWixRESTDocsMenu");
|
|
15142
15784
|
return {
|
|
15143
15785
|
menuUrl: external_exports.string().optional().describe(d.menuUrl),
|
|
15144
15786
|
reason: external_exports.string().describe(d.reason)
|
|
@@ -15147,7 +15789,7 @@ ${text}`
|
|
|
15147
15789
|
{ readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
15148
15790
|
async ({ menuUrl, reason }, { httpClient }) => {
|
|
15149
15791
|
try {
|
|
15150
|
-
if (menuUrl && !menuUrl.startsWith(
|
|
15792
|
+
if (menuUrl && !docsUrlPrefixes.some((p) => menuUrl.startsWith(p))) {
|
|
15151
15793
|
return {
|
|
15152
15794
|
content: [
|
|
15153
15795
|
{
|
|
@@ -15427,7 +16069,7 @@ function resolveBase64Image(imageBase64, mimeType) {
|
|
|
15427
16069
|
function errorResponse(text) {
|
|
15428
16070
|
return { isError: true, content: [{ type: "text", text }] };
|
|
15429
16071
|
}
|
|
15430
|
-
async function uploadFromUrl(item, authHeaders, displayName) {
|
|
16072
|
+
async function uploadFromUrl(item, authHeaders, displayName, imagePickerToolName) {
|
|
15431
16073
|
if (!item.download_url) {
|
|
15432
16074
|
return {
|
|
15433
16075
|
success: false,
|
|
@@ -15437,7 +16079,7 @@ async function uploadFromUrl(item, authHeaders, displayName) {
|
|
|
15437
16079
|
if (!/^https?:\/\//i.test(item.download_url)) {
|
|
15438
16080
|
return {
|
|
15439
16081
|
success: false,
|
|
15440
|
-
error: `
|
|
16082
|
+
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` : ""}.`
|
|
15441
16083
|
};
|
|
15442
16084
|
}
|
|
15443
16085
|
try {
|
|
@@ -15535,7 +16177,10 @@ function formatBatchResults(results) {
|
|
|
15535
16177
|
};
|
|
15536
16178
|
}
|
|
15537
16179
|
function addUploadImageTool(server, strategy, options) {
|
|
15538
|
-
const
|
|
16180
|
+
const imagePickerToolName = options?.imagePickerToolName;
|
|
16181
|
+
const baseDescription = options?.description || defaultToolDescriptions.UploadImageToWixSite;
|
|
16182
|
+
const description = imagePickerToolName ? `${baseDescription}
|
|
16183
|
+
If you can't deliver the image any of these ways, call ${imagePickerToolName} so the user picks it.` : baseDescription;
|
|
15539
16184
|
const hints = {
|
|
15540
16185
|
readOnlyHint: options?.hints?.readOnlyHint ?? false,
|
|
15541
16186
|
destructiveHint: options?.hints?.destructiveHint ?? false,
|
|
@@ -15602,7 +16247,9 @@ function addUploadImageTool(server, strategy, options) {
|
|
|
15602
16247
|
try {
|
|
15603
16248
|
const authHeaders = await strategy.getSiteAuthHeaders(siteId);
|
|
15604
16249
|
const urlResults = await Promise.all(
|
|
15605
|
-
urlItems.map(
|
|
16250
|
+
urlItems.map(
|
|
16251
|
+
(item) => uploadFromUrl(item, authHeaders, displayName, imagePickerToolName)
|
|
16252
|
+
)
|
|
15606
16253
|
);
|
|
15607
16254
|
let base64Result = null;
|
|
15608
16255
|
if (imageBase64) {
|
|
@@ -15669,7 +16316,8 @@ function formatApiResponse(responseData, maxResponseSize = DEFAULT_MAX_RESPONSE_
|
|
|
15669
16316
|
}
|
|
15670
16317
|
return { text: responseText, isError: false, originalLength, returnedRows };
|
|
15671
16318
|
}
|
|
15672
|
-
function addApiCallTool(server, strategy, allowedTools = ["CallWixSiteAPI", "ListWixSites", "ManageWixSite"], disableTools = [], enableTools = [], toolDescriptions, toolHints, maxResponseSize) {
|
|
16319
|
+
function addApiCallTool(server, strategy, allowedTools = ["CallWixSiteAPI", "ListWixSites", "ManageWixSite"], disableTools = [], enableTools = [], toolDescriptions, toolHints, maxResponseSize, options) {
|
|
16320
|
+
const executeAdvice = (text, reconnect, plain) => options?.executeWixAPIServed ? text : text.replace(reconnect, plain);
|
|
15673
16321
|
const effectiveAllowedTools = [
|
|
15674
16322
|
.../* @__PURE__ */ new Set([...allowedTools, ...enableTools])
|
|
15675
16323
|
].filter((tool) => !disableTools.includes(tool));
|
|
@@ -15682,7 +16330,14 @@ function addApiCallTool(server, strategy, allowedTools = ["CallWixSiteAPI", "Lis
|
|
|
15682
16330
|
if (effectiveAllowedTools.includes("CallWixSiteAPI") && !disableTools.includes("CallWixSiteAPI")) {
|
|
15683
16331
|
server.tool(
|
|
15684
16332
|
"CallWixSiteAPI",
|
|
15685
|
-
|
|
16333
|
+
executeAdvice(
|
|
16334
|
+
getDescription(
|
|
16335
|
+
"CallWixSiteAPI",
|
|
16336
|
+
defaultToolDescriptions.CallWixSiteAPI
|
|
16337
|
+
),
|
|
16338
|
+
CALL_WIX_SITE_API_RECONNECT_LINE,
|
|
16339
|
+
CALL_WIX_SITE_API_NO_EXECUTE_LINE
|
|
16340
|
+
),
|
|
15686
16341
|
(() => {
|
|
15687
16342
|
const d = desc("CallWixSiteAPI");
|
|
15688
16343
|
return {
|
|
@@ -15691,17 +16346,34 @@ function addApiCallTool(server, strategy, allowedTools = ["CallWixSiteAPI", "Lis
|
|
|
15691
16346
|
method: external_exports.string().describe(d.method),
|
|
15692
16347
|
body: external_exports.union([external_exports.object({}).passthrough(), external_exports.array(external_exports.unknown())]).optional().describe(d.body),
|
|
15693
16348
|
reason: external_exports.string().describe(d.reason),
|
|
16349
|
+
whyNotExecuteWixAPI: external_exports.string().optional().describe(
|
|
16350
|
+
executeAdvice(
|
|
16351
|
+
d.whyNotExecuteWixAPI,
|
|
16352
|
+
WHY_NOT_EXECUTE_RECONNECT,
|
|
16353
|
+
WHY_NOT_EXECUTE_NO_EXECUTE
|
|
16354
|
+
)
|
|
16355
|
+
),
|
|
15694
16356
|
sourceDocUrl: external_exports.string().describe(d.sourceDocUrl)
|
|
15695
16357
|
};
|
|
15696
16358
|
})(),
|
|
16359
|
+
// openWorldHint true: a free-form URL into the whole Wix REST surface is an
|
|
16360
|
+
// open-ended target, even though auth is bounded to the user's account.
|
|
15697
16361
|
getHints("CallWixSiteAPI", {
|
|
15698
16362
|
readOnlyHint: false,
|
|
15699
16363
|
destructiveHint: true,
|
|
15700
|
-
openWorldHint:
|
|
16364
|
+
openWorldHint: true
|
|
15701
16365
|
}),
|
|
15702
|
-
async ({
|
|
16366
|
+
async ({
|
|
16367
|
+
url,
|
|
16368
|
+
body,
|
|
16369
|
+
method,
|
|
16370
|
+
siteId,
|
|
16371
|
+
reason,
|
|
16372
|
+
whyNotExecuteWixAPI,
|
|
16373
|
+
sourceDocUrl
|
|
16374
|
+
}, { httpClient, setToolBiParams }) => {
|
|
15703
16375
|
logger.log(
|
|
15704
|
-
`[CallWixSiteAPI] siteId=${siteId}, method=${method}, url=${url}, sourceDocUrl=${sourceDocUrl}, reason="${reason}", body=${JSON.stringify(body)}`
|
|
16376
|
+
`[CallWixSiteAPI] siteId=${siteId}, method=${method}, url=${url}, sourceDocUrl=${sourceDocUrl}, reason="${reason}", whyNotExecuteWixAPI="${whyNotExecuteWixAPI ?? ""}", body=${JSON.stringify(body)}`
|
|
15705
16377
|
);
|
|
15706
16378
|
const requestHeaders = {
|
|
15707
16379
|
// Ask the Wix API edge for JSON error bodies (it serves a branded HTML
|
|
@@ -15874,7 +16546,7 @@ function addApiCallTool(server, strategy, allowedTools = ["CallWixSiteAPI", "Lis
|
|
|
15874
16546
|
getHints("ManageWixSite", {
|
|
15875
16547
|
readOnlyHint: false,
|
|
15876
16548
|
destructiveHint: true,
|
|
15877
|
-
openWorldHint:
|
|
16549
|
+
openWorldHint: true
|
|
15878
16550
|
}),
|
|
15879
16551
|
async ({ url, body, method, reason }, { httpClient, setToolBiParams }) => {
|
|
15880
16552
|
logger.log(
|
|
@@ -15944,7 +16616,8 @@ function addApiCallTool(server, strategy, allowedTools = ["CallWixSiteAPI", "Lis
|
|
|
15944
16616
|
if (effectiveAllowedTools.includes("UploadImageToWixSite") && !disableTools.includes("UploadImageToWixSite")) {
|
|
15945
16617
|
addUploadImageTool(server, strategy, {
|
|
15946
16618
|
description: getDescription("UploadImageToWixSite", ""),
|
|
15947
|
-
hints: toolHints?.["UploadImageToWixSite"]
|
|
16619
|
+
hints: toolHints?.["UploadImageToWixSite"],
|
|
16620
|
+
imagePickerToolName: options?.imagePickerToolName
|
|
15948
16621
|
});
|
|
15949
16622
|
}
|
|
15950
16623
|
}
|
|
@@ -15962,10 +16635,14 @@ function truncateResponse(content, maxChars = DEFAULT_MAX_CHARS) {
|
|
|
15962
16635
|
const truncated = text.slice(0, effectiveMax);
|
|
15963
16636
|
const estimatedTokens = Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
15964
16637
|
const limitTokens = Math.ceil(effectiveMax / CHARS_PER_TOKEN);
|
|
15965
|
-
|
|
16638
|
+
const cutChars = text.length - effectiveMax;
|
|
16639
|
+
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.`;
|
|
16640
|
+
return `[TRUNCATED: ${note}]
|
|
16641
|
+
|
|
16642
|
+
${truncated}
|
|
15966
16643
|
|
|
15967
16644
|
--- TRUNCATED ---
|
|
15968
|
-
|
|
16645
|
+
${note}`;
|
|
15969
16646
|
}
|
|
15970
16647
|
|
|
15971
16648
|
// src/graphql/constants.ts
|
|
@@ -16630,7 +17307,8 @@ function addExecuteGraphqlTool(server, description, authStrategy, options) {
|
|
|
16630
17307
|
hasMutations: external_exports.boolean().describe(d.hasMutations),
|
|
16631
17308
|
reason: external_exports.string().describe(d.reason)
|
|
16632
17309
|
},
|
|
16633
|
-
|
|
17310
|
+
// openWorldHint true: any operation in the Wix GraphQL schema, chosen at call time.
|
|
17311
|
+
{ readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
16634
17312
|
async ({ query, variables, operationName, siteId, scope, hasMutations, reason }, extra) => {
|
|
16635
17313
|
const effectiveScope = scope ?? (siteId ? "site" : "account");
|
|
16636
17314
|
logger.log(
|
|
@@ -16725,7 +17403,7 @@ ${text.slice(0, 2e3)}`
|
|
|
16725
17403
|
|
|
16726
17404
|
// src/code-mode/attachments.ts
|
|
16727
17405
|
var MAX_ATTACHMENTS_BYTES = 10 * 1024 * 1024;
|
|
16728
|
-
var
|
|
17406
|
+
var MAX_REDIRECTS2 = 4;
|
|
16729
17407
|
var OAI_USERCONTENT_HOST = /^[a-z0-9-]+\.oaiusercontent\.com$/;
|
|
16730
17408
|
var OAI_BLOB_ACCOUNTS = new Set(
|
|
16731
17409
|
[
|
|
@@ -16799,125 +17477,1117 @@ async function fetchAllowed(start, fetchImpl, label) {
|
|
|
16799
17477
|
const isRedirect = res.status >= 300 && res.status < 400 && location;
|
|
16800
17478
|
if (!isRedirect) return res;
|
|
16801
17479
|
await res.body?.cancel().catch(() => void 0);
|
|
16802
|
-
if (hop >=
|
|
17480
|
+
if (hop >= MAX_REDIRECTS2) {
|
|
16803
17481
|
throw new AttachmentError(`${label}: too many redirects.`);
|
|
16804
17482
|
}
|
|
16805
|
-
let next;
|
|
16806
|
-
try {
|
|
16807
|
-
next = new URL(location, url);
|
|
16808
|
-
} catch {
|
|
16809
|
-
throw new AttachmentError(`${label}: redirect to an invalid URL.`);
|
|
17483
|
+
let next;
|
|
17484
|
+
try {
|
|
17485
|
+
next = new URL(location, url);
|
|
17486
|
+
} catch {
|
|
17487
|
+
throw new AttachmentError(`${label}: redirect to an invalid URL.`);
|
|
17488
|
+
}
|
|
17489
|
+
const problem = checkAttachmentUrl(next);
|
|
17490
|
+
if (problem) {
|
|
17491
|
+
throw new AttachmentError(
|
|
17492
|
+
`${label}: the download redirected to a URL that ${problem}; refused.`
|
|
17493
|
+
);
|
|
17494
|
+
}
|
|
17495
|
+
url = next;
|
|
17496
|
+
}
|
|
17497
|
+
}
|
|
17498
|
+
async function readWithin(res, budget, overBudget) {
|
|
17499
|
+
const declared = Number(res.headers.get("content-length") ?? 0);
|
|
17500
|
+
if (declared > budget) throw overBudget();
|
|
17501
|
+
if (!res.body) {
|
|
17502
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
17503
|
+
if (bytes.byteLength > budget) throw overBudget();
|
|
17504
|
+
return bytes;
|
|
17505
|
+
}
|
|
17506
|
+
const reader = res.body.getReader();
|
|
17507
|
+
const chunks = [];
|
|
17508
|
+
let total = 0;
|
|
17509
|
+
for (; ; ) {
|
|
17510
|
+
const { done, value } = await reader.read();
|
|
17511
|
+
if (done) break;
|
|
17512
|
+
total += value.byteLength;
|
|
17513
|
+
if (total > budget) {
|
|
17514
|
+
await reader.cancel().catch(() => void 0);
|
|
17515
|
+
throw overBudget();
|
|
17516
|
+
}
|
|
17517
|
+
chunks.push(value);
|
|
17518
|
+
}
|
|
17519
|
+
const out = new Uint8Array(total);
|
|
17520
|
+
let offset = 0;
|
|
17521
|
+
for (const c of chunks) {
|
|
17522
|
+
out.set(c, offset);
|
|
17523
|
+
offset += c.byteLength;
|
|
17524
|
+
}
|
|
17525
|
+
return out;
|
|
17526
|
+
}
|
|
17527
|
+
async function resolveAttachments(attachments, fetchImpl = fetch) {
|
|
17528
|
+
if (!Array.isArray(attachments) || attachments.length === 0) return [];
|
|
17529
|
+
const items = [];
|
|
17530
|
+
attachments.forEach((raw, i) => {
|
|
17531
|
+
const a = typeof raw === "object" && raw;
|
|
17532
|
+
const downloadUrl = a && typeof a.download_url === "string" ? a.download_url : "";
|
|
17533
|
+
let url;
|
|
17534
|
+
try {
|
|
17535
|
+
url = /^https?:\/\//i.test(downloadUrl) ? new URL(downloadUrl) : void 0;
|
|
17536
|
+
} catch {
|
|
17537
|
+
url = void 0;
|
|
17538
|
+
}
|
|
17539
|
+
if (!a || !url) {
|
|
17540
|
+
throw new AttachmentError(
|
|
17541
|
+
`attachments[${i}] was not resolved to a download URL (got ${JSON.stringify(
|
|
17542
|
+
typeof raw === "string" ? raw : (a && a.download_url) ?? raw
|
|
17543
|
+
)}). \`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).`
|
|
17544
|
+
);
|
|
17545
|
+
}
|
|
17546
|
+
const problem = checkAttachmentUrl(url);
|
|
17547
|
+
if (problem) {
|
|
17548
|
+
throw new AttachmentError(
|
|
17549
|
+
`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.`
|
|
17550
|
+
);
|
|
17551
|
+
}
|
|
17552
|
+
items.push({ a, url });
|
|
17553
|
+
});
|
|
17554
|
+
let remaining = MAX_ATTACHMENTS_BYTES;
|
|
17555
|
+
const out = [];
|
|
17556
|
+
for (const [i, { a, url }] of items.entries()) {
|
|
17557
|
+
const path = attachmentPath(a, url);
|
|
17558
|
+
const label = `attachments[${i}] (${path})`;
|
|
17559
|
+
let res;
|
|
17560
|
+
try {
|
|
17561
|
+
res = await fetchAllowed(url, fetchImpl, label);
|
|
17562
|
+
} catch (e) {
|
|
17563
|
+
if (e instanceof AttachmentError) throw e;
|
|
17564
|
+
throw new AttachmentError(
|
|
17565
|
+
`${label}: download failed (${e.message}).`
|
|
17566
|
+
);
|
|
17567
|
+
}
|
|
17568
|
+
if (!res.ok) {
|
|
17569
|
+
await res.body?.cancel().catch(() => void 0);
|
|
17570
|
+
throw new AttachmentError(
|
|
17571
|
+
`${label}: download returned HTTP ${res.status}. The download_url may have expired; attach the file again.`
|
|
17572
|
+
);
|
|
17573
|
+
}
|
|
17574
|
+
const bytes = await readWithin(
|
|
17575
|
+
res,
|
|
17576
|
+
remaining,
|
|
17577
|
+
() => new AttachmentError(
|
|
17578
|
+
`${label} pushes the attachments past ${MAX_ATTACHMENTS_BYTES} bytes in total for this call; split the upload across calls.`
|
|
17579
|
+
)
|
|
17580
|
+
);
|
|
17581
|
+
remaining -= bytes.byteLength;
|
|
17582
|
+
out.push({ path, content: toBase64(bytes), encoding: "base64" });
|
|
17583
|
+
}
|
|
17584
|
+
return out;
|
|
17585
|
+
}
|
|
17586
|
+
|
|
17587
|
+
// src/code-mode/validation.ts
|
|
17588
|
+
var EXECUTE_FUNCTION_EXPRESSION_HINT = 'ExecuteWixAPI code must be a function expression, not a script body. Pass code like: async () => { const response = await wix.request({ scope: "site", method: "GET", url: "https://www.wixapis.com/..." }); return response.data; }. Do not write top-level const/await/return outside the function and do not invoke the function yourself.';
|
|
17589
|
+
function validateExecuteCode(code) {
|
|
17590
|
+
const trimmed = code.trim();
|
|
17591
|
+
const expression = trimmed.startsWith("(") ? trimmed.slice(1).trimStart() : trimmed;
|
|
17592
|
+
const isAsyncFunctionExpression = /^async\s+function(?:\s+\w+)?\s*\(/.test(expression) || /^async\s*\([^)]*\)\s*=>/.test(expression) || /^async\s+\w+\s*=>/.test(expression);
|
|
17593
|
+
const invokesFunctionExpression = /\)\s*\(\s*\)\s*;?\s*$/.test(trimmed);
|
|
17594
|
+
const namedFunctionWithTrailingCode = /^async\s+function\s+\w+\s*\(/.test(expression) && !expression.endsWith("}");
|
|
17595
|
+
if (!isAsyncFunctionExpression || invokesFunctionExpression || namedFunctionWithTrailingCode) {
|
|
17596
|
+
return { isValid: false, error: EXECUTE_FUNCTION_EXPRESSION_HINT };
|
|
17597
|
+
}
|
|
17598
|
+
return { isValid: true };
|
|
17599
|
+
}
|
|
17600
|
+
|
|
17601
|
+
// src/api-call/site-files.ts
|
|
17602
|
+
var MAX_FILES_BUNDLE_CHARS = 4e6;
|
|
17603
|
+
var MAX_UNZIP_BYTES = 64 * 1024 * 1024;
|
|
17604
|
+
var MAX_SITE_FILES = 2e3;
|
|
17605
|
+
var textEncoder = new TextEncoder();
|
|
17606
|
+
function base64ToBytes(b64) {
|
|
17607
|
+
const clean = b64.replace(/^data:[^,]*,/, "").replace(/\s+/g, "");
|
|
17608
|
+
return Uint8Array.from(atob(clean), (c) => c.charCodeAt(0));
|
|
17609
|
+
}
|
|
17610
|
+
function parseFileBundle(bundle) {
|
|
17611
|
+
if (!bundle || !bundle.trim()) return [];
|
|
17612
|
+
if (bundle.length > MAX_FILES_BUNDLE_CHARS) {
|
|
17613
|
+
throw new Error(
|
|
17614
|
+
`files bundle is too large (${bundle.length} chars; max ${MAX_FILES_BUNDLE_CHARS}). Keep it to text and small base64 assets; pass larger files through attachments or reference media by URL.`
|
|
17615
|
+
);
|
|
17616
|
+
}
|
|
17617
|
+
const delimiter = /^=== FILE: (.+?)(?: (base64))? ===[ \t]*$/gm;
|
|
17618
|
+
const matches = [...bundle.matchAll(delimiter)];
|
|
17619
|
+
if (matches.length === 0) {
|
|
17620
|
+
throw new Error(
|
|
17621
|
+
"files bundle has no `=== FILE: <path> ===` sections. Start each file with that line (add ` base64` before the closing === for binary content)."
|
|
17622
|
+
);
|
|
17623
|
+
}
|
|
17624
|
+
return matches.map((match, i) => {
|
|
17625
|
+
const path = match[1].trim();
|
|
17626
|
+
const start = (match.index ?? 0) + match[0].length;
|
|
17627
|
+
const end = i + 1 < matches.length ? matches[i + 1].index ?? bundle.length : bundle.length;
|
|
17628
|
+
const content = bundle.slice(start, end).replace(/^\r?\n/, "").replace(/\r?\n$/, "");
|
|
17629
|
+
return {
|
|
17630
|
+
path,
|
|
17631
|
+
bytes: match[2] === "base64" ? base64ToBytes(content) : textEncoder.encode(content)
|
|
17632
|
+
};
|
|
17633
|
+
});
|
|
17634
|
+
}
|
|
17635
|
+
async function unzip(z) {
|
|
17636
|
+
const u16 = (o) => z[o] | z[o + 1] << 8;
|
|
17637
|
+
const u32 = (o) => (u16(o) | u16(o + 2) << 16) >>> 0;
|
|
17638
|
+
let end = z.length - 22;
|
|
17639
|
+
while (end >= 0 && !(u32(end) === 101010256 && end + 22 + u16(end + 20) === z.length))
|
|
17640
|
+
end--;
|
|
17641
|
+
if (end < 0) throw new Error("not a zip file (no end-of-central-directory)");
|
|
17642
|
+
const out = [];
|
|
17643
|
+
let p = u32(end + 16);
|
|
17644
|
+
let total = 0;
|
|
17645
|
+
const decoder = new TextDecoder();
|
|
17646
|
+
const n = u16(end + 10);
|
|
17647
|
+
if (n > MAX_SITE_FILES) {
|
|
17648
|
+
throw new Error(
|
|
17649
|
+
`zip holds ${n} entries; a site upload is at most ${MAX_SITE_FILES} files`
|
|
17650
|
+
);
|
|
17651
|
+
}
|
|
17652
|
+
for (let i = 0; i < n; i++) {
|
|
17653
|
+
const path = decoder.decode(z.slice(p + 46, p + 46 + u16(p + 28)));
|
|
17654
|
+
const method = u16(p + 10);
|
|
17655
|
+
const size = u32(p + 24);
|
|
17656
|
+
total += size;
|
|
17657
|
+
if (total > MAX_UNZIP_BYTES) {
|
|
17658
|
+
throw new Error(
|
|
17659
|
+
`zip expands past ${MAX_UNZIP_BYTES} bytes; a static site is at most 20 MB`
|
|
17660
|
+
);
|
|
17661
|
+
}
|
|
17662
|
+
const at = u32(p + 42);
|
|
17663
|
+
const start = at + 30 + u16(at + 26) + u16(at + 28);
|
|
17664
|
+
const comp = z.slice(start, start + u32(p + 20));
|
|
17665
|
+
p += 46 + u16(p + 28) + u16(p + 30) + u16(p + 32);
|
|
17666
|
+
if (path.endsWith("/")) continue;
|
|
17667
|
+
let bytes;
|
|
17668
|
+
if (method === 0) bytes = comp;
|
|
17669
|
+
else if (method === 8) {
|
|
17670
|
+
bytes = new Uint8Array(
|
|
17671
|
+
await new Response(
|
|
17672
|
+
new Blob([comp]).stream().pipeThrough(new DecompressionStream("deflate-raw"))
|
|
17673
|
+
).arrayBuffer()
|
|
17674
|
+
);
|
|
17675
|
+
} else
|
|
17676
|
+
throw new Error(
|
|
17677
|
+
`zip entry '${path}' uses unsupported compression method ${method}`
|
|
17678
|
+
);
|
|
17679
|
+
if (bytes.length !== size) {
|
|
17680
|
+
throw new Error(
|
|
17681
|
+
`zip entry '${path}' declares ${size} bytes but holds ${bytes.length}`
|
|
17682
|
+
);
|
|
17683
|
+
}
|
|
17684
|
+
out.push({ path, bytes });
|
|
17685
|
+
}
|
|
17686
|
+
return out;
|
|
17687
|
+
}
|
|
17688
|
+
var CRC_TABLE = (() => {
|
|
17689
|
+
const t = new Uint32Array(256);
|
|
17690
|
+
for (let n = 0; n < 256; n++) {
|
|
17691
|
+
let c = n;
|
|
17692
|
+
for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
17693
|
+
t[n] = c >>> 0;
|
|
17694
|
+
}
|
|
17695
|
+
return t;
|
|
17696
|
+
})();
|
|
17697
|
+
function crc32(bytes) {
|
|
17698
|
+
let c = 4294967295;
|
|
17699
|
+
for (let i = 0; i < bytes.length; i++)
|
|
17700
|
+
c = CRC_TABLE[(c ^ bytes[i]) & 255] ^ c >>> 8;
|
|
17701
|
+
return (c ^ 4294967295) >>> 0;
|
|
17702
|
+
}
|
|
17703
|
+
function zipStored(files) {
|
|
17704
|
+
const encoder = new TextEncoder();
|
|
17705
|
+
const entries = files.map((f) => ({
|
|
17706
|
+
name: encoder.encode(f.path),
|
|
17707
|
+
bytes: f.bytes,
|
|
17708
|
+
crc: crc32(f.bytes)
|
|
17709
|
+
}));
|
|
17710
|
+
const localSize = entries.reduce(
|
|
17711
|
+
(sum, e) => sum + 30 + e.name.length + e.bytes.length,
|
|
17712
|
+
0
|
|
17713
|
+
);
|
|
17714
|
+
const centralSize = entries.reduce((sum, e) => sum + 46 + e.name.length, 0);
|
|
17715
|
+
const out = new Uint8Array(localSize + centralSize + 22);
|
|
17716
|
+
const view = new DataView(out.buffer);
|
|
17717
|
+
const UTF8_FLAG = 2048;
|
|
17718
|
+
let p = 0;
|
|
17719
|
+
const offsets = [];
|
|
17720
|
+
for (const e of entries) {
|
|
17721
|
+
offsets.push(p);
|
|
17722
|
+
view.setUint32(p, 67324752, true);
|
|
17723
|
+
view.setUint16(p + 4, 20, true);
|
|
17724
|
+
view.setUint16(p + 6, UTF8_FLAG, true);
|
|
17725
|
+
view.setUint16(p + 8, 0, true);
|
|
17726
|
+
view.setUint32(p + 10, 0, true);
|
|
17727
|
+
view.setUint32(p + 14, e.crc, true);
|
|
17728
|
+
view.setUint32(p + 18, e.bytes.length, true);
|
|
17729
|
+
view.setUint32(p + 22, e.bytes.length, true);
|
|
17730
|
+
view.setUint16(p + 26, e.name.length, true);
|
|
17731
|
+
view.setUint16(p + 28, 0, true);
|
|
17732
|
+
out.set(e.name, p + 30);
|
|
17733
|
+
out.set(e.bytes, p + 30 + e.name.length);
|
|
17734
|
+
p += 30 + e.name.length + e.bytes.length;
|
|
17735
|
+
}
|
|
17736
|
+
const centralStart = p;
|
|
17737
|
+
entries.forEach((e, i) => {
|
|
17738
|
+
view.setUint32(p, 33639248, true);
|
|
17739
|
+
view.setUint16(p + 4, 20, true);
|
|
17740
|
+
view.setUint16(p + 6, 20, true);
|
|
17741
|
+
view.setUint16(p + 8, UTF8_FLAG, true);
|
|
17742
|
+
view.setUint16(p + 10, 0, true);
|
|
17743
|
+
view.setUint32(p + 12, 0, true);
|
|
17744
|
+
view.setUint32(p + 16, e.crc, true);
|
|
17745
|
+
view.setUint32(p + 20, e.bytes.length, true);
|
|
17746
|
+
view.setUint32(p + 24, e.bytes.length, true);
|
|
17747
|
+
view.setUint16(p + 28, e.name.length, true);
|
|
17748
|
+
view.setUint32(p + 42, offsets[i], true);
|
|
17749
|
+
out.set(e.name, p + 46);
|
|
17750
|
+
p += 46 + e.name.length;
|
|
17751
|
+
});
|
|
17752
|
+
view.setUint32(p, 101010256, true);
|
|
17753
|
+
view.setUint16(p + 8, entries.length, true);
|
|
17754
|
+
view.setUint16(p + 10, entries.length, true);
|
|
17755
|
+
view.setUint32(p + 12, p - centralStart, true);
|
|
17756
|
+
view.setUint32(p + 16, centralStart, true);
|
|
17757
|
+
return out;
|
|
17758
|
+
}
|
|
17759
|
+
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);
|
|
17760
|
+
var isRootIndex = (p) => /^index\.html?$/i.test(p);
|
|
17761
|
+
async function expandSiteFiles(input) {
|
|
17762
|
+
const list = [];
|
|
17763
|
+
let bytes = 0;
|
|
17764
|
+
for (const f of input) {
|
|
17765
|
+
const added = isZip(f) ? await unzip(f.bytes) : [f];
|
|
17766
|
+
list.push(...added);
|
|
17767
|
+
bytes += added.reduce((sum, a) => sum + a.bytes.byteLength, 0);
|
|
17768
|
+
if (list.length > MAX_SITE_FILES) {
|
|
17769
|
+
throw new Error(
|
|
17770
|
+
`Too many files (more than ${MAX_SITE_FILES}); a site upload is at most ${MAX_SITE_FILES}.`
|
|
17771
|
+
);
|
|
17772
|
+
}
|
|
17773
|
+
if (bytes > MAX_UNZIP_BYTES) {
|
|
17774
|
+
throw new Error(
|
|
17775
|
+
`The upload expands past ${MAX_UNZIP_BYTES} bytes; send only the site's files.`
|
|
17776
|
+
);
|
|
17777
|
+
}
|
|
17778
|
+
}
|
|
17779
|
+
return list.map((f) => ({
|
|
17780
|
+
...f,
|
|
17781
|
+
path: f.path.replace(/\\/g, "/").replace(/^\.?\//, "")
|
|
17782
|
+
})).filter(
|
|
17783
|
+
(f) => f.path && !f.path.startsWith("__MACOSX/") && !/(^|\/)\.DS_Store$/.test(f.path) && !f.path.split("/").includes("..")
|
|
17784
|
+
);
|
|
17785
|
+
}
|
|
17786
|
+
function staticSiteFiles(expanded) {
|
|
17787
|
+
let list = expanded;
|
|
17788
|
+
if (list.length === 0) {
|
|
17789
|
+
throw new Error(
|
|
17790
|
+
"No files to upload: pass the site in `files` or `attachments`."
|
|
17791
|
+
);
|
|
17792
|
+
}
|
|
17793
|
+
if (list.length > MAX_SITE_FILES) {
|
|
17794
|
+
throw new Error(
|
|
17795
|
+
`Too many files (${list.length}); a site upload is at most ${MAX_SITE_FILES}.`
|
|
17796
|
+
);
|
|
17797
|
+
}
|
|
17798
|
+
if (!list.some((f) => isRootIndex(f.path))) {
|
|
17799
|
+
const first = list[0].path.split("/")[0];
|
|
17800
|
+
if (list.every((f) => f.path.includes("/") && f.path.split("/")[0] === first)) {
|
|
17801
|
+
list = list.map((f) => ({
|
|
17802
|
+
...f,
|
|
17803
|
+
path: f.path.split("/").slice(1).join("/")
|
|
17804
|
+
}));
|
|
17805
|
+
}
|
|
17806
|
+
}
|
|
17807
|
+
const rootHtml = list.filter(
|
|
17808
|
+
(f) => !f.path.includes("/") && /\.html?$/i.test(f.path)
|
|
17809
|
+
);
|
|
17810
|
+
if (!list.some((f) => isRootIndex(f.path)) && rootHtml.length === 1) {
|
|
17811
|
+
list = list.map(
|
|
17812
|
+
(f) => f === rootHtml[0] ? { ...f, path: "index.html" } : f
|
|
17813
|
+
);
|
|
17814
|
+
}
|
|
17815
|
+
if (!list.some((f) => isRootIndex(f.path))) {
|
|
17816
|
+
const shown = list.slice(0, 8).map((f) => f.path).join(", ");
|
|
17817
|
+
throw new Error(
|
|
17818
|
+
`The site needs an index.html at its root. Files received: ${shown}${list.length > 8 ? ", \u2026" : ""}`
|
|
17819
|
+
);
|
|
17820
|
+
}
|
|
17821
|
+
return list;
|
|
17822
|
+
}
|
|
17823
|
+
function guessContentType(path) {
|
|
17824
|
+
const ext = (/\.([a-zA-Z0-9]+)$/.exec(path)?.[1] ?? "").toLowerCase();
|
|
17825
|
+
const map = {
|
|
17826
|
+
html: "text/html",
|
|
17827
|
+
htm: "text/html",
|
|
17828
|
+
css: "text/css",
|
|
17829
|
+
js: "text/javascript",
|
|
17830
|
+
mjs: "text/javascript",
|
|
17831
|
+
json: "application/json",
|
|
17832
|
+
svg: "image/svg+xml",
|
|
17833
|
+
txt: "text/plain",
|
|
17834
|
+
md: "text/markdown",
|
|
17835
|
+
xml: "application/xml",
|
|
17836
|
+
webmanifest: "application/manifest+json",
|
|
17837
|
+
ico: "image/x-icon",
|
|
17838
|
+
png: "image/png",
|
|
17839
|
+
jpg: "image/jpeg",
|
|
17840
|
+
jpeg: "image/jpeg",
|
|
17841
|
+
gif: "image/gif",
|
|
17842
|
+
webp: "image/webp",
|
|
17843
|
+
avif: "image/avif",
|
|
17844
|
+
woff: "font/woff",
|
|
17845
|
+
woff2: "font/woff2",
|
|
17846
|
+
ttf: "font/ttf",
|
|
17847
|
+
otf: "font/otf",
|
|
17848
|
+
pdf: "application/pdf",
|
|
17849
|
+
mp4: "video/mp4",
|
|
17850
|
+
webm: "video/webm",
|
|
17851
|
+
mp3: "audio/mpeg"
|
|
17852
|
+
};
|
|
17853
|
+
return map[ext] ?? "application/octet-stream";
|
|
17854
|
+
}
|
|
17855
|
+
function buildFormData(files) {
|
|
17856
|
+
const fd = new FormData();
|
|
17857
|
+
for (const f of files) {
|
|
17858
|
+
fd.append(
|
|
17859
|
+
"files",
|
|
17860
|
+
new Blob([f.bytes], { type: guessContentType(f.path) }),
|
|
17861
|
+
f.path
|
|
17862
|
+
);
|
|
17863
|
+
}
|
|
17864
|
+
return fd;
|
|
17865
|
+
}
|
|
17866
|
+
|
|
17867
|
+
// src/api-call/headless-release.ts
|
|
17868
|
+
var BUILD_METADATA_SUFFIX = ".wix/build-metadata.json";
|
|
17869
|
+
function withoutEmptyStatics(files, build) {
|
|
17870
|
+
const dir = build.clientDir;
|
|
17871
|
+
if (dir === void 0) return { files, dropped: [] };
|
|
17872
|
+
const dropped = [];
|
|
17873
|
+
const kept = files.filter((f) => {
|
|
17874
|
+
const path = normalizePath(f.path);
|
|
17875
|
+
if (f.bytes.byteLength === 0 && path.startsWith(dir)) {
|
|
17876
|
+
dropped.push(path.slice(dir.length));
|
|
17877
|
+
return false;
|
|
17878
|
+
}
|
|
17879
|
+
return true;
|
|
17880
|
+
});
|
|
17881
|
+
return { files: kept, dropped };
|
|
17882
|
+
}
|
|
17883
|
+
var normalizePath = (p) => p.replace(/\\/g, "/").replace(/^\.?\//, "");
|
|
17884
|
+
function findHeadlessBuild(files) {
|
|
17885
|
+
const entries = new Map(files.map((f) => [normalizePath(f.path), f.bytes]));
|
|
17886
|
+
const metadataEntry = [...entries.keys()].find(
|
|
17887
|
+
(name) => name === BUILD_METADATA_SUFFIX || name.endsWith(`/${BUILD_METADATA_SUFFIX}`)
|
|
17888
|
+
);
|
|
17889
|
+
if (metadataEntry === void 0) return null;
|
|
17890
|
+
const zipRoot = metadataEntry.slice(
|
|
17891
|
+
0,
|
|
17892
|
+
metadataEntry.length - BUILD_METADATA_SUFFIX.length
|
|
17893
|
+
);
|
|
17894
|
+
let metadata;
|
|
17895
|
+
try {
|
|
17896
|
+
metadata = JSON.parse(
|
|
17897
|
+
new TextDecoder().decode(entries.get(metadataEntry))
|
|
17898
|
+
);
|
|
17899
|
+
} catch {
|
|
17900
|
+
throw new Error(`${BUILD_METADATA_SUFFIX} is not valid JSON.`);
|
|
17901
|
+
}
|
|
17902
|
+
if (typeof metadata.appManifestPath !== "string") {
|
|
17903
|
+
throw new Error(
|
|
17904
|
+
`${BUILD_METADATA_SUFFIX} has no appManifestPath; send the build as the Wix hosting adapter wrote it (.wix/ and dist/ zipped from the project root).`
|
|
17905
|
+
);
|
|
17906
|
+
}
|
|
17907
|
+
const manifestPath = resolveZipPath(
|
|
17908
|
+
metadata.appManifestPath,
|
|
17909
|
+
zipRoot,
|
|
17910
|
+
entries
|
|
17911
|
+
);
|
|
17912
|
+
if (manifestPath === void 0) {
|
|
17913
|
+
throw new Error(
|
|
17914
|
+
`The build's app manifest (${metadata.appManifestPath}) is not in the upload; zip .wix/ and dist/ together from the project root after \`astro build\`.`
|
|
17915
|
+
);
|
|
17916
|
+
}
|
|
17917
|
+
const builderRoot = metadata.appManifestPath.slice(
|
|
17918
|
+
0,
|
|
17919
|
+
metadata.appManifestPath.length - (manifestPath.length - zipRoot.length)
|
|
17920
|
+
);
|
|
17921
|
+
const clientDir = metadata.clientDir === void 0 ? void 0 : toZipDir(metadata.clientDir, builderRoot, zipRoot);
|
|
17922
|
+
const serverDir = metadata.serverDir === void 0 ? void 0 : toZipDir(metadata.serverDir, builderRoot, zipRoot);
|
|
17923
|
+
const clientFiles = [];
|
|
17924
|
+
const serverFiles = [];
|
|
17925
|
+
for (const [name, bytes] of entries) {
|
|
17926
|
+
if (name === manifestPath) continue;
|
|
17927
|
+
if (serverDir !== void 0 && name.startsWith(serverDir)) {
|
|
17928
|
+
serverFiles.push({ path: name.slice(serverDir.length), bytes });
|
|
17929
|
+
} else if (clientDir !== void 0 && name.startsWith(clientDir)) {
|
|
17930
|
+
clientFiles.push({ path: name.slice(clientDir.length), bytes });
|
|
17931
|
+
}
|
|
17932
|
+
}
|
|
17933
|
+
if (clientFiles.length + serverFiles.length === 0) {
|
|
17934
|
+
throw new Error(
|
|
17935
|
+
"The build has no client or server output files; run `astro build` first and zip .wix/ and dist/ from the project root."
|
|
17936
|
+
);
|
|
17937
|
+
}
|
|
17938
|
+
if (serverFiles.length > 0 && !serverFiles.some((f) => f.path === "entry.mjs")) {
|
|
17939
|
+
throw new Error(
|
|
17940
|
+
"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."
|
|
17941
|
+
);
|
|
17942
|
+
}
|
|
17943
|
+
const manifestText = new TextDecoder().decode(entries.get(manifestPath));
|
|
17944
|
+
return {
|
|
17945
|
+
metadata,
|
|
17946
|
+
root: zipRoot,
|
|
17947
|
+
manifestText,
|
|
17948
|
+
clientFiles,
|
|
17949
|
+
serverFiles,
|
|
17950
|
+
clientDir
|
|
17951
|
+
};
|
|
17952
|
+
}
|
|
17953
|
+
var NOT_SOURCE_TOP = /* @__PURE__ */ new Set(["dist", ".wix", ".astro", ".agents"]);
|
|
17954
|
+
var isSourcePath = (relative) => {
|
|
17955
|
+
const segments = relative.split("/");
|
|
17956
|
+
return !NOT_SOURCE_TOP.has(segments[0]) && !segments.some((s) => s === "node_modules" || s.startsWith(".env"));
|
|
17957
|
+
};
|
|
17958
|
+
function buildSourceFiles(files, build) {
|
|
17959
|
+
return files.flatMap((f) => {
|
|
17960
|
+
const path = normalizePath(f.path);
|
|
17961
|
+
if (!path.startsWith(build.root)) return [];
|
|
17962
|
+
const relative = path.slice(build.root.length);
|
|
17963
|
+
return relative && isSourcePath(relative) ? [{ path: relative, bytes: f.bytes }] : [];
|
|
17964
|
+
});
|
|
17965
|
+
}
|
|
17966
|
+
function resolveZipPath(builderPath, zipRoot, entries) {
|
|
17967
|
+
const segments = normalizePath(builderPath).split("/").filter((s) => s.length > 0);
|
|
17968
|
+
for (let k = 0; k < segments.length; k++) {
|
|
17969
|
+
const candidate = zipRoot + segments.slice(k).join("/");
|
|
17970
|
+
if (entries.has(candidate)) return candidate;
|
|
17971
|
+
}
|
|
17972
|
+
return void 0;
|
|
17973
|
+
}
|
|
17974
|
+
function toZipDir(builderDir, builderRoot, zipRoot) {
|
|
17975
|
+
const normalized = normalizePath(builderDir);
|
|
17976
|
+
const normalizedRoot = normalizePath(builderRoot);
|
|
17977
|
+
const relative = builderDir.startsWith(builderRoot) ? builderDir.slice(builderRoot.length) : normalized.startsWith(normalizedRoot) ? normalized.slice(normalizedRoot.length) : normalized;
|
|
17978
|
+
const dir = zipRoot + normalizePath(relative);
|
|
17979
|
+
return dir.endsWith("/") ? dir : `${dir}/`;
|
|
17980
|
+
}
|
|
17981
|
+
|
|
17982
|
+
// src/infra/redact-personal-data.ts
|
|
17983
|
+
var FREE_TEXT_INFO_PARAMS = [
|
|
17984
|
+
"userRequest",
|
|
17985
|
+
"siteDescription",
|
|
17986
|
+
"siteForm",
|
|
17987
|
+
"siteFunctionality",
|
|
17988
|
+
"agentIdentity",
|
|
17989
|
+
"wixAccess",
|
|
17990
|
+
"reason",
|
|
17991
|
+
"whyNotExecuteWixAPI"
|
|
17992
|
+
];
|
|
17993
|
+
var EMAIL = /[A-Z0-9._%+-]+@[A-Z0-9-]+(?:\.[A-Z0-9-]+)*\.[A-Z]{2,}/gi;
|
|
17994
|
+
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;
|
|
17995
|
+
var BEARER = /\bBearer\s+[A-Za-z0-9._~+/=-]+/gi;
|
|
17996
|
+
var JWT = /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g;
|
|
17997
|
+
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;
|
|
17998
|
+
var BACKTICK = "`";
|
|
17999
|
+
var UNIX_ROOT = String.raw`(?<![\w.:/~-])(?:~|/(?:Users|home|root|mnt|tmp|private|var|Volumes|workspace|work))`;
|
|
18000
|
+
var WINDOWS_ROOT = String.raw`(?<!\w)[A-Za-z]:(?=\\\w)`;
|
|
18001
|
+
var PATH_ROOT = `(?:${UNIX_ROOT}|${WINDOWS_ROOT})`;
|
|
18002
|
+
var QUOTED_PATH = new RegExp(
|
|
18003
|
+
String.raw`(?<![\w])(["'${BACKTICK}])${PATH_ROOT}[^"'${BACKTICK}\n]*\1`,
|
|
18004
|
+
"g"
|
|
18005
|
+
);
|
|
18006
|
+
var FOLDER = String.raw`[^\s/\\"'${BACKTICK},;()<>]+(?: [^\s/\\"'${BACKTICK},;()<>]+)*`;
|
|
18007
|
+
var LOCAL_PATH = new RegExp(
|
|
18008
|
+
String.raw`${PATH_ROOT}(?:[/\\]${FOLDER}(?=[/\\]))*[/\\][^\s"'${BACKTICK},;()<>]*`,
|
|
18009
|
+
"g"
|
|
18010
|
+
);
|
|
18011
|
+
var PHONE_CANDIDATE = /(?<![\w-])\+?\(?\d[\d\s().-]{7,}\d(?![\w-])/g;
|
|
18012
|
+
var DATE_START = /^\d{4}-\d{2}-\d{2}/;
|
|
18013
|
+
function redactPhone(match, offset, text) {
|
|
18014
|
+
if (DATE_START.test(match) || /^:\d{2}\b/.test(text.slice(offset + match.length))) {
|
|
18015
|
+
return match;
|
|
18016
|
+
}
|
|
18017
|
+
const digits = match.replace(/\D/g, "").length;
|
|
18018
|
+
return digits >= 9 && digits <= 15 ? "[phone]" : match;
|
|
18019
|
+
}
|
|
18020
|
+
function redactPersonalData(text) {
|
|
18021
|
+
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);
|
|
18022
|
+
}
|
|
18023
|
+
function redactInfoParams(value) {
|
|
18024
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
18025
|
+
return value;
|
|
18026
|
+
}
|
|
18027
|
+
const record = value;
|
|
18028
|
+
if (!FREE_TEXT_INFO_PARAMS.some((key) => typeof record[key] === "string")) {
|
|
18029
|
+
return value;
|
|
18030
|
+
}
|
|
18031
|
+
const redacted = { ...record };
|
|
18032
|
+
for (const key of FREE_TEXT_INFO_PARAMS) {
|
|
18033
|
+
if (typeof redacted[key] === "string") {
|
|
18034
|
+
redacted[key] = redactPersonalData(redacted[key]);
|
|
18035
|
+
}
|
|
18036
|
+
}
|
|
18037
|
+
return redacted;
|
|
18038
|
+
}
|
|
18039
|
+
function serializeToolParamsForBi(args) {
|
|
18040
|
+
return JSON.stringify(
|
|
18041
|
+
Array.isArray(args) ? args.map(redactInfoParams) : redactInfoParams(args)
|
|
18042
|
+
);
|
|
18043
|
+
}
|
|
18044
|
+
|
|
18045
|
+
// src/api-call/upload-headless-website-files.ts
|
|
18046
|
+
var UPLOAD_HEADLESS_WEBSITE_FILES_TOOL_NAME = "UploadHeadlessWebsiteFiles";
|
|
18047
|
+
var HEADLESS_BUSINESS = "https://www.wixapis.com/headless-business-setup/v1/headless-business";
|
|
18048
|
+
var MAX_BUNDLE_BYTES = 100 * 1024 * 1024;
|
|
18049
|
+
function dropAttribution(clientName) {
|
|
18050
|
+
const slug = (clientName ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
|
|
18051
|
+
return `?campaign=mcp&agent=${encodeURIComponent(slug || "wix-mcp")}`;
|
|
18052
|
+
}
|
|
18053
|
+
var UPLOAD_HEADLESS_WEBSITE_FILES_PARAM_DESCRIPTIONS = {
|
|
18054
|
+
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.",
|
|
18055
|
+
attachments: fileParamNote(
|
|
18056
|
+
"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"
|
|
18057
|
+
) + " 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.",
|
|
18058
|
+
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.",
|
|
18059
|
+
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.`,
|
|
18060
|
+
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`.",
|
|
18061
|
+
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.",
|
|
18062
|
+
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.",
|
|
18063
|
+
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.",
|
|
18064
|
+
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.",
|
|
18065
|
+
calledStartDeployWebsite: "Whether you called StartDeployWebsite earlier in this conversation and are following the recipe it returned. Answer truthfully; it is recorded, not checked.",
|
|
18066
|
+
agentIdentity: START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS.agentIdentity,
|
|
18067
|
+
reason: `One sentence: the user request that led here and what is being published. ${NO_PERSONAL_DATA}`
|
|
18068
|
+
};
|
|
18069
|
+
var UPLOAD_RECIPE_SKILL_URL = "https://www.wix.com/skills/wix-manage/references/sites/upload-static-site.md";
|
|
18070
|
+
var DOMAIN_RECIPE_SKILL_URL = "https://www.wix.com/skills/wix-manage/references/domains/domain-search-purchase-and-connect.md";
|
|
18071
|
+
function formatDropResult(r) {
|
|
18072
|
+
const shown = r.files.slice(0, 60);
|
|
18073
|
+
return [
|
|
18074
|
+
"Uploaded; the site is live with this file set.",
|
|
18075
|
+
`Live URL: ${r.siteUrl ?? "(not returned)"}`,
|
|
18076
|
+
`metaSiteId: ${r.metaSiteId} \u2014 pass it as \`siteId\` to update this site later; every upload replaces the whole file set.`,
|
|
18077
|
+
`Dashboard: https://manage.wix.com/dashboard/${r.metaSiteId}`,
|
|
18078
|
+
`${r.files.length} file(s): ${shown.join(", ")}${r.files.length > shown.length ? ", \u2026" : ""}`,
|
|
18079
|
+
"",
|
|
18080
|
+
`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}).`,
|
|
18081
|
+
"",
|
|
18082
|
+
`- 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.`,
|
|
18083
|
+
`- Wix business features (${WIX_BUSINESS_FEATURES}): call StartHeadlessBuild with startingPoint "an existing Wix Headless site"; this site is one.`
|
|
18084
|
+
].join("\n");
|
|
18085
|
+
}
|
|
18086
|
+
function curlPut(zipName, put, fallback) {
|
|
18087
|
+
const headers = Object.entries(
|
|
18088
|
+
put.headers && Object.keys(put.headers).length ? put.headers : fallback
|
|
18089
|
+
);
|
|
18090
|
+
return [
|
|
18091
|
+
"curl -X PUT",
|
|
18092
|
+
...headers.map(([k, v]) => `-H "${k}: ${v}"`),
|
|
18093
|
+
`--data-binary @${zipName} "${put.putUrl}"`
|
|
18094
|
+
].join(" ");
|
|
18095
|
+
}
|
|
18096
|
+
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`.";
|
|
18097
|
+
var SOURCE_ZIP_CONTENTS = "Source zip, from the project root: the project minus `node_modules`, `dist`, `.wix`, `.astro`, `.env*` and `.agents`.";
|
|
18098
|
+
function formatPrepareResult(siteId, urls, size, given = {}) {
|
|
18099
|
+
const expiry = urls.expiresAt ? ` They expire at ${urls.expiresAt}.` : "";
|
|
18100
|
+
const secondCall = {
|
|
18101
|
+
siteId,
|
|
18102
|
+
uploadKey: urls.uploadKey
|
|
18103
|
+
};
|
|
18104
|
+
const zipHeaders = {
|
|
18105
|
+
"content-type": "application/zip",
|
|
18106
|
+
...size ? { "content-length": String(size) } : {}
|
|
18107
|
+
};
|
|
18108
|
+
const lines = [
|
|
18109
|
+
`Upload URLs ready for site ${siteId}.${expiry} Two steps, then call this tool again.`,
|
|
18110
|
+
"",
|
|
18111
|
+
`1. ${BUILD_ZIP_CONTENTS} 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:`,
|
|
18112
|
+
` ${curlPut("build.zip", urls, zipHeaders)}`
|
|
18113
|
+
];
|
|
18114
|
+
if (given.expectedHeadRevisionId)
|
|
18115
|
+
secondCall.expectedHeadRevisionId = given.expectedHeadRevisionId;
|
|
18116
|
+
if (urls.source) {
|
|
18117
|
+
const s = urls.source;
|
|
18118
|
+
secondCall.sourceUploadId = s.uploadId;
|
|
18119
|
+
if (!secondCall.expectedHeadRevisionId && s.headRevisionId)
|
|
18120
|
+
secondCall.expectedHeadRevisionId = s.headRevisionId;
|
|
18121
|
+
lines.push(
|
|
18122
|
+
"",
|
|
18123
|
+
`2. ${SOURCE_ZIP_CONTENTS} Upload it:`,
|
|
18124
|
+
` ${curlPut("source.zip", s, { "content-type": "application/zip" })}`,
|
|
18125
|
+
...s.expiresAt && s.expiresAt !== urls.expiresAt ? [` This URL expires at ${s.expiresAt}.`] : [],
|
|
18126
|
+
...s.headRevisionId ? [
|
|
18127
|
+
` 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.`
|
|
18128
|
+
] : []
|
|
18129
|
+
);
|
|
18130
|
+
} else {
|
|
18131
|
+
lines.push(
|
|
18132
|
+
"",
|
|
18133
|
+
"2. The site's repository takes no source this time: the project's source is not uploaded, and the second call carries no `sourceUploadId`."
|
|
18134
|
+
);
|
|
18135
|
+
}
|
|
18136
|
+
if (given.releaseNotes)
|
|
18137
|
+
secondCall.releaseNotes = releaseNotesForService(given.releaseNotes);
|
|
18138
|
+
lines.push(
|
|
18139
|
+
"",
|
|
18140
|
+
`3. Call UploadHeadlessWebsiteFiles again with ${JSON.stringify(secondCall)} (plus \`calledStartDeployWebsite\`, \`agentIdentity\`, \`reason\`). No \`files\` or \`attachments\` on that call.`
|
|
18141
|
+
);
|
|
18142
|
+
return lines.join("\n");
|
|
18143
|
+
}
|
|
18144
|
+
function formatBundleReleaseResult(siteId, r, notes = []) {
|
|
18145
|
+
return [
|
|
18146
|
+
"Released; the site is live.",
|
|
18147
|
+
`Live URL: ${r.siteUrl ?? "(not returned)"}`,
|
|
18148
|
+
`metaSiteId: ${siteId} \u2014 pass it as \`siteId\` to release this site again, the same way. The URL stays.`,
|
|
18149
|
+
`Dashboard: https://manage.wix.com/dashboard/${siteId}`,
|
|
18150
|
+
`Deployment ${r.deploymentId ?? "(id not returned)"}.${r.revision?.id ? ` Source stored as revision ${r.revision.id}.` : ""}`,
|
|
18151
|
+
...notes,
|
|
18152
|
+
"",
|
|
18153
|
+
"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.",
|
|
18154
|
+
"Give the user both links, each on its own line and named for what it is: the live site, and the site's dashboard."
|
|
18155
|
+
].join("\n");
|
|
18156
|
+
}
|
|
18157
|
+
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";
|
|
18158
|
+
var apiBody = (err) => err.response.data ? ` (${err.response.data.slice(0, 300)})` : "";
|
|
18159
|
+
function describeBundleUploadUrlError(err) {
|
|
18160
|
+
if (!(err instanceof WixFetchError)) {
|
|
18161
|
+
return err instanceof Error ? err.message : String(err);
|
|
18162
|
+
}
|
|
18163
|
+
const body = apiBody(err);
|
|
18164
|
+
switch (err.status) {
|
|
18165
|
+
case 400:
|
|
18166
|
+
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}`;
|
|
18167
|
+
case 429:
|
|
18168
|
+
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}`;
|
|
18169
|
+
default:
|
|
18170
|
+
return err.message;
|
|
18171
|
+
}
|
|
18172
|
+
}
|
|
18173
|
+
function deployAuthorization(siteHeaders) {
|
|
18174
|
+
const authorization3 = siteHeaders.Authorization ?? siteHeaders.authorization;
|
|
18175
|
+
return authorization3 ? { deployAuthorization: authorization3 } : {};
|
|
18176
|
+
}
|
|
18177
|
+
var RELEASE_NOTES_MAX_CHARS = 200;
|
|
18178
|
+
function releaseNotesForService(notes) {
|
|
18179
|
+
const chars = [...notes.replace(/\s+/g, " ").trim()];
|
|
18180
|
+
return chars.length <= RELEASE_NOTES_MAX_CHARS ? chars.join("") : `${chars.slice(0, RELEASE_NOTES_MAX_CHARS - 1).join("").trimEnd()}\u2026`;
|
|
18181
|
+
}
|
|
18182
|
+
function describeBundleReleaseError(err, uploadKey, retry = "prepare") {
|
|
18183
|
+
if (!(err instanceof WixFetchError)) {
|
|
18184
|
+
return err instanceof Error ? err.message : String(err);
|
|
18185
|
+
}
|
|
18186
|
+
const body = apiBody(err);
|
|
18187
|
+
const code = err.response.data ?? "";
|
|
18188
|
+
switch (err.status) {
|
|
18189
|
+
case 404:
|
|
18190
|
+
return `Nothing uploaded for uploadKey ${uploadKey}: the build zip was not PUT to its URL, or the URL expired. To try again, ${startOver(retry)}.${body}`;
|
|
18191
|
+
case 409:
|
|
18192
|
+
return `The project's source repository moved since the upload URLs were made; nothing was released. ${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}`;
|
|
18193
|
+
case 403:
|
|
18194
|
+
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}`;
|
|
18195
|
+
case 413:
|
|
18196
|
+
case 428:
|
|
18197
|
+
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}`;
|
|
18198
|
+
case 400:
|
|
18199
|
+
if (code.includes("EXPECTED_HEAD_REQUIRED")) {
|
|
18200
|
+
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}`;
|
|
18201
|
+
}
|
|
18202
|
+
if (code.includes("BUNDLE_TOO_LARGE")) {
|
|
18203
|
+
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}`;
|
|
18204
|
+
}
|
|
18205
|
+
if (code.includes("INVALID_RELEASE")) {
|
|
18206
|
+
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}`;
|
|
18207
|
+
}
|
|
18208
|
+
if (code.includes("INVALID_UPLOAD_KEY")) {
|
|
18209
|
+
return `\`uploadKey\` is the lowercase UUID the \`prepare\` call returned; pass it as it came.${body}`;
|
|
18210
|
+
}
|
|
18211
|
+
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}`;
|
|
18212
|
+
default:
|
|
18213
|
+
if (err.status >= 500) {
|
|
18214
|
+
const what = err.status === 503 ? "The release could not hold its upload" : `Wix's release step failed on its side (HTTP ${err.status})`;
|
|
18215
|
+
return `${what}; nothing was released. The staged upload is kept under uploadKey ${uploadKey}, so do not call \`prepare\` again 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 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`"}. 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}`;
|
|
18216
|
+
}
|
|
18217
|
+
return err.message;
|
|
18218
|
+
}
|
|
18219
|
+
}
|
|
18220
|
+
function validateBundleArgs(a) {
|
|
18221
|
+
const hasContent = !!a.files?.trim() || !!(a.attachments && a.attachments.length);
|
|
18222
|
+
if (a.prepare) {
|
|
18223
|
+
if (hasContent || a.uploadKey || a.sourceUploadId) {
|
|
18224
|
+
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?}.";
|
|
18225
|
+
}
|
|
18226
|
+
if (a.size === void 0) {
|
|
18227
|
+
return "`prepare: true` needs `size`: zip the build first and pass the zip's length in bytes (`wc -c < build.zip`).";
|
|
16810
18228
|
}
|
|
16811
|
-
|
|
16812
|
-
|
|
16813
|
-
throw new AttachmentError(
|
|
16814
|
-
`${label}: the download redirected to a URL that ${problem}; refused.`
|
|
16815
|
-
);
|
|
18229
|
+
if (!Number.isInteger(a.size) || a.size < 1 || a.size > MAX_BUNDLE_BYTES) {
|
|
18230
|
+
return `\`size\` is the build zip's length in bytes, an integer from 1 to ${MAX_BUNDLE_BYTES} (100 MB); got ${a.size}.`;
|
|
16816
18231
|
}
|
|
16817
|
-
|
|
18232
|
+
return null;
|
|
16818
18233
|
}
|
|
16819
|
-
|
|
16820
|
-
|
|
16821
|
-
const declared = Number(res.headers.get("content-length") ?? 0);
|
|
16822
|
-
if (declared > budget) throw overBudget();
|
|
16823
|
-
if (!res.body) {
|
|
16824
|
-
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
16825
|
-
if (bytes.byteLength > budget) throw overBudget();
|
|
16826
|
-
return bytes;
|
|
18234
|
+
if (a.size !== void 0) {
|
|
18235
|
+
return "`size` goes only with `prepare: true`: it is the length of the build zip that call makes an upload URL for.";
|
|
16827
18236
|
}
|
|
16828
|
-
|
|
16829
|
-
|
|
16830
|
-
|
|
16831
|
-
for (; ; ) {
|
|
16832
|
-
const { done, value } = await reader.read();
|
|
16833
|
-
if (done) break;
|
|
16834
|
-
total += value.byteLength;
|
|
16835
|
-
if (total > budget) {
|
|
16836
|
-
await reader.cancel().catch(() => void 0);
|
|
16837
|
-
throw overBudget();
|
|
18237
|
+
if (a.uploadKey) {
|
|
18238
|
+
if (hasContent) {
|
|
18239
|
+
return "`uploadKey` releases a build zip already uploaded to its URL: pass no `files` or `attachments` with it.";
|
|
16838
18240
|
}
|
|
16839
|
-
|
|
18241
|
+
return null;
|
|
16840
18242
|
}
|
|
16841
|
-
|
|
16842
|
-
|
|
16843
|
-
for (const c of chunks) {
|
|
16844
|
-
out.set(c, offset);
|
|
16845
|
-
offset += c.byteLength;
|
|
18243
|
+
if (a.sourceUploadId) {
|
|
18244
|
+
return "`sourceUploadId` goes with the `uploadKey` the `prepare` call returned.";
|
|
16846
18245
|
}
|
|
16847
|
-
|
|
18246
|
+
if (a.expectedHeadRevisionId && !hasContent) {
|
|
18247
|
+
return "`expectedHeadRevisionId` goes with `uploadKey`, or with `files`/`attachments` holding a build.";
|
|
18248
|
+
}
|
|
18249
|
+
return null;
|
|
16848
18250
|
}
|
|
16849
|
-
async function
|
|
16850
|
-
|
|
16851
|
-
|
|
16852
|
-
|
|
16853
|
-
|
|
16854
|
-
|
|
16855
|
-
|
|
16856
|
-
|
|
16857
|
-
|
|
16858
|
-
|
|
16859
|
-
|
|
16860
|
-
|
|
16861
|
-
|
|
16862
|
-
|
|
16863
|
-
|
|
16864
|
-
|
|
16865
|
-
|
|
16866
|
-
|
|
16867
|
-
|
|
16868
|
-
|
|
16869
|
-
if (problem) {
|
|
16870
|
-
throw new AttachmentError(
|
|
16871
|
-
`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.`
|
|
16872
|
-
);
|
|
16873
|
-
}
|
|
16874
|
-
items.push({ a, url });
|
|
18251
|
+
async function readJson(res, what) {
|
|
18252
|
+
const text = await res.text();
|
|
18253
|
+
if (!res.ok)
|
|
18254
|
+
throw new WixFetchError(res.status, res.statusText, `${what}: ${text}`);
|
|
18255
|
+
try {
|
|
18256
|
+
return JSON.parse(text);
|
|
18257
|
+
} catch {
|
|
18258
|
+
throw new Error(`${what} returned a non-JSON body: ${text.slice(0, 200)}`);
|
|
18259
|
+
}
|
|
18260
|
+
}
|
|
18261
|
+
async function putZip(fetchImpl, put, bytes, what) {
|
|
18262
|
+
const headers = Object.fromEntries(
|
|
18263
|
+
Object.entries(put.headers ?? { "content-type": "application/zip" }).filter(
|
|
18264
|
+
([k]) => k.toLowerCase() !== "content-length"
|
|
18265
|
+
)
|
|
18266
|
+
);
|
|
18267
|
+
const res = await fetchImpl(put.putUrl, {
|
|
18268
|
+
method: "PUT",
|
|
18269
|
+
headers,
|
|
18270
|
+
body: bytes
|
|
16875
18271
|
});
|
|
16876
|
-
|
|
16877
|
-
|
|
16878
|
-
|
|
16879
|
-
|
|
16880
|
-
const label = `attachments[${i}] (${path})`;
|
|
16881
|
-
let res;
|
|
16882
|
-
try {
|
|
16883
|
-
res = await fetchAllowed(url, fetchImpl, label);
|
|
16884
|
-
} catch (e) {
|
|
16885
|
-
if (e instanceof AttachmentError) throw e;
|
|
16886
|
-
throw new AttachmentError(
|
|
16887
|
-
`${label}: download failed (${e.message}).`
|
|
16888
|
-
);
|
|
16889
|
-
}
|
|
16890
|
-
if (!res.ok) {
|
|
16891
|
-
await res.body?.cancel().catch(() => void 0);
|
|
16892
|
-
throw new AttachmentError(
|
|
16893
|
-
`${label}: download returned HTTP ${res.status}. The download_url may have expired; attach the file again.`
|
|
16894
|
-
);
|
|
16895
|
-
}
|
|
16896
|
-
const bytes = await readWithin(
|
|
16897
|
-
res,
|
|
16898
|
-
remaining,
|
|
16899
|
-
() => new AttachmentError(
|
|
16900
|
-
`${label} pushes the attachments past ${MAX_ATTACHMENTS_BYTES} bytes in total for this call; split the upload across calls.`
|
|
16901
|
-
)
|
|
18272
|
+
if (!res.ok) {
|
|
18273
|
+
const text = await res.text().catch(() => "");
|
|
18274
|
+
throw new Error(
|
|
18275
|
+
`The ${what} upload was refused (${res.status}${text ? `: ${text.slice(0, 200)}` : ""}); call this tool again with the same files.`
|
|
16902
18276
|
);
|
|
16903
|
-
remaining -= bytes.byteLength;
|
|
16904
|
-
out.push({ path, content: toBase64(bytes), encoding: "base64" });
|
|
16905
18277
|
}
|
|
16906
|
-
return out;
|
|
16907
18278
|
}
|
|
16908
|
-
|
|
16909
|
-
|
|
16910
|
-
|
|
16911
|
-
|
|
16912
|
-
const
|
|
16913
|
-
|
|
16914
|
-
|
|
16915
|
-
|
|
16916
|
-
|
|
16917
|
-
|
|
16918
|
-
|
|
18279
|
+
async function releaseBuildByUrl(o) {
|
|
18280
|
+
const log = o.log ?? (() => {
|
|
18281
|
+
});
|
|
18282
|
+
const headers = { ...o.siteHeaders, "Content-Type": "application/json" };
|
|
18283
|
+
const urls = await readJson(
|
|
18284
|
+
await o.fetchImpl(`${HEADLESS_BUSINESS}/${o.siteId}/bundle-upload-url`, {
|
|
18285
|
+
method: "POST",
|
|
18286
|
+
headers,
|
|
18287
|
+
body: JSON.stringify({ size: o.siteZip.byteLength })
|
|
18288
|
+
}),
|
|
18289
|
+
"bundle-upload-url"
|
|
18290
|
+
);
|
|
18291
|
+
if (!urls.uploadKey || !urls.putUrl) {
|
|
18292
|
+
throw new Error("bundle-upload-url returned no uploadKey or putUrl.");
|
|
16919
18293
|
}
|
|
16920
|
-
|
|
18294
|
+
await putZip(o.fetchImpl, urls, o.siteZip, "site zip");
|
|
18295
|
+
o.onStaged?.(urls.uploadKey);
|
|
18296
|
+
log(
|
|
18297
|
+
`staged ${urls.uploadKey} (${o.siteZip.byteLength} bytes), source ${urls.source ? `offered, ${o.sourceFiles.length} file(s)` : "not offered"}`
|
|
18298
|
+
);
|
|
18299
|
+
const source = urls.source && o.sourceFiles.length > 0 ? urls.source : void 0;
|
|
18300
|
+
if (source) {
|
|
18301
|
+
await putZip(o.fetchImpl, source, zipStored(o.sourceFiles), "source zip");
|
|
18302
|
+
}
|
|
18303
|
+
return await readJson(
|
|
18304
|
+
await o.fetchImpl(`${HEADLESS_BUSINESS}/${o.siteId}/release-bundle`, {
|
|
18305
|
+
method: "POST",
|
|
18306
|
+
headers,
|
|
18307
|
+
body: JSON.stringify({
|
|
18308
|
+
uploadKey: urls.uploadKey,
|
|
18309
|
+
...deployAuthorization(o.siteHeaders),
|
|
18310
|
+
...o.releaseNotes ? { releaseNotes: releaseNotesForService(o.releaseNotes) } : {},
|
|
18311
|
+
...source ? {
|
|
18312
|
+
sourceUploadId: source.uploadId,
|
|
18313
|
+
...o.expectedHeadRevisionId ? { expectedHeadRevisionId: o.expectedHeadRevisionId } : {}
|
|
18314
|
+
} : {}
|
|
18315
|
+
})
|
|
18316
|
+
}),
|
|
18317
|
+
"release-bundle"
|
|
18318
|
+
);
|
|
18319
|
+
}
|
|
18320
|
+
function errorResult2(message) {
|
|
18321
|
+
return {
|
|
18322
|
+
isError: true,
|
|
18323
|
+
content: [
|
|
18324
|
+
{
|
|
18325
|
+
type: "text",
|
|
18326
|
+
text: `UploadHeadlessWebsiteFiles error: ${message}`
|
|
18327
|
+
}
|
|
18328
|
+
]
|
|
18329
|
+
};
|
|
18330
|
+
}
|
|
18331
|
+
function addUploadHeadlessWebsiteFilesTool(server, options) {
|
|
18332
|
+
const { authStrategy, toolDescriptions } = options;
|
|
18333
|
+
const clientName = () => options.clientName ?? server.clientName?.() ?? server.server?.getClientVersion?.()?.name;
|
|
18334
|
+
const fetchImpl = options.fetchImpl ?? ((u, i) => fetch(u, i));
|
|
18335
|
+
const d = UPLOAD_HEADLESS_WEBSITE_FILES_PARAM_DESCRIPTIONS;
|
|
18336
|
+
const registered = server.tool(
|
|
18337
|
+
UPLOAD_HEADLESS_WEBSITE_FILES_TOOL_NAME,
|
|
18338
|
+
toolDescriptions?.[UPLOAD_HEADLESS_WEBSITE_FILES_TOOL_NAME] ?? defaultToolDescriptions.UploadHeadlessWebsiteFiles,
|
|
18339
|
+
{
|
|
18340
|
+
files: external_exports.string().optional().describe(d.files),
|
|
18341
|
+
attachments: external_exports.array(
|
|
18342
|
+
external_exports.object({
|
|
18343
|
+
download_url: external_exports.string(),
|
|
18344
|
+
file_id: external_exports.string(),
|
|
18345
|
+
file_name: external_exports.string().optional(),
|
|
18346
|
+
mime_type: external_exports.string().optional()
|
|
18347
|
+
})
|
|
18348
|
+
).optional().describe(d.attachments),
|
|
18349
|
+
prepare: external_exports.boolean().optional().describe(d.prepare),
|
|
18350
|
+
size: external_exports.number().int().optional().describe(d.size),
|
|
18351
|
+
uploadKey: external_exports.string().min(1).optional().describe(d.uploadKey),
|
|
18352
|
+
sourceUploadId: external_exports.string().min(1).optional().describe(d.sourceUploadId),
|
|
18353
|
+
expectedHeadRevisionId: external_exports.string().min(1).optional().describe(d.expectedHeadRevisionId),
|
|
18354
|
+
releaseNotes: external_exports.string().optional().describe(d.releaseNotes),
|
|
18355
|
+
siteId: external_exports.string().uuid("siteId must be a Wix metaSiteId (UUID)").describe(d.siteId),
|
|
18356
|
+
calledStartDeployWebsite: external_exports.boolean().describe(d.calledStartDeployWebsite),
|
|
18357
|
+
agentIdentity: external_exports.string().min(1).describe(d.agentIdentity),
|
|
18358
|
+
reason: external_exports.string().describe(d.reason)
|
|
18359
|
+
},
|
|
18360
|
+
// openWorldHint true: it fetches agent-supplied attachment URLs and publishes a
|
|
18361
|
+
// public website.
|
|
18362
|
+
{ readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
18363
|
+
async (rawArgs, extra) => {
|
|
18364
|
+
const {
|
|
18365
|
+
files,
|
|
18366
|
+
attachments,
|
|
18367
|
+
prepare,
|
|
18368
|
+
size,
|
|
18369
|
+
uploadKey,
|
|
18370
|
+
sourceUploadId,
|
|
18371
|
+
expectedHeadRevisionId,
|
|
18372
|
+
releaseNotes,
|
|
18373
|
+
siteId,
|
|
18374
|
+
calledStartDeployWebsite,
|
|
18375
|
+
agentIdentity,
|
|
18376
|
+
reason
|
|
18377
|
+
} = rawArgs;
|
|
18378
|
+
const phase = prepare ? "prepare" : uploadKey ? "release-bundle" : "files";
|
|
18379
|
+
logger.log(
|
|
18380
|
+
`[UploadHeadlessWebsiteFiles] siteId=${siteId} phase=${phase} calledStartDeployWebsite=${calledStartDeployWebsite} agent="${redactPersonalData(agentIdentity)}" files=${files?.length ?? 0} chars attachments=${attachments?.length ?? 0} reason="${redactPersonalData(reason)}"`
|
|
18381
|
+
);
|
|
18382
|
+
const invalid = validateBundleArgs(rawArgs);
|
|
18383
|
+
if (invalid) return errorResult2(invalid);
|
|
18384
|
+
if (prepare || uploadKey) {
|
|
18385
|
+
const siteHeaders = await authStrategy.getSiteAuthHeaders(siteId);
|
|
18386
|
+
const headers = {
|
|
18387
|
+
...siteHeaders,
|
|
18388
|
+
"Content-Type": "application/json"
|
|
18389
|
+
};
|
|
18390
|
+
try {
|
|
18391
|
+
extra?.setToolBiParams?.({ siteId });
|
|
18392
|
+
if (prepare) {
|
|
18393
|
+
const urls = await readJson(
|
|
18394
|
+
await fetchImpl(
|
|
18395
|
+
`${HEADLESS_BUSINESS}/${siteId}/bundle-upload-url`,
|
|
18396
|
+
{
|
|
18397
|
+
method: "POST",
|
|
18398
|
+
headers,
|
|
18399
|
+
body: JSON.stringify({ size })
|
|
18400
|
+
}
|
|
18401
|
+
),
|
|
18402
|
+
"bundle-upload-url"
|
|
18403
|
+
);
|
|
18404
|
+
if (!urls.uploadKey || !urls.putUrl) {
|
|
18405
|
+
throw new Error(
|
|
18406
|
+
"bundle-upload-url returned no uploadKey or putUrl."
|
|
18407
|
+
);
|
|
18408
|
+
}
|
|
18409
|
+
extra?.setToolBiParams?.({
|
|
18410
|
+
siteId,
|
|
18411
|
+
upload: { path: "prepare", sourceOffered: Boolean(urls.source) }
|
|
18412
|
+
});
|
|
18413
|
+
return {
|
|
18414
|
+
content: [
|
|
18415
|
+
{
|
|
18416
|
+
type: "text",
|
|
18417
|
+
text: formatPrepareResult(siteId, urls, size, {
|
|
18418
|
+
expectedHeadRevisionId,
|
|
18419
|
+
releaseNotes
|
|
18420
|
+
})
|
|
18421
|
+
}
|
|
18422
|
+
]
|
|
18423
|
+
};
|
|
18424
|
+
}
|
|
18425
|
+
const released = await readJson(
|
|
18426
|
+
await fetchImpl(`${HEADLESS_BUSINESS}/${siteId}/release-bundle`, {
|
|
18427
|
+
method: "POST",
|
|
18428
|
+
headers,
|
|
18429
|
+
body: JSON.stringify({
|
|
18430
|
+
uploadKey,
|
|
18431
|
+
...deployAuthorization(siteHeaders),
|
|
18432
|
+
...sourceUploadId ? { sourceUploadId } : {},
|
|
18433
|
+
...expectedHeadRevisionId ? { expectedHeadRevisionId } : {},
|
|
18434
|
+
...releaseNotes ? { releaseNotes: releaseNotesForService(releaseNotes) } : {}
|
|
18435
|
+
})
|
|
18436
|
+
}),
|
|
18437
|
+
"release-bundle"
|
|
18438
|
+
);
|
|
18439
|
+
extra?.setToolBiParams?.({
|
|
18440
|
+
siteId,
|
|
18441
|
+
upload: {
|
|
18442
|
+
path: "release-bundle",
|
|
18443
|
+
sourceStored: Boolean(released.revision?.id)
|
|
18444
|
+
}
|
|
18445
|
+
});
|
|
18446
|
+
return {
|
|
18447
|
+
content: [
|
|
18448
|
+
{
|
|
18449
|
+
type: "text",
|
|
18450
|
+
text: formatBundleReleaseResult(siteId, released)
|
|
18451
|
+
}
|
|
18452
|
+
]
|
|
18453
|
+
};
|
|
18454
|
+
} catch (err) {
|
|
18455
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
18456
|
+
logger.log(`[UploadHeadlessWebsiteFiles] ${phase} error: ${message}`);
|
|
18457
|
+
rethrowIfUnauthorized(err);
|
|
18458
|
+
await authStrategy.enforceValidWixToken?.(err);
|
|
18459
|
+
return errorResult2(
|
|
18460
|
+
uploadKey ? describeBundleReleaseError(err, uploadKey) : describeBundleUploadUrlError(err)
|
|
18461
|
+
);
|
|
18462
|
+
}
|
|
18463
|
+
}
|
|
18464
|
+
if (!files?.trim() && !(attachments && attachments.length)) {
|
|
18465
|
+
return errorResult2(
|
|
18466
|
+
"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`."
|
|
18467
|
+
);
|
|
18468
|
+
}
|
|
18469
|
+
let siteFiles = [];
|
|
18470
|
+
let uploaded = [];
|
|
18471
|
+
let given = [];
|
|
18472
|
+
let build = null;
|
|
18473
|
+
try {
|
|
18474
|
+
const fromAttachments = (await resolveAttachments(attachments, fetchImpl)).map((a) => ({ path: a.path, bytes: base64ToBytes(a.content) }));
|
|
18475
|
+
given = [...parseFileBundle(files), ...fromAttachments];
|
|
18476
|
+
uploaded = await expandSiteFiles(given);
|
|
18477
|
+
build = findHeadlessBuild(uploaded);
|
|
18478
|
+
if (!build) siteFiles = staticSiteFiles(uploaded);
|
|
18479
|
+
} catch (e) {
|
|
18480
|
+
if (e instanceof AttachmentError || e instanceof Error)
|
|
18481
|
+
return errorResult2(e.message);
|
|
18482
|
+
throw e;
|
|
18483
|
+
}
|
|
18484
|
+
if (!build && expectedHeadRevisionId) {
|
|
18485
|
+
return errorResult2(
|
|
18486
|
+
"`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."
|
|
18487
|
+
);
|
|
18488
|
+
}
|
|
18489
|
+
if (build) {
|
|
18490
|
+
const siteHeaders = await authStrategy.getSiteAuthHeaders(siteId);
|
|
18491
|
+
let stagedKey;
|
|
18492
|
+
try {
|
|
18493
|
+
extra?.setToolBiParams?.({ siteId });
|
|
18494
|
+
const sourceFiles = buildSourceFiles(uploaded, build);
|
|
18495
|
+
const statics = withoutEmptyStatics(uploaded, build);
|
|
18496
|
+
const released = await releaseBuildByUrl({
|
|
18497
|
+
siteId,
|
|
18498
|
+
// One zip as given goes as is; anything else is zipped from the expanded files.
|
|
18499
|
+
siteZip: given.length === 1 && isZip(given[0]) && statics.dropped.length === 0 ? given[0].bytes : zipStored(statics.files),
|
|
18500
|
+
sourceFiles,
|
|
18501
|
+
expectedHeadRevisionId,
|
|
18502
|
+
releaseNotes,
|
|
18503
|
+
siteHeaders,
|
|
18504
|
+
fetchImpl,
|
|
18505
|
+
onStaged: (key) => {
|
|
18506
|
+
stagedKey = key;
|
|
18507
|
+
},
|
|
18508
|
+
log: (line) => logger.log(`[UploadHeadlessWebsiteFiles] release ${line}`)
|
|
18509
|
+
});
|
|
18510
|
+
const sourceStored = Boolean(released.revision?.id);
|
|
18511
|
+
extra?.setToolBiParams?.({
|
|
18512
|
+
siteId,
|
|
18513
|
+
upload: {
|
|
18514
|
+
path: "build",
|
|
18515
|
+
sourceStored,
|
|
18516
|
+
sourceFiles: sourceFiles.length,
|
|
18517
|
+
emptyStaticsDropped: statics.dropped.length
|
|
18518
|
+
}
|
|
18519
|
+
});
|
|
18520
|
+
const notes = [
|
|
18521
|
+
...statics.dropped.length ? [
|
|
18522
|
+
`Left out, empty: ${statics.dropped.slice(0, 20).join(", ")} (the hosting serves no file of size 0).`
|
|
18523
|
+
] : [],
|
|
18524
|
+
...sourceStored ? [] : [
|
|
18525
|
+
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."
|
|
18526
|
+
]
|
|
18527
|
+
];
|
|
18528
|
+
return {
|
|
18529
|
+
content: [
|
|
18530
|
+
{
|
|
18531
|
+
type: "text",
|
|
18532
|
+
text: formatBundleReleaseResult(siteId, released, notes)
|
|
18533
|
+
}
|
|
18534
|
+
]
|
|
18535
|
+
};
|
|
18536
|
+
} catch (err) {
|
|
18537
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
18538
|
+
logger.log(`[UploadHeadlessWebsiteFiles] release error: ${message}`);
|
|
18539
|
+
rethrowIfUnauthorized(err);
|
|
18540
|
+
await authStrategy.enforceValidWixToken?.(err);
|
|
18541
|
+
return errorResult2(
|
|
18542
|
+
stagedKey ? describeBundleReleaseError(err, stagedKey, "resend") : describeBundleUploadUrlError(err)
|
|
18543
|
+
);
|
|
18544
|
+
}
|
|
18545
|
+
}
|
|
18546
|
+
const authHeaders = await authStrategy.getAccountAuthHeaders();
|
|
18547
|
+
try {
|
|
18548
|
+
const metaSiteId = siteId;
|
|
18549
|
+
extra?.setToolBiParams?.({ siteId: metaSiteId });
|
|
18550
|
+
const dropped = await readJson(
|
|
18551
|
+
await fetchImpl(
|
|
18552
|
+
`${HEADLESS_BUSINESS}/${metaSiteId}/drop${dropAttribution(clientName())}`,
|
|
18553
|
+
{
|
|
18554
|
+
method: "POST",
|
|
18555
|
+
headers: { ...authHeaders },
|
|
18556
|
+
// fetch sets the multipart boundary itself
|
|
18557
|
+
body: buildFormData(siteFiles)
|
|
18558
|
+
}
|
|
18559
|
+
),
|
|
18560
|
+
"drop"
|
|
18561
|
+
);
|
|
18562
|
+
extra?.setToolBiParams?.({
|
|
18563
|
+
siteId: metaSiteId,
|
|
18564
|
+
upload: { path: "drop", files: siteFiles.length }
|
|
18565
|
+
});
|
|
18566
|
+
return {
|
|
18567
|
+
content: [
|
|
18568
|
+
{
|
|
18569
|
+
type: "text",
|
|
18570
|
+
text: formatDropResult({
|
|
18571
|
+
metaSiteId,
|
|
18572
|
+
siteUrl: dropped.siteUrl,
|
|
18573
|
+
uploadId: dropped.uploadId,
|
|
18574
|
+
files: siteFiles.map((f) => f.path)
|
|
18575
|
+
}) + (releaseNotes ? "\n\nRelease notes are kept for a Wix Astro build only; a static drop has none, so yours were not stored." : "")
|
|
18576
|
+
}
|
|
18577
|
+
]
|
|
18578
|
+
};
|
|
18579
|
+
} catch (err) {
|
|
18580
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
18581
|
+
logger.log(`[UploadHeadlessWebsiteFiles] Error: ${message}`);
|
|
18582
|
+
rethrowIfUnauthorized(err);
|
|
18583
|
+
await authStrategy.enforceValidWixToken?.(err);
|
|
18584
|
+
return errorResult2(message);
|
|
18585
|
+
}
|
|
18586
|
+
}
|
|
18587
|
+
);
|
|
18588
|
+
registered?.update?.({
|
|
18589
|
+
_meta: { ...registered._meta, "openai/fileParams": ["attachments"] }
|
|
18590
|
+
});
|
|
16921
18591
|
}
|
|
16922
18592
|
|
|
16923
18593
|
// src/code-mode/index.ts
|
|
@@ -16931,7 +18601,8 @@ function addSearchCodeModeTool(server, options) {
|
|
|
16931
18601
|
authStrategy,
|
|
16932
18602
|
disableTools = [],
|
|
16933
18603
|
toolDescriptions,
|
|
16934
|
-
maxResponseSize
|
|
18604
|
+
maxResponseSize,
|
|
18605
|
+
docsSite
|
|
16935
18606
|
} = options;
|
|
16936
18607
|
if (disableTools.includes(SEARCH_TOOL_NAME)) {
|
|
16937
18608
|
return;
|
|
@@ -16943,7 +18614,8 @@ function addSearchCodeModeTool(server, options) {
|
|
|
16943
18614
|
description,
|
|
16944
18615
|
maxResponseSize,
|
|
16945
18616
|
authStrategy,
|
|
16946
|
-
codeModeFetch
|
|
18617
|
+
codeModeFetch,
|
|
18618
|
+
docsSite
|
|
16947
18619
|
);
|
|
16948
18620
|
}
|
|
16949
18621
|
function addExecuteCodeModeTool(server, options) {
|
|
@@ -16969,6 +18641,7 @@ function addExecuteCodeModeTool(server, options) {
|
|
|
16969
18641
|
});
|
|
16970
18642
|
}
|
|
16971
18643
|
var SEARCH_MAX_RESPONSE_CHARS = 64e3;
|
|
18644
|
+
var EXECUTE_MAX_RESPONSE_CHARS = 1e5;
|
|
16972
18645
|
async function resolveLoopbackAuthorization(authStrategy) {
|
|
16973
18646
|
try {
|
|
16974
18647
|
return (await authStrategy?.getAccountAuthHeaders())?.Authorization;
|
|
@@ -16976,7 +18649,7 @@ async function resolveLoopbackAuthorization(authStrategy) {
|
|
|
16976
18649
|
return void 0;
|
|
16977
18650
|
}
|
|
16978
18651
|
}
|
|
16979
|
-
function registerSearchTool(server, codeModeBaseUrl, description, maxResponseSize, authStrategy, codeModeFetch = defaultCodeModeFetch) {
|
|
18652
|
+
function registerSearchTool(server, codeModeBaseUrl, description, maxResponseSize, authStrategy, codeModeFetch = defaultCodeModeFetch, docsSite) {
|
|
16980
18653
|
const searchUrl = `${codeModeBaseUrl}/api/code-mode/search`;
|
|
16981
18654
|
server.tool(
|
|
16982
18655
|
SEARCH_TOOL_NAME,
|
|
@@ -17002,7 +18675,8 @@ function registerSearchTool(server, codeModeBaseUrl, description, maxResponseSiz
|
|
|
17002
18675
|
"Content-Type": "application/json",
|
|
17003
18676
|
...authorization3 ? { Authorization: authorization3 } : {}
|
|
17004
18677
|
},
|
|
17005
|
-
|
|
18678
|
+
// docsSite only when set: without it the request is as before.
|
|
18679
|
+
body: JSON.stringify(docsSite ? { code, docsSite } : { code })
|
|
17006
18680
|
});
|
|
17007
18681
|
if (!resp.ok) {
|
|
17008
18682
|
const errorBody = await resp.text();
|
|
@@ -17029,8 +18703,14 @@ function registerSearchTool(server, codeModeBaseUrl, description, maxResponseSiz
|
|
|
17029
18703
|
]
|
|
17030
18704
|
};
|
|
17031
18705
|
}
|
|
18706
|
+
const result = isMintlifyDocsSite(docsSite) && data.result !== void 0 ? JSON.parse(
|
|
18707
|
+
(await rewriteDocsLinks(
|
|
18708
|
+
toDevWixHostText(JSON.stringify(data.result)),
|
|
18709
|
+
{ label: "search SearchWixAPISpec" }
|
|
18710
|
+
)).text
|
|
18711
|
+
) : data.result;
|
|
17032
18712
|
const text = truncateResponse(
|
|
17033
|
-
|
|
18713
|
+
result,
|
|
17034
18714
|
maxResponseSize ?? SEARCH_MAX_RESPONSE_CHARS
|
|
17035
18715
|
);
|
|
17036
18716
|
return {
|
|
@@ -17063,10 +18743,13 @@ async function postExecute(executeUrl, body, authorization3, codeModeFetch = def
|
|
|
17063
18743
|
}
|
|
17064
18744
|
return await resp.json();
|
|
17065
18745
|
}
|
|
17066
|
-
function executeErrorResult(message) {
|
|
18746
|
+
function executeErrorResult(message, failedCalls) {
|
|
17067
18747
|
return {
|
|
17068
18748
|
isError: true,
|
|
17069
|
-
content: [{ type: "text", text: `Execute error: ${message}` }]
|
|
18749
|
+
content: [{ type: "text", text: `Execute error: ${message}` }],
|
|
18750
|
+
// The failed calls as fields, request ids included. The text carries the
|
|
18751
|
+
// ids too, for clients that don't read structured results.
|
|
18752
|
+
...failedCalls?.length ? { structuredContent: { failedCalls } } : {}
|
|
17070
18753
|
};
|
|
17071
18754
|
}
|
|
17072
18755
|
function executeTextResult(message) {
|
|
@@ -17076,7 +18759,7 @@ function executeTextResult(message) {
|
|
|
17076
18759
|
}
|
|
17077
18760
|
function formatExecuteResult(data, maxResponseSize, reportBi) {
|
|
17078
18761
|
if (data.error) {
|
|
17079
|
-
return executeErrorResult(data.error);
|
|
18762
|
+
return executeErrorResult(data.error, data.failedCalls);
|
|
17080
18763
|
}
|
|
17081
18764
|
reportBi?.({
|
|
17082
18765
|
originalLength: typeof data.result === "string" ? data.result.length : JSON.stringify(data.result)?.length
|
|
@@ -17096,7 +18779,13 @@ function formatExecuteResult(data, maxResponseSize, reportBi) {
|
|
|
17096
18779
|
}
|
|
17097
18780
|
return {
|
|
17098
18781
|
content: [
|
|
17099
|
-
{
|
|
18782
|
+
{
|
|
18783
|
+
type: "text",
|
|
18784
|
+
text: truncateResponse(
|
|
18785
|
+
data.result,
|
|
18786
|
+
maxResponseSize ?? EXECUTE_MAX_RESPONSE_CHARS
|
|
18787
|
+
)
|
|
18788
|
+
}
|
|
17100
18789
|
]
|
|
17101
18790
|
};
|
|
17102
18791
|
}
|
|
@@ -17134,7 +18823,7 @@ function addExecuteTool(server, options) {
|
|
|
17134
18823
|
"One sentence explaining the original user request and why you are executing code to complete it."
|
|
17135
18824
|
),
|
|
17136
18825
|
hasMutations: external_exports.boolean().describe(
|
|
17137
|
-
visitor ? "
|
|
18826
|
+
visitor ? "True if the code changes site data on the visitor\u2019s behalf (create, update, delete, import, upload); false if it only reads (get, query, list, search)." : "True if the code changes anything in the Wix account or a site (create, update, delete, publish, install, import, upload); false if it only reads (get, query, list, search)."
|
|
17138
18827
|
),
|
|
17139
18828
|
sourceDocUrls: external_exports.array(external_exports.string()).describe(d.sourceDocUrls),
|
|
17140
18829
|
files: external_exports.string().optional().describe(
|
|
@@ -17158,7 +18847,9 @@ function addExecuteTool(server, options) {
|
|
|
17158
18847
|
mime_type: external_exports.string().optional()
|
|
17159
18848
|
})
|
|
17160
18849
|
).optional().describe(
|
|
17161
|
-
|
|
18850
|
+
fileParamNote(
|
|
18851
|
+
"use `files`, or upload from your shell if it reaches the internet"
|
|
18852
|
+
) + ' 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.'
|
|
17162
18853
|
),
|
|
17163
18854
|
// Visitor mode takes an agent-supplied token and pins the site; account/site
|
|
17164
18855
|
// mode lets the agent pick the site.
|
|
@@ -17172,7 +18863,10 @@ function addExecuteTool(server, options) {
|
|
|
17172
18863
|
)
|
|
17173
18864
|
}
|
|
17174
18865
|
},
|
|
17175
|
-
|
|
18866
|
+
// openWorldHint true: agent code picks the Wix endpoints at run time (and may
|
|
18867
|
+
// receive platform-resolved attachment URLs); the sandbox's host allowlist
|
|
18868
|
+
// bounds it, but the operation set is open-ended, which is what the hint means.
|
|
18869
|
+
{ readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
17176
18870
|
async (rawArgs, extra) => {
|
|
17177
18871
|
const {
|
|
17178
18872
|
code,
|
|
@@ -17228,6 +18922,7 @@ function addExecuteTool(server, options) {
|
|
|
17228
18922
|
sessionId,
|
|
17229
18923
|
toolInvocationId,
|
|
17230
18924
|
clientName: server.server?.getClientVersion?.()?.name,
|
|
18925
|
+
...visitor ? { visitorMode: true } : {},
|
|
17231
18926
|
// A visitor token is a site-scoped bearer token: sent as siteToken
|
|
17232
18927
|
// (→ Authorization + wix-site-id) and as accountToken only to satisfy
|
|
17233
18928
|
// the runtime's required field. Account/site mode uses strategy tokens.
|
|
@@ -19254,6 +20949,7 @@ function createWixToolkit(options) {
|
|
|
19254
20949
|
readmeDocs,
|
|
19255
20950
|
authStrategy,
|
|
19256
20951
|
dynamicContextEnabled: true,
|
|
20952
|
+
includeCompanionApp: true,
|
|
19257
20953
|
// README-free defaults first so consumer overrides still win.
|
|
19258
20954
|
toolDescriptions: {
|
|
19259
20955
|
...descriptionsWithoutReadme,
|
|
@@ -19264,6 +20960,7 @@ function createWixToolkit(options) {
|
|
|
19264
20960
|
addDocsTools(server, void 0, {
|
|
19265
20961
|
authStrategy,
|
|
19266
20962
|
dynamicContextEnabled: true,
|
|
20963
|
+
includeCompanionApp: true,
|
|
19267
20964
|
toolDescriptions: descriptionsWithoutReadme
|
|
19268
20965
|
});
|
|
19269
20966
|
}
|
|
@@ -19304,7 +21001,9 @@ function createWixToolkit(options) {
|
|
|
19304
21001
|
authStrategy,
|
|
19305
21002
|
disableTools,
|
|
19306
21003
|
toolDescriptions: effectiveToolDescriptions,
|
|
19307
|
-
maxResponseSize
|
|
21004
|
+
maxResponseSize,
|
|
21005
|
+
// The connection's docs site (`docs.docsSite`) applies to spec search too.
|
|
21006
|
+
docsSite: docs?.docsSite
|
|
19308
21007
|
});
|
|
19309
21008
|
}
|
|
19310
21009
|
if (requestedTools.has("ExecuteWixAPI") && !disableTools?.includes("ExecuteWixAPI")) {
|
|
@@ -19317,6 +21016,13 @@ function createWixToolkit(options) {
|
|
|
19317
21016
|
maxResponseSize
|
|
19318
21017
|
});
|
|
19319
21018
|
}
|
|
21019
|
+
if (requestedTools.has("UploadHeadlessWebsiteFiles") && !disableTools?.includes("UploadHeadlessWebsiteFiles")) {
|
|
21020
|
+
addUploadHeadlessWebsiteFilesTool(server, {
|
|
21021
|
+
authStrategy,
|
|
21022
|
+
toolDescriptions: effectiveToolDescriptions,
|
|
21023
|
+
clientName
|
|
21024
|
+
});
|
|
21025
|
+
}
|
|
19320
21026
|
const requestedGraphqlTools = GRAPHQL_TOOL_NAMES.filter(
|
|
19321
21027
|
(name) => requestedTools.has(name)
|
|
19322
21028
|
);
|
|
@@ -19377,7 +21083,7 @@ function createWixToolkit(options) {
|
|
|
19377
21083
|
biLogger.updateDefaults({ _uuid: "" });
|
|
19378
21084
|
panorama.transaction(toolName).start({
|
|
19379
21085
|
toolInvocationId,
|
|
19380
|
-
params:
|
|
21086
|
+
params: serializeToolParamsForBi(mergedArgs)
|
|
19381
21087
|
});
|
|
19382
21088
|
}
|
|
19383
21089
|
const startTime = performance.now();
|
|
@@ -19397,7 +21103,8 @@ function createWixToolkit(options) {
|
|
|
19397
21103
|
cbResult,
|
|
19398
21104
|
{
|
|
19399
21105
|
returnedRows: toolBiParams.returnedRows,
|
|
19400
|
-
originalLength: toolBiParams.originalLength
|
|
21106
|
+
originalLength: toolBiParams.originalLength,
|
|
21107
|
+
upload: toolBiParams.upload
|
|
19401
21108
|
},
|
|
19402
21109
|
{ name: clientName }
|
|
19403
21110
|
);
|
|
@@ -19412,7 +21119,7 @@ function createWixToolkit(options) {
|
|
|
19412
21119
|
context: {
|
|
19413
21120
|
toolName,
|
|
19414
21121
|
toolInvocationId,
|
|
19415
|
-
params:
|
|
21122
|
+
params: serializeToolParamsForBi(mergedArgs),
|
|
19416
21123
|
error: JSON.stringify(cbResult, null, 2)
|
|
19417
21124
|
}
|
|
19418
21125
|
}
|
|
@@ -19430,7 +21137,7 @@ function createWixToolkit(options) {
|
|
|
19430
21137
|
context: {
|
|
19431
21138
|
toolName,
|
|
19432
21139
|
toolInvocationId,
|
|
19433
|
-
params:
|
|
21140
|
+
params: serializeToolParamsForBi(mergedArgs),
|
|
19434
21141
|
error: errorBody
|
|
19435
21142
|
}
|
|
19436
21143
|
});
|
|
@@ -19458,7 +21165,7 @@ function createWixToolkit(options) {
|
|
|
19458
21165
|
errorBody,
|
|
19459
21166
|
toolInvocationId,
|
|
19460
21167
|
toolName,
|
|
19461
|
-
params:
|
|
21168
|
+
params: serializeToolParamsForBi(mergedArgs),
|
|
19462
21169
|
origin: nodeEnv,
|
|
19463
21170
|
sessionId,
|
|
19464
21171
|
executionResult: toolSucceeded ? "Success" : "Error",
|
|
@@ -19506,7 +21213,8 @@ function createWixToolkit(options) {
|
|
|
19506
21213
|
const httpClient = new HttpClient();
|
|
19507
21214
|
return await getDynamicContext(authStrategy, httpClient, defaults2, {
|
|
19508
21215
|
partnerContextEnabled: true,
|
|
19509
|
-
includeAccountContext: true
|
|
21216
|
+
includeAccountContext: true,
|
|
21217
|
+
includeCompanionApp: true
|
|
19510
21218
|
}) || null;
|
|
19511
21219
|
} catch {
|
|
19512
21220
|
return null;
|