@wix/mcp 1.0.95 → 1.0.96
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/bin-standalone.js +352 -22
- package/build/cjs/index.cjs +910 -530
- package/build/cjs/toolkit.cjs +405 -46
- package/build/dts/api-call/index.js +23 -1
- package/build/dts/api-call/upload-headless-website-files.d.ts +31 -2
- package/build/dts/api-call/upload-headless-website-files.js +107 -15
- package/build/dts/code-mode/attachments.d.ts +9 -6
- package/build/dts/code-mode/attachments.js +23 -16
- package/build/dts/code-mode/index.js +2 -2
- package/build/dts/config/default-config.d.ts +1 -1
- package/build/dts/config/default-config.js +17 -16
- package/build/dts/docs/docs.js +64 -1
- package/build/dts/docs/guessed-url.d.ts +41 -0
- package/build/dts/docs/guessed-url.js +185 -0
- package/build/dts/infra/redact-personal-data.d.ts +1 -1
- package/build/dts/infra/redact-personal-data.js +72 -3
- package/build/dts/site-widget-tools/constants.d.ts +3 -3
- package/build/dts/site-widget-tools/constants.js +3 -3
- package/build/dts/site-widget-tools/pull-site-creation-job-tool/index.js +1 -1
- package/build/dts/site-widget-tools/search-site-templates-tool/index.js +10 -1
- package/build/dts/site-widget-tools/site-builder-tool/index.js +14 -2
- package/build/dts/site-widget-tools/suggested-domain-tool/index.js +20 -9
- package/build/dts/tool-utils.d.ts +8 -1
- package/build/dts/tool-utils.js +26 -2
- package/build/dts/toolkit.js +7 -1
- package/build/esm/index.js +793 -413
- package/build/esm/toolkit.js +405 -46
- package/package.json +2 -2
package/build/esm/toolkit.js
CHANGED
|
@@ -12018,6 +12018,140 @@ async function fetchPlatformizedMenu(menuUrl) {
|
|
|
12018
12018
|
return data.content;
|
|
12019
12019
|
}
|
|
12020
12020
|
|
|
12021
|
+
// src/docs/guessed-url.ts
|
|
12022
|
+
var SKILLS_BASE = "https://www.wix.com/skills/";
|
|
12023
|
+
var registryUrl = (url) => normalizeSkillsUrl(url).replace(
|
|
12024
|
+
/^https:\/\/(?:dev\.wix\.com(?:\/docs)?|www\.wix\.com\/velo\/reference)\/skills\//,
|
|
12025
|
+
SKILLS_BASE
|
|
12026
|
+
);
|
|
12027
|
+
var DOCS_BASE = "https://dev.wix.com/docs/";
|
|
12028
|
+
var FILLER = /* @__PURE__ */ new Set([
|
|
12029
|
+
"",
|
|
12030
|
+
"md",
|
|
12031
|
+
"a",
|
|
12032
|
+
"an",
|
|
12033
|
+
"the",
|
|
12034
|
+
"and",
|
|
12035
|
+
"or",
|
|
12036
|
+
"of",
|
|
12037
|
+
"to",
|
|
12038
|
+
"wix",
|
|
12039
|
+
"s"
|
|
12040
|
+
]);
|
|
12041
|
+
var slugWords = (slug) => new Set(
|
|
12042
|
+
slug.toLowerCase().replace(/\.md$/, "").split(/[^a-z0-9]+/).filter((w) => !FILLER.has(w)).map((w) => w.length > 3 && w.endsWith("s") ? w.slice(0, -1) : w)
|
|
12043
|
+
);
|
|
12044
|
+
function slugSimilarity(a, b) {
|
|
12045
|
+
const x = slugWords(a);
|
|
12046
|
+
const y = slugWords(b);
|
|
12047
|
+
if (x.size === 0 || y.size === 0) return 0;
|
|
12048
|
+
let shared = 0;
|
|
12049
|
+
for (const w of x) if (y.has(w)) shared++;
|
|
12050
|
+
return shared / (x.size + y.size - shared);
|
|
12051
|
+
}
|
|
12052
|
+
var lastSegment = (path) => path.slice(path.lastIndexOf("/") + 1);
|
|
12053
|
+
var folderOf = (path) => path.includes("/") ? path.slice(0, path.lastIndexOf("/")) : "";
|
|
12054
|
+
var SKILL_FILE_MATCH = 0.5;
|
|
12055
|
+
function closestSkillFile(url, manifest, manifestUrl) {
|
|
12056
|
+
const normalized = registryUrl(url).replace(/[?#].*$/, "");
|
|
12057
|
+
if (!normalized.startsWith(SKILLS_BASE)) return null;
|
|
12058
|
+
const [, ...rest] = normalized.slice(SKILLS_BASE.length).split("/");
|
|
12059
|
+
const asked = rest.join("/");
|
|
12060
|
+
const paths = (manifest.files ?? []).map((f) => f.path);
|
|
12061
|
+
if (!asked || paths.length === 0) return null;
|
|
12062
|
+
const base = manifest.base ?? manifestUrl.replace(/\.manifest\.json$/, "/");
|
|
12063
|
+
const name = lastSegment(asked);
|
|
12064
|
+
const folder = folderOf(asked);
|
|
12065
|
+
const sameName = paths.filter((p) => lastSegment(p) === name);
|
|
12066
|
+
if (sameName.length === 1) {
|
|
12067
|
+
return { kind: "file", path: sameName[0], url: base + sameName[0] };
|
|
12068
|
+
}
|
|
12069
|
+
const ranked = paths.filter((p) => folderOf(p) === folder).map((p) => ({ p, score: slugSimilarity(lastSegment(p), name) })).sort((a, b) => b.score - a.score);
|
|
12070
|
+
const [best, next] = ranked;
|
|
12071
|
+
if (best && best.score >= SKILL_FILE_MATCH && best.score > (next?.score ?? 0)) {
|
|
12072
|
+
return { kind: "file", path: best.p, url: base + best.p };
|
|
12073
|
+
}
|
|
12074
|
+
if (ranked.length > 0) {
|
|
12075
|
+
return {
|
|
12076
|
+
kind: "folder",
|
|
12077
|
+
folder,
|
|
12078
|
+
files: ranked.map((r) => base + r.p),
|
|
12079
|
+
manifestUrl
|
|
12080
|
+
};
|
|
12081
|
+
}
|
|
12082
|
+
const top = [...new Set(paths.map((p) => p.split("/")[0]))].sort();
|
|
12083
|
+
return {
|
|
12084
|
+
kind: "folder",
|
|
12085
|
+
folder: "",
|
|
12086
|
+
files: top.map((t) => base + t),
|
|
12087
|
+
manifestUrl
|
|
12088
|
+
};
|
|
12089
|
+
}
|
|
12090
|
+
function skillManifestUrl(url) {
|
|
12091
|
+
const normalized = registryUrl(url);
|
|
12092
|
+
if (!normalized.startsWith(SKILLS_BASE)) return null;
|
|
12093
|
+
const skill = normalized.slice(SKILLS_BASE.length).split(/[/?#]/)[0];
|
|
12094
|
+
return skill ? `${SKILLS_BASE}${skill}.manifest.json` : null;
|
|
12095
|
+
}
|
|
12096
|
+
var MENU_WALK_UP_LEVELS = 3;
|
|
12097
|
+
var MIN_MENU_SEGMENTS = 3;
|
|
12098
|
+
function parentMenuUrls(url) {
|
|
12099
|
+
const clean = url.replace(/[?#].*$/, "").replace(/\/+$/, "");
|
|
12100
|
+
if (!clean.startsWith(DOCS_BASE)) return [];
|
|
12101
|
+
const segments = clean.slice(DOCS_BASE.length).split("/");
|
|
12102
|
+
const out = [];
|
|
12103
|
+
for (let up = 1; up <= MENU_WALK_UP_LEVELS; up++) {
|
|
12104
|
+
const kept = segments.length - up;
|
|
12105
|
+
if (kept < MIN_MENU_SEGMENTS) break;
|
|
12106
|
+
out.push(DOCS_BASE + segments.slice(0, kept).join("/"));
|
|
12107
|
+
}
|
|
12108
|
+
return out;
|
|
12109
|
+
}
|
|
12110
|
+
function closestMenuLinks(menuText, menuUrl, askedUrl, limit = 3) {
|
|
12111
|
+
const asked = lastSegment(
|
|
12112
|
+
askedUrl.replace(/[?#].*$/, "").replace(/\/+$/, "")
|
|
12113
|
+
);
|
|
12114
|
+
const under = `${menuUrl.replace(/\/+$/, "")}/`;
|
|
12115
|
+
const links = [
|
|
12116
|
+
...new Set(
|
|
12117
|
+
menuText.match(/https:\/\/dev\.wix\.com\/docs\/[^\s)\]>"'`]+/g) ?? []
|
|
12118
|
+
)
|
|
12119
|
+
].filter((l) => l.startsWith(under));
|
|
12120
|
+
return links.map((l) => ({
|
|
12121
|
+
l,
|
|
12122
|
+
score: slugSimilarity(lastSegment(l.replace(/\.md$/, "")), asked)
|
|
12123
|
+
})).filter((r) => r.score > 0).sort((a, b) => b.score - a.score).slice(0, limit).map((r) => r.l);
|
|
12124
|
+
}
|
|
12125
|
+
var SMALL_MENU = 3;
|
|
12126
|
+
var MENU_ENTRY_MATCH = 0.75;
|
|
12127
|
+
function subMenusToRead(menuText, menuUrl, askedUrl, limit = 2) {
|
|
12128
|
+
const asked = lastSegment(
|
|
12129
|
+
askedUrl.replace(/[?#].*$/, "").replace(/\/+$/, "")
|
|
12130
|
+
);
|
|
12131
|
+
const under = `${menuUrl.replace(/\/+$/, "")}/`;
|
|
12132
|
+
const methods = new Set(
|
|
12133
|
+
[
|
|
12134
|
+
...menuText.matchAll(
|
|
12135
|
+
/\]\((https:\/\/dev\.wix\.com\/docs\/[^)\s]+)\)\s*_\((?:GET|POST|PUT|PATCH|DELETE)\)_/g
|
|
12136
|
+
)
|
|
12137
|
+
].map((m) => m[1])
|
|
12138
|
+
);
|
|
12139
|
+
const entries = [
|
|
12140
|
+
...new Set(
|
|
12141
|
+
menuText.match(/https:\/\/dev\.wix\.com\/docs\/[^\s)\]>"'`]+/g) ?? []
|
|
12142
|
+
)
|
|
12143
|
+
].filter(
|
|
12144
|
+
(l) => l.startsWith(under) && !l.slice(under.length).replace(/\.md$/, "").includes("/") && !methods.has(l)
|
|
12145
|
+
);
|
|
12146
|
+
const scored = entries.map((l) => ({
|
|
12147
|
+
l,
|
|
12148
|
+
score: slugSimilarity(lastSegment(l.replace(/\.md$/, "")), asked)
|
|
12149
|
+
})).sort((a, b) => b.score - a.score);
|
|
12150
|
+
if (scored.some((s) => s.score >= MENU_ENTRY_MATCH)) return [];
|
|
12151
|
+
if (entries.length <= SMALL_MENU) return entries;
|
|
12152
|
+
return scored.filter((s) => s.score > 0).slice(0, limit).map((s) => s.l);
|
|
12153
|
+
}
|
|
12154
|
+
|
|
12021
12155
|
// ../../node_modules/dedent/dist/dedent.mjs
|
|
12022
12156
|
function ownKeys(object, enumerableOnly) {
|
|
12023
12157
|
var keys = Object.keys(object);
|
|
@@ -12222,7 +12356,8 @@ var DISCOVERY_FLOWS = [
|
|
|
12222
12356
|
function directRouteLines(includeReadme, includeImageUploadGuidance) {
|
|
12223
12357
|
return [
|
|
12224
12358
|
...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.
|
|
12359
|
+
`- **Creating a new site.** Skip the docs too. A site is written in code and uploaded: StartDeployWebsite returns the upload guide; StartHeadlessBuild returns the headless guide, for a site that needs Wix business features (${WIX_BUSINESS_FEATURES}). Call one right away, without asking the user anything first: StartHeadlessBuild when the site needs Wix business features, otherwise StartDeployWebsite. WixHarmonyEditorBuild or SearchSiteTemplates only when the user asked for Wix's editor, the Wix Harmony Editor, a template, Wix Studio or the Classic Editor. Never create a site through ManageWixSite, CallWixSiteAPI or ExecuteWixAPI.`,
|
|
12360
|
+
"- **Suggesting domain names**: call GetSuggestedDomains directly, with the business or site name as the query."
|
|
12226
12361
|
] : [],
|
|
12227
12362
|
"- **Listing, showing or finding their Wix sites**: call ListWixSites directly.",
|
|
12228
12363
|
...includeImageUploadGuidance ? [
|
|
@@ -12296,7 +12431,7 @@ var toWidget = (text) => Object.values(SITE_CREATION_COPY).reduce(
|
|
|
12296
12431
|
(acc, { nonWidget, widget }) => acc.replaceAll(nonWidget, widget),
|
|
12297
12432
|
text
|
|
12298
12433
|
);
|
|
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";
|
|
12434
|
+
var WIX_API_DATA = "products, inventory, carts, checkouts, orders, bookings, services, staff, time slots, rentals, restaurant menus, online orders, reservations, events, tickets, RSVPs, blog posts, CMS collections and items, pricing plans, subscriptions, portfolios, gift cards, coupons, donations, forms, form submissions, contacts, members, loyalty points, groups, reviews, inbox messages, CRM tasks, automations, email campaigns, payments, invoices, SEO, redirects, translations, locations, analytics, media files, site properties, installed apps, and the account's sites and domains (searching whether a domain is available, buying one, and connecting one bought elsewhere)";
|
|
12300
12435
|
var EXECUTE_WIX_API_INTRO = dedent_default`
|
|
12301
12436
|
Reads and changes the data of a Wix site or account: query, list, count, create, update and delete it with one JavaScript function over the Wix REST API.
|
|
12302
12437
|
One function holds the whole task: chained calls, pagination, loops, branching, and the reads that feed its writes.
|
|
@@ -12612,8 +12747,7 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true,
|
|
|
12612
12747
|
${reminder}
|
|
12613
12748
|
`,
|
|
12614
12749
|
ManageWixSite: dedent_default`
|
|
12615
|
-
|
|
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\`.
|
|
12750
|
+
Calls an account-level Wix API: one that acts on the account itself (its sites list, domains, account settings) or on sites as objects in it (create, rename, duplicate, delete a site). It sends no site, so an API that reads or writes inside a site (its products, data, bookings, members, pages, publishing) fails here: call that through ExecuteWixAPI with the site's \`siteId\`.
|
|
12617
12751
|
For POST/PATCH/PUT requests, pass the request body as a JSON object or array in the "body" parameter with all the required fields and values as described in the API schema, code examples, or docs you retrieved (e.g. body: {"name": "value", "nested": {"key": "value"}} or body: [{"key": "value"}]).
|
|
12618
12752
|
The API endpoint url param MUST ALWAYS be taken from the conversation context.
|
|
12619
12753
|
By conversation context we mean the endpoint url was given in the user prompt or got into the conversation context ${includeReadme ? 'by the "WixREADME" tool or ' : ""}by the "SearchWixRESTDocumentation" tool or by the "BrowseWixRESTDocsMenu" tool or by the "ReadFullDocsArticle" tool.
|
|
@@ -12683,7 +12817,7 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true,
|
|
|
12683
12817
|
${reminder}
|
|
12684
12818
|
`,
|
|
12685
12819
|
WixSiteCreation: dedent_default`
|
|
12686
|
-
|
|
12820
|
+
Returns which Wix tool creates a new site, and creates nothing itself. A new site is written in code and uploaded: StartDeployWebsite returns the upload guide, and StartHeadlessBuild returns the guide for a site that needs Wix business features. Use WixHarmonyEditorBuild or SearchSiteTemplates only when the user asked for Wix's editor, a template, Wix Studio or the Classic Editor.
|
|
12687
12821
|
`,
|
|
12688
12822
|
"import-claude-design-from-url": dedent_default`
|
|
12689
12823
|
Import a design created in Claude's design tool into Wix. Only for the signed \`claudeusercontent.com\` bundle URL Claude Design's "Send to Wix" produces (a self-contained HTML bundle with all images, fonts, and styles inlined). Creates a live Wix-hosted site and returns its URL.
|
|
@@ -13125,16 +13259,16 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true,
|
|
|
13125
13259
|
`,
|
|
13126
13260
|
ExecuteWixAPI: executeWixApiDescription,
|
|
13127
13261
|
// Named in SERVER_INSTRUCTIONS: when this tool's name or what it is for changes, update them to match.
|
|
13128
|
-
|
|
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.
|
|
13262
|
+
WixHarmonyEditorBuild: dedent_default`
|
|
13263
|
+
A secondary site-creation tool, only for a user who asked for the Wix Harmony Editor or for Wix to design the site in Wix's editor. A request for a site on Wix, or for Wix to build or make one, is neither. Don't call it for any other site request, not even to check with the flag false. A new site is otherwise written in code and uploaded, with StartDeployWebsite, or StartHeadlessBuild when it needs Wix business features.
|
|
13130
13264
|
Builds a site with the Wix Harmony Editor: Wix designs a first version from the user's description, and the user keeps designing it in Wix's editor. Shows the build's progress and a preview.
|
|
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
|
|
13265
|
+
Call it when the user asked for the Wix Harmony Editor, or for Wix to design the site in Wix's editor, in their own words or as their answer to your question, and quote those words in \`editorRequest\`. ${EDITOR_TAIL}
|
|
13132
13266
|
`,
|
|
13133
13267
|
// Named in SERVER_INSTRUCTIONS: when this tool's name or what it is for changes, update them to match.
|
|
13134
13268
|
SearchSiteTemplates: dedent_default`
|
|
13135
13269
|
A secondary site-creation tool, only for a user who asked for a template, Wix Studio or the Classic Editor. Don't call it for any other site request, not even to check with the flag false. A new site is otherwise written in code and uploaded, with StartDeployWebsite, or StartHeadlessBuild when it needs Wix business features.
|
|
13136
13270
|
Shows a gallery of Wix templates, from Harmony, Wix Studio or the Classic Editor, for a site the user will design in Wix's editor. Wix templates come with Wix's business features (a store, online ordering, bookings, forms), so a request for a template comes here even when the site needs them.
|
|
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
|
|
13271
|
+
Call it when the user asked for a template, Wix Studio or the Classic Editor, in their own words or as their answer to your question, and quote those words in \`templateRequest\`. ${EDITOR_TAIL} ${SITE_CREATION_COPY.templatePick.nonWidget}
|
|
13138
13272
|
`,
|
|
13139
13273
|
CreateSiteFromTemplate: dedent_default`
|
|
13140
13274
|
Creates a Wix site from a specific template (by \`metaSiteId\` or \`templateId\`), publishes it when possible, and shows a live preview with links to the editor and the published site.
|
|
@@ -13171,20 +13305,20 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true,
|
|
|
13171
13305
|
UploadHeadlessWebsiteFiles: dedent_default`
|
|
13172
13306
|
The upload step of the StartDeployWebsite and StartHeadlessBuild guides, and nothing else: puts a finished website's files — a static site (index.html plus its CSS/JS/images, a static build, or a zip of the site) or a Wix Astro build — onto an existing Wix Headless site by its metaSiteId. Call it ONLY after one of them has returned its guide in this conversation and the site exists: for a new site, first run the recipe's provision call and pass the metaSiteId it returns; for a site deployed earlier, pass its metaSiteId. It does not create sites. Every upload replaces the whole file set and the URL stays. Returns the live URL and the dashboard link, and points back to the recipe for what comes next.
|
|
13173
13307
|
Pass the files themselves: in ${FILE_PARAM_CLIENTS} as \`attachments\` (any type, a zip is unpacked), elsewhere as the \`files\` text bundle (\`=== FILE: <path> ===\` sections, \`base64\` tag for small binaries). A static site must contain index.html at its root. A Wix Astro build with no file handle, from a shell that reaches the internet, goes in two calls: zip the build, call with \`prepare: true\` and the zip's \`size\` in bytes, PUT the zips it names with the \`curl\` commands it returns (the build, and the project source when the site's repository accepts it), then call again with the \`uploadKey\` it returned; that releases the build. The two calls, literally: {siteId, prepare: true, size} then {siteId, uploadKey, sourceUploadId?, expectedHeadRevisionId?, releaseNotes?}. A sandbox whose network is limited to a few hosts cannot upload to those URLs. A static site does not use the two calls. A Wix Astro build (the project's .wix/ and dist/ after \`astro build\` with the Wix hosting adapter, zipped from the project root) needs no index.html: it is released to the site's Wix hosting instead, server-rendered pages, the same site and URL. The project's source is stored with the release when the upload holds the project and the site's repository accepts it.
|
|
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 (
|
|
13308
|
+
NOT a site-creation tool: a request to create, build, design or generate a site — with or without a description — never comes here first. Unless the user asked for Wix's editor or a template (WixHarmonyEditorBuild, SearchSiteTemplates), call StartDeployWebsite right away, without asking the user anything: it also takes files the user brought, uploaded first even when the site needs business features. StartHeadlessBuild instead for a new site with no files that needs Wix business features. Also NOT for media uploads to an existing site (UploadImageToWixSite) or any other API call (ExecuteWixAPI).
|
|
13175
13309
|
`,
|
|
13176
13310
|
// Named in SERVER_INSTRUCTIONS: when this tool's name or what it is for changes, update them to match.
|
|
13177
13311
|
StartDeployWebsite: dedent_default`
|
|
13178
13312
|
Creates a website on Wix and puts it live, or replaces the files of one deployed earlier. You write the site's code, or take the files the user brought (HTML/CSS/JS, a static build, a zip, another builder's export), and this returns the guide to create the site and upload them. Updates keep the same site and URL. Works with or without a Wix login (anonymous deploy with a save link), through the Wix MCP alone or from a shell. Call it right away, without asking the user anything first.
|
|
13179
13313
|
When a new site needs Wix business features (${WIX_BUSINESS_FEATURES}), call StartHeadlessBuild instead. When the user brought files and the site needs those features, upload the files here first; the upload's result then points to StartHeadlessBuild to connect them.
|
|
13180
13314
|
Returns the guide; follow it and do the work yourself. Upload with UploadHeadlessWebsiteFiles once the site exists; the drop page is only the fallback.
|
|
13181
|
-
NOT for: a user who asked for Wix's editor, the Wix Harmony Editor, a template, Wix Studio or the Classic Editor (
|
|
13315
|
+
NOT for: a user who asked for Wix's editor, the Wix Harmony Editor, a template, Wix Studio or the Classic Editor (WixHarmonyEditorBuild, SearchSiteTemplates); a Claude Design bundle URL (import-claude-design-from-url); migrating a live site by URL or CSV exports; adding HTML or code into an existing Wix editor site; uploading images or documents to a site's media (UploadImageToWixSite).
|
|
13182
13316
|
`,
|
|
13183
13317
|
// Named in SERVER_INSTRUCTIONS: when this tool's name or what it is for changes, update them to match.
|
|
13184
13318
|
StartHeadlessBuild: dedent_default`
|
|
13185
13319
|
Returns Wix's headless guide, for a site that needs Wix business features (${WIX_BUSINESS_FEATURES}), run by Wix as a Wix Headless site. You write the site; the guide creates it, connects the features, and uploads it. Also to add those features to an existing Wix Headless site (one deployed from files is one), from a project download link, or to move a Wix site to code.
|
|
13186
13320
|
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 (
|
|
13321
|
+
NOT for: a site that needs no Wix business feature, or files the user brought that are not live yet (StartDeployWebsite: upload them first, then come back here for the features); a user who asked for Wix's editor, the Wix Harmony Editor, a template, Wix Studio or the Classic Editor, even when the site needs business features (WixHarmonyEditorBuild, SearchSiteTemplates); a Claude Design URL (import-claude-design-from-url).
|
|
13188
13322
|
`
|
|
13189
13323
|
};
|
|
13190
13324
|
descriptions.SupportAndFeedback = descriptions.ReportIssueToWix;
|
|
@@ -13412,7 +13546,7 @@ var defaultConfig = {
|
|
|
13412
13546
|
var widgetSiteCreationConfig = {
|
|
13413
13547
|
toolDescriptions: Object.fromEntries(
|
|
13414
13548
|
[
|
|
13415
|
-
"
|
|
13549
|
+
"WixHarmonyEditorBuild",
|
|
13416
13550
|
"WixSiteCreation",
|
|
13417
13551
|
"SearchSiteTemplates",
|
|
13418
13552
|
"CreateSiteFromTemplate"
|
|
@@ -14835,9 +14969,16 @@ function shouldTryMenuFallbackForDocsRead(error, articleUrl) {
|
|
|
14835
14969
|
if (!message.includes("404") || !isMenuFallbackUrl) {
|
|
14836
14970
|
return false;
|
|
14837
14971
|
}
|
|
14838
|
-
return !
|
|
14839
|
-
|
|
14840
|
-
|
|
14972
|
+
return !isKnownArticleError(error);
|
|
14973
|
+
}
|
|
14974
|
+
function isKnownArticleError(error) {
|
|
14975
|
+
const message = getErrorMessage(error);
|
|
14976
|
+
return [
|
|
14977
|
+
"This resource is deprecated",
|
|
14978
|
+
"This method is deprecated",
|
|
14979
|
+
"This article is deprecated",
|
|
14980
|
+
"No schema found"
|
|
14981
|
+
].some((knownArticleError) => message.includes(knownArticleError));
|
|
14841
14982
|
}
|
|
14842
14983
|
function formatDocsReadError(toolAction, error) {
|
|
14843
14984
|
return `Could not fetch ${toolAction}: "${getErrorMessage(error)}".`;
|
|
@@ -14924,6 +15065,58 @@ var addDocsTools = (server, allowedTools = [
|
|
|
14924
15065
|
}
|
|
14925
15066
|
return menuContent.length > MENU_CONTENT_THRESHOLD ? summarizeLargeMenu(menuContent, url) : normalizeMenuContent(menuContent);
|
|
14926
15067
|
};
|
|
15068
|
+
const nearestToMissingPage = async (httpClient, url, isSkillsPage) => {
|
|
15069
|
+
try {
|
|
15070
|
+
if (isSkillsPage) {
|
|
15071
|
+
const manifestUrl = skillManifestUrl(url);
|
|
15072
|
+
if (!manifestUrl) return null;
|
|
15073
|
+
const manifest = JSON.parse(
|
|
15074
|
+
await fetchArticleContent(httpClient, manifestUrl, "article")
|
|
15075
|
+
);
|
|
15076
|
+
const guess = closestSkillFile(url, manifest, manifestUrl);
|
|
15077
|
+
if (!guess) return null;
|
|
15078
|
+
if (guess.kind === "file") {
|
|
15079
|
+
const text = await fetchArticleContent(
|
|
15080
|
+
httpClient,
|
|
15081
|
+
guess.url,
|
|
15082
|
+
"article"
|
|
15083
|
+
);
|
|
15084
|
+
return `Note: "${url}" does not exist. Found a similar file in the skill, ${guess.url}, and read it:
|
|
15085
|
+
|
|
15086
|
+
${text}`;
|
|
15087
|
+
}
|
|
15088
|
+
return `Note: "${url}" does not exist, and no file in ${guess.folder ? `its folder (${guess.folder}/)` : "the skill"} has a name close to it. ${guess.folder ? "The files in that folder" : "The skill's top-level folders and files"}, to read the one you need (every file: ${guess.manifestUrl}):
|
|
15089
|
+
|
|
15090
|
+
${guess.files.map((f) => `- ${f}`).join("\n")}`;
|
|
15091
|
+
}
|
|
15092
|
+
for (const menuUrl of parentMenuUrls(url)) {
|
|
15093
|
+
const text = await fetchMenuTextForUrl(httpClient, menuUrl);
|
|
15094
|
+
if (!text) continue;
|
|
15095
|
+
const subMenus = (await Promise.all(
|
|
15096
|
+
subMenusToRead(text, menuUrl, url).map(async (sub) => ({
|
|
15097
|
+
sub,
|
|
15098
|
+
text: await fetchMenuTextForUrl(httpClient, sub).catch(() => null)
|
|
15099
|
+
}))
|
|
15100
|
+
)).filter((m) => !!m.text);
|
|
15101
|
+
const closest = closestMenuLinks(
|
|
15102
|
+
[text, ...subMenus.map((m) => m.text)].join("\n"),
|
|
15103
|
+
menuUrl,
|
|
15104
|
+
url
|
|
15105
|
+
);
|
|
15106
|
+
return [
|
|
15107
|
+
`Note: "${url}" does not exist. Showing the nearest documentation menu above it, ${menuUrl}; read the page you need from its links.`,
|
|
15108
|
+
...closest.length ? [
|
|
15109
|
+
`Closest to what you asked for:
|
|
15110
|
+
${closest.map((l) => `- ${l}`).join("\n")}`
|
|
15111
|
+
] : [],
|
|
15112
|
+
text
|
|
15113
|
+
].join("\n\n");
|
|
15114
|
+
}
|
|
15115
|
+
} catch (error) {
|
|
15116
|
+
logger.error(`[docs] no nearest page for ${url}: ${error}`);
|
|
15117
|
+
}
|
|
15118
|
+
return null;
|
|
15119
|
+
};
|
|
14927
15120
|
if (startDeployWebsiteEnabled) {
|
|
14928
15121
|
addStartDeployWebsiteTool(server, disableTools || [], toolDescriptions, {
|
|
14929
15122
|
skillsPortalUrl,
|
|
@@ -15660,6 +15853,16 @@ ${text}`
|
|
|
15660
15853
|
} catch {
|
|
15661
15854
|
}
|
|
15662
15855
|
}
|
|
15856
|
+
if (getErrorMessage(error).includes("404") && !isKnownArticleError(error)) {
|
|
15857
|
+
const nearest = await nearestToMissingPage(
|
|
15858
|
+
httpClient,
|
|
15859
|
+
articleUrl,
|
|
15860
|
+
isSkillsPage
|
|
15861
|
+
);
|
|
15862
|
+
if (nearest) {
|
|
15863
|
+
return { content: [{ type: "text", text: nearest }] };
|
|
15864
|
+
}
|
|
15865
|
+
}
|
|
15663
15866
|
return {
|
|
15664
15867
|
isError: true,
|
|
15665
15868
|
content: [
|
|
@@ -15850,6 +16053,16 @@ var getRetryHint = (errorDetails) => {
|
|
|
15850
16053
|
}
|
|
15851
16054
|
return /"operator":"\{/.test(errorDetails) ? SINGLE_OPERATOR_PER_FIELD_HINT : FIELD_CAPABILITIES_HINT;
|
|
15852
16055
|
};
|
|
16056
|
+
var NO_SITE_IN_REQUEST = /META_SITE_NOT_FOUND|MetaSite(?:Id)? (?:context )?(?:is missing|was not found|not found)|NO_METASITE_IN_CALL_CONTEXT|MISSING_METASITE_ID|MISSING_REQUEST_SITE_CONTEXT|metasiteId from the request context/i;
|
|
16057
|
+
function missingSiteContext(status, responseData) {
|
|
16058
|
+
const details = typeof responseData === "string" ? responseData : JSON.stringify(responseData ?? "");
|
|
16059
|
+
if (NO_SITE_IN_REQUEST.test(details)) return "certain";
|
|
16060
|
+
const parsed = typeof responseData === "string" ? safeParseJSON(responseData) : responseData;
|
|
16061
|
+
if (status === 403 && parsed && typeof parsed === "object" && !parsed.message) {
|
|
16062
|
+
return "likely";
|
|
16063
|
+
}
|
|
16064
|
+
return null;
|
|
16065
|
+
}
|
|
15853
16066
|
var safeParseJSON = (text) => {
|
|
15854
16067
|
try {
|
|
15855
16068
|
return JSON.parse(text);
|
|
@@ -15861,7 +16074,8 @@ var buildErrorMessage = ({
|
|
|
15861
16074
|
status,
|
|
15862
16075
|
statusText,
|
|
15863
16076
|
requestId,
|
|
15864
|
-
responseData
|
|
16077
|
+
responseData,
|
|
16078
|
+
retryHint
|
|
15865
16079
|
}) => {
|
|
15866
16080
|
const responseDataObject = typeof responseData === "string" ? safeParseJSON(responseData) : responseData;
|
|
15867
16081
|
const errorDetails = typeof responseDataObject === "object" ? JSON.stringify(responseDataObject) : responseDataObject;
|
|
@@ -15873,7 +16087,7 @@ var buildErrorMessage = ({
|
|
|
15873
16087
|
].filter((str) => !!str).join("\n");
|
|
15874
16088
|
return `${baseMessage}
|
|
15875
16089
|
|
|
15876
|
-
${getRetryHint(String(errorDetails))}`;
|
|
16090
|
+
${retryHint ?? getRetryHint(String(errorDetails))}`;
|
|
15877
16091
|
};
|
|
15878
16092
|
var handleWixAPIResponse = async (response) => {
|
|
15879
16093
|
const requestId = response.headers?.["x-wix-request-id"];
|
|
@@ -16285,6 +16499,9 @@ If you can't deliver the image any of these ways, call ${imagePickerToolName} so
|
|
|
16285
16499
|
var MAX_ALLOWED_RESPONSE_CHARS = 2e6;
|
|
16286
16500
|
|
|
16287
16501
|
// src/api-call/index.ts
|
|
16502
|
+
var siteToolCall = (executeServed) => executeServed ? "through ExecuteWixAPI with that site's `siteId` (`scope: \"site\"`), loading ExecuteWixAPI through your client's tool search if it isn't loaded" : "through CallWixSiteAPI with that site's `siteId`";
|
|
16503
|
+
var manageNoSiteHint = (executeServed) => `This is a site-level API: it acts inside a site, and ManageWixSite sends no site, so the same call fails here every time. Call the same URL, method and body ${siteToolCall(executeServed)}.`;
|
|
16504
|
+
var manageEmpty403Hint = (executeServed) => `If this endpoint acts inside a site (its products, data, bookings, members, pages, publishing), it is site-level, and ManageWixSite sends no site: call the same URL, method and body ${siteToolCall(executeServed)}. If it acts on the account or on sites as objects in it, the account lacks the permission; don't retry.`;
|
|
16288
16505
|
var DEFAULT_MAX_RESPONSE_SIZE = 5e4;
|
|
16289
16506
|
function extractReturnedRows(responseData) {
|
|
16290
16507
|
if (responseData === null || typeof responseData !== "object") {
|
|
@@ -16608,6 +16825,18 @@ function addApiCallTool(server, strategy, allowedTools = ["CallWixSiteAPI", "Lis
|
|
|
16608
16825
|
if (!response) {
|
|
16609
16826
|
throw new Error(`Failed to call Wix Account API: ${error}`);
|
|
16610
16827
|
}
|
|
16828
|
+
const noSite = missingSiteContext(response.status, response.data);
|
|
16829
|
+
if (noSite) {
|
|
16830
|
+
throw new Error(
|
|
16831
|
+
buildErrorMessage({
|
|
16832
|
+
status: response.status,
|
|
16833
|
+
statusText: response.statusText,
|
|
16834
|
+
requestId: response.headers?.["x-wix-request-id"] ?? null,
|
|
16835
|
+
responseData: response.data,
|
|
16836
|
+
retryHint: (noSite === "certain" ? manageNoSiteHint : manageEmpty403Hint)(!!options?.executeWixAPIServed)
|
|
16837
|
+
})
|
|
16838
|
+
);
|
|
16839
|
+
}
|
|
16611
16840
|
return handleWixAPIResponse(response);
|
|
16612
16841
|
}
|
|
16613
16842
|
}
|
|
@@ -17434,15 +17663,18 @@ var OAI_BLOB_ACCOUNTS = new Set(
|
|
|
17434
17663
|
"westus3"
|
|
17435
17664
|
].map((region) => `oaisdmntpr${region}.blob.core.windows.net`)
|
|
17436
17665
|
);
|
|
17437
|
-
|
|
17666
|
+
var WIX_AI_ASSISTANT_ATTACHMENT_HOSTS = /* @__PURE__ */ new Set([
|
|
17667
|
+
"wixmp-a85a29aaf55baa3e5171a82d.wixmp.com"
|
|
17668
|
+
]);
|
|
17669
|
+
function isAllowedAttachmentHost(hostname, extraHosts) {
|
|
17438
17670
|
const h = hostname.toLowerCase();
|
|
17439
|
-
return OAI_USERCONTENT_HOST.test(h) || OAI_BLOB_ACCOUNTS.has(h);
|
|
17671
|
+
return OAI_USERCONTENT_HOST.test(h) || OAI_BLOB_ACCOUNTS.has(h) || !!extraHosts?.has(h);
|
|
17440
17672
|
}
|
|
17441
|
-
function checkAttachmentUrl(url) {
|
|
17673
|
+
function checkAttachmentUrl(url, extraHosts) {
|
|
17442
17674
|
if (url.protocol !== "https:") return "is not an https URL";
|
|
17443
17675
|
if (url.port) return "uses a non-default port";
|
|
17444
|
-
if (!isAllowedAttachmentHost(url.hostname)) {
|
|
17445
|
-
return `host "${url.hostname}" is not a
|
|
17676
|
+
if (!isAllowedAttachmentHost(url.hostname, extraHosts)) {
|
|
17677
|
+
return `host "${url.hostname}" is not a chat attachment host`;
|
|
17446
17678
|
}
|
|
17447
17679
|
return void 0;
|
|
17448
17680
|
}
|
|
@@ -17469,7 +17701,7 @@ function toBase64(bytes) {
|
|
|
17469
17701
|
}
|
|
17470
17702
|
return btoa(binary);
|
|
17471
17703
|
}
|
|
17472
|
-
async function fetchAllowed(start, fetchImpl, label) {
|
|
17704
|
+
async function fetchAllowed(start, fetchImpl, label, extraHosts) {
|
|
17473
17705
|
let url = start;
|
|
17474
17706
|
for (let hop = 0; ; hop++) {
|
|
17475
17707
|
const res = await fetchImpl(url.toString(), { redirect: "manual" });
|
|
@@ -17486,7 +17718,7 @@ async function fetchAllowed(start, fetchImpl, label) {
|
|
|
17486
17718
|
} catch {
|
|
17487
17719
|
throw new AttachmentError(`${label}: redirect to an invalid URL.`);
|
|
17488
17720
|
}
|
|
17489
|
-
const problem = checkAttachmentUrl(next);
|
|
17721
|
+
const problem = checkAttachmentUrl(next, extraHosts);
|
|
17490
17722
|
if (problem) {
|
|
17491
17723
|
throw new AttachmentError(
|
|
17492
17724
|
`${label}: the download redirected to a URL that ${problem}; refused.`
|
|
@@ -17524,7 +17756,7 @@ async function readWithin(res, budget, overBudget) {
|
|
|
17524
17756
|
}
|
|
17525
17757
|
return out;
|
|
17526
17758
|
}
|
|
17527
|
-
async function resolveAttachments(attachments, fetchImpl = fetch) {
|
|
17759
|
+
async function resolveAttachments(attachments, fetchImpl = fetch, extraHosts) {
|
|
17528
17760
|
if (!Array.isArray(attachments) || attachments.length === 0) return [];
|
|
17529
17761
|
const items = [];
|
|
17530
17762
|
attachments.forEach((raw, i) => {
|
|
@@ -17543,10 +17775,10 @@ async function resolveAttachments(attachments, fetchImpl = fetch) {
|
|
|
17543
17775
|
)}). \`attachments\` works only in ChatGPT, which swaps each file for a download URL before the call reaches Wix. Here it arrived as written, and Wix cannot read a path on your machine, so retrying the same value fails the same way. Upload another way. With a shell that reaches the internet, send the file from there (for a site, the StartDeployWebsite recipe's curl drop). Otherwise leave \`attachments\` out and put the files in \`files\`: text as is, and a binary as a \`=== FILE: <path> base64 ===\` entry only when it is a few KB (an icon, a logo), since a larger base64 payload does not survive your own output. A larger image goes in by public URL or through the user (the Media Manager).`
|
|
17544
17776
|
);
|
|
17545
17777
|
}
|
|
17546
|
-
const problem = checkAttachmentUrl(url);
|
|
17778
|
+
const problem = checkAttachmentUrl(url, extraHosts);
|
|
17547
17779
|
if (problem) {
|
|
17548
17780
|
throw new AttachmentError(
|
|
17549
|
-
`attachments[${i}].download_url ${problem}.
|
|
17781
|
+
`attachments[${i}].download_url ${problem}. \`attachments\` takes only files the chat platform attached and resolved; leave it out. For a file at a URL, import it in \`code\` with \`POST https://www.wixapis.com/site-media/v1/files/import\` and \`{ url }\` (Wix fetches it); for a small file you have as text, use the \`files\` bundle.`
|
|
17550
17782
|
);
|
|
17551
17783
|
}
|
|
17552
17784
|
items.push({ a, url });
|
|
@@ -17558,7 +17790,7 @@ async function resolveAttachments(attachments, fetchImpl = fetch) {
|
|
|
17558
17790
|
const label = `attachments[${i}] (${path})`;
|
|
17559
17791
|
let res;
|
|
17560
17792
|
try {
|
|
17561
|
-
res = await fetchAllowed(url, fetchImpl, label);
|
|
17793
|
+
res = await fetchAllowed(url, fetchImpl, label, extraHosts);
|
|
17562
17794
|
} catch (e) {
|
|
17563
17795
|
if (e instanceof AttachmentError) throw e;
|
|
17564
17796
|
throw new AttachmentError(
|
|
@@ -17988,7 +18220,9 @@ var FREE_TEXT_INFO_PARAMS = [
|
|
|
17988
18220
|
"agentIdentity",
|
|
17989
18221
|
"wixAccess",
|
|
17990
18222
|
"reason",
|
|
17991
|
-
"whyNotExecuteWixAPI"
|
|
18223
|
+
"whyNotExecuteWixAPI",
|
|
18224
|
+
"editorRequest",
|
|
18225
|
+
"templateRequest"
|
|
17992
18226
|
];
|
|
17993
18227
|
var EMAIL = /[A-Z0-9._%+-]+@[A-Z0-9-]+(?:\.[A-Z0-9-]+)*\.[A-Z]{2,}/gi;
|
|
17994
18228
|
var NAMED_SECRET = /\b(passwords?|passwd|pwd|passcodes?|secrets?|tokens?|api[ _-]?keys?|access[ _-]?keys?|credentials?)(\s*[:=]\s*|\s+(?:is|was|to)\s+)(?:"[^"]*"|'[^']*'|`[^`]*`|[^\s"'`,;][^,;\n]*?(?=[.!?]\s|[,;\n]|$))/gi;
|
|
@@ -18036,9 +18270,65 @@ function redactInfoParams(value) {
|
|
|
18036
18270
|
}
|
|
18037
18271
|
return redacted;
|
|
18038
18272
|
}
|
|
18273
|
+
var MAX_LISTED_PATHS_CHARS = 2e3;
|
|
18274
|
+
function summarizeFileBundle(bundle) {
|
|
18275
|
+
let sections;
|
|
18276
|
+
try {
|
|
18277
|
+
sections = parseFileBundle(bundle).map((file) => ({
|
|
18278
|
+
path: file.path,
|
|
18279
|
+
bytes: file.bytes.length
|
|
18280
|
+
}));
|
|
18281
|
+
} catch {
|
|
18282
|
+
sections = [];
|
|
18283
|
+
}
|
|
18284
|
+
if (sections.length === 0) {
|
|
18285
|
+
return `${new TextEncoder().encode(bundle).length} bytes, no file sections`;
|
|
18286
|
+
}
|
|
18287
|
+
return formatFileSummary(sections);
|
|
18288
|
+
}
|
|
18289
|
+
function formatFileSummary(files) {
|
|
18290
|
+
const total = files.reduce((sum, file) => sum + file.bytes, 0);
|
|
18291
|
+
let listed = "";
|
|
18292
|
+
let shown = 0;
|
|
18293
|
+
for (const file of files) {
|
|
18294
|
+
const entry = `${shown ? ", " : ""}${file.path} (${file.bytes})`;
|
|
18295
|
+
if (listed.length + entry.length > MAX_LISTED_PATHS_CHARS) break;
|
|
18296
|
+
listed += entry;
|
|
18297
|
+
shown++;
|
|
18298
|
+
}
|
|
18299
|
+
const more = files.length - shown;
|
|
18300
|
+
return `${files.length} file(s), ${total} bytes: ${listed}${more ? `${shown ? ", " : ""}and ${more} more` : ""}`;
|
|
18301
|
+
}
|
|
18302
|
+
function compactFileParams(value) {
|
|
18303
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
18304
|
+
return value;
|
|
18305
|
+
}
|
|
18306
|
+
const record = value;
|
|
18307
|
+
const files = record.files;
|
|
18308
|
+
if (typeof files === "string") {
|
|
18309
|
+
return { ...record, files: summarizeFileBundle(files) };
|
|
18310
|
+
}
|
|
18311
|
+
if (Array.isArray(files) && files.length > 0 && files.every(
|
|
18312
|
+
(entry) => entry && typeof entry === "object" && typeof entry.content === "string"
|
|
18313
|
+
)) {
|
|
18314
|
+
return {
|
|
18315
|
+
...record,
|
|
18316
|
+
files: formatFileSummary(
|
|
18317
|
+
files.map((entry) => ({
|
|
18318
|
+
path: entry.path ?? "?",
|
|
18319
|
+
bytes: new TextEncoder().encode(entry.content).length
|
|
18320
|
+
}))
|
|
18321
|
+
)
|
|
18322
|
+
};
|
|
18323
|
+
}
|
|
18324
|
+
return value;
|
|
18325
|
+
}
|
|
18326
|
+
function prepareForBi(value) {
|
|
18327
|
+
return compactFileParams(redactInfoParams(value));
|
|
18328
|
+
}
|
|
18039
18329
|
function serializeToolParamsForBi(args) {
|
|
18040
18330
|
return JSON.stringify(
|
|
18041
|
-
Array.isArray(args) ? args.map(
|
|
18331
|
+
Array.isArray(args) ? args.map(prepareForBi) : prepareForBi(args)
|
|
18042
18332
|
);
|
|
18043
18333
|
}
|
|
18044
18334
|
|
|
@@ -18046,6 +18336,8 @@ function serializeToolParamsForBi(args) {
|
|
|
18046
18336
|
var UPLOAD_HEADLESS_WEBSITE_FILES_TOOL_NAME = "UploadHeadlessWebsiteFiles";
|
|
18047
18337
|
var HEADLESS_BUSINESS = "https://www.wixapis.com/headless-business-setup/v1/headless-business";
|
|
18048
18338
|
var MAX_BUNDLE_BYTES = 100 * 1024 * 1024;
|
|
18339
|
+
var MAX_RELEASE_STATIC_FILES = 2500;
|
|
18340
|
+
var MAX_SOURCE_ZIP_BYTES = 64 * 1024 * 1024;
|
|
18049
18341
|
function dropAttribution(clientName) {
|
|
18050
18342
|
const slug = (clientName ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
|
|
18051
18343
|
return `?campaign=mcp&agent=${encodeURIComponent(slug || "wix-mcp")}`;
|
|
@@ -18068,6 +18360,17 @@ var UPLOAD_HEADLESS_WEBSITE_FILES_PARAM_DESCRIPTIONS = {
|
|
|
18068
18360
|
};
|
|
18069
18361
|
var UPLOAD_RECIPE_SKILL_URL = "https://www.wix.com/skills/wix-manage/references/sites/upload-static-site.md";
|
|
18070
18362
|
var DOMAIN_RECIPE_SKILL_URL = "https://www.wix.com/skills/wix-manage/references/domains/domain-search-purchase-and-connect.md";
|
|
18363
|
+
function formatInlineGuide(guide) {
|
|
18364
|
+
if (!guide || !guide.trim()) return "";
|
|
18365
|
+
return [
|
|
18366
|
+
"",
|
|
18367
|
+
"---",
|
|
18368
|
+
"",
|
|
18369
|
+
`Wix's headless guide, the build of a site that needs Wix business features (also at https://www.wix.com/headless/skill.md):`,
|
|
18370
|
+
"",
|
|
18371
|
+
guide.trim()
|
|
18372
|
+
].join("\n");
|
|
18373
|
+
}
|
|
18071
18374
|
function formatDropResult(r) {
|
|
18072
18375
|
const shown = r.files.slice(0, 60);
|
|
18073
18376
|
return [
|
|
@@ -18080,7 +18383,7 @@ function formatDropResult(r) {
|
|
|
18080
18383
|
`Give the user both links, each on its own line and named for what it is: the live site, and the site's dashboard. Then offer a custom domain, bought through Wix or one they already own (${DOMAIN_RECIPE_SKILL_URL}).`,
|
|
18081
18384
|
"",
|
|
18082
18385
|
`- To change this site later: "Change it later" in ${UPLOAD_RECIPE_SKILL_URL}, with this metaSiteId (${r.metaSiteId}). The same site every time, never a new one.`,
|
|
18083
|
-
`- Wix business features (${WIX_BUSINESS_FEATURES}):
|
|
18386
|
+
`- When the site needs Wix business features (${WIX_BUSINESS_FEATURES}), a drop of static pages is not its build: a full Wix Headless site has those features run by Wix and its pages reading from them. That build is Wix's headless guide, https://www.wix.com/headless/skill.md (what StartHeadlessBuild returns; call it with startingPoint "an existing Wix Headless site", this site is one): it installs the solution on this site, seeds its content through the API, and puts up pages that fetch it, uploaded here again. A site with no business features is done.`
|
|
18084
18387
|
].join("\n");
|
|
18085
18388
|
}
|
|
18086
18389
|
function curlPut(zipName, put, fallback) {
|
|
@@ -18108,7 +18411,7 @@ function formatPrepareResult(siteId, urls, size, given = {}) {
|
|
|
18108
18411
|
const lines = [
|
|
18109
18412
|
`Upload URLs ready for site ${siteId}.${expiry} Two steps, then call this tool again.`,
|
|
18110
18413
|
"",
|
|
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:`,
|
|
18414
|
+
`1. ${BUILD_ZIP_CONTENTS} Its client folder (\`dist/client\`) holds at most ${MAX_RELEASE_STATIC_FILES} files (\`find dist/client -type f | wc -l\`); a larger build fails the release. Upload the zip you measured${size ? ` (${size} bytes)` : ""}; the URL takes a body of exactly that length, so a zip changed since needs a new prepare with its size:`,
|
|
18112
18415
|
` ${curlPut("build.zip", urls, zipHeaders)}`
|
|
18113
18416
|
];
|
|
18114
18417
|
if (given.expectedHeadRevisionId)
|
|
@@ -18120,7 +18423,7 @@ function formatPrepareResult(siteId, urls, size, given = {}) {
|
|
|
18120
18423
|
secondCall.expectedHeadRevisionId = s.headRevisionId;
|
|
18121
18424
|
lines.push(
|
|
18122
18425
|
"",
|
|
18123
|
-
`2. ${SOURCE_ZIP_CONTENTS} Upload it:`,
|
|
18426
|
+
`2. ${SOURCE_ZIP_CONTENTS} At most 64 MiB (\`wc -c < source.zip\`): a larger one fails the whole release. Leave large media and generated files out of it; if it is still larger, skip this upload and leave \`sourceUploadId\` out of the second call, and the site is released without its source. Upload it:`,
|
|
18124
18427
|
` ${curlPut("source.zip", s, { "content-type": "application/zip" })}`,
|
|
18125
18428
|
...s.expiresAt && s.expiresAt !== urls.expiresAt ? [` This URL expires at ${s.expiresAt}.`] : [],
|
|
18126
18429
|
...s.headRevisionId ? [
|
|
@@ -18139,6 +18442,13 @@ function formatPrepareResult(siteId, urls, size, given = {}) {
|
|
|
18139
18442
|
"",
|
|
18140
18443
|
`3. Call UploadHeadlessWebsiteFiles again with ${JSON.stringify(secondCall)} (plus \`calledStartDeployWebsite\`, \`agentIdentity\`, \`reason\`). No \`files\` or \`attachments\` on that call.`
|
|
18141
18444
|
);
|
|
18445
|
+
if (secondCall.sourceUploadId) {
|
|
18446
|
+
const withoutSource = { ...secondCall };
|
|
18447
|
+
delete withoutSource.sourceUploadId;
|
|
18448
|
+
lines.push(
|
|
18449
|
+
` If you skipped the source upload in step 2, call with ${JSON.stringify(withoutSource)} instead: a \`sourceUploadId\` whose zip was never uploaded fails the release.`
|
|
18450
|
+
);
|
|
18451
|
+
}
|
|
18142
18452
|
return lines.join("\n");
|
|
18143
18453
|
}
|
|
18144
18454
|
function formatBundleReleaseResult(siteId, r, notes = []) {
|
|
@@ -18156,10 +18466,21 @@ function formatBundleReleaseResult(siteId, r, notes = []) {
|
|
|
18156
18466
|
}
|
|
18157
18467
|
var startOver = (retry) => retry === "prepare" ? "call with `prepare: true` and the zip's `size` again, upload the zips to the new URLs, and release with the new `uploadKey`" : "call this tool again with the same files";
|
|
18158
18468
|
var apiBody = (err) => err.response.data ? ` (${err.response.data.slice(0, 300)})` : "";
|
|
18159
|
-
|
|
18469
|
+
var SITE_ACCESS_PERMISSION = "OAUTH_APP.APP_CREATE";
|
|
18470
|
+
function describeSiteAccessDenied(err, siteId, stagedKey) {
|
|
18471
|
+
if (!(err instanceof WixFetchError) || err.status !== 403 || !err.response.data.includes(SITE_ACCESS_PERMISSION)) {
|
|
18472
|
+
return null;
|
|
18473
|
+
}
|
|
18474
|
+
const site = siteId ? `site ${siteId}` : "this site";
|
|
18475
|
+
const outcome = stagedKey ? `Nothing was released; the build you uploaded is still staged under uploadKey ${stagedKey} and counts against the site's unreleased uploads, so do not call \`prepare\` again and do not send the files again. The same call fails the same way until the login changes; once connected as the owning account, release that staged upload by calling with \`uploadKey: "${stagedKey}"\` before preparing a new one.` : "Nothing was uploaded, and the same call fails the same way, so do not retry and do not stage another upload.";
|
|
18476
|
+
return `The connected Wix login has no rights on ${site}: Wix refused it the \`${SITE_ACCESS_PERMISSION}\` permission there. That means the site belongs to another Wix account, or this login's role on the owning account is too low (a Basic Team Member cannot upload). ${outcome} Ask the user which Wix account owns the site. Reconnecting Wix helps only if they pick that account, as its owner or an admin, at the consent screen; otherwise the owner has to give this login a higher role on the account, or move the site to it.${apiBody(err)}`;
|
|
18477
|
+
}
|
|
18478
|
+
function describeBundleUploadUrlError(err, siteId) {
|
|
18160
18479
|
if (!(err instanceof WixFetchError)) {
|
|
18161
18480
|
return err instanceof Error ? err.message : String(err);
|
|
18162
18481
|
}
|
|
18482
|
+
const denied = describeSiteAccessDenied(err, siteId);
|
|
18483
|
+
if (denied) return denied;
|
|
18163
18484
|
const body = apiBody(err);
|
|
18164
18485
|
switch (err.status) {
|
|
18165
18486
|
case 400:
|
|
@@ -18179,17 +18500,24 @@ function releaseNotesForService(notes) {
|
|
|
18179
18500
|
const chars = [...notes.replace(/\s+/g, " ").trim()];
|
|
18180
18501
|
return chars.length <= RELEASE_NOTES_MAX_CHARS ? chars.join("") : `${chars.slice(0, RELEASE_NOTES_MAX_CHARS - 1).join("").trimEnd()}\u2026`;
|
|
18181
18502
|
}
|
|
18182
|
-
function describeBundleReleaseError(err, uploadKey, retry = "prepare") {
|
|
18503
|
+
function describeBundleReleaseError(err, uploadKey, retry = "prepare", siteId) {
|
|
18183
18504
|
if (!(err instanceof WixFetchError)) {
|
|
18184
18505
|
return err instanceof Error ? err.message : String(err);
|
|
18185
18506
|
}
|
|
18507
|
+
const denied = describeSiteAccessDenied(err, siteId, uploadKey);
|
|
18508
|
+
if (denied) return denied;
|
|
18186
18509
|
const body = apiBody(err);
|
|
18187
18510
|
const code = err.response.data ?? "";
|
|
18511
|
+
const sameCall = `calling with \`uploadKey: "${uploadKey}"\`${retry === "resend" ? " (plus the `expectedHeadRevisionId` and `releaseNotes` you passed, if any; the source is stored with the next release)" : " and the same ids and `releaseNotes`"}`;
|
|
18512
|
+
const afterDrop = "If an earlier call with this `uploadKey` ended in a dropped connection, that release went through: the site is live with this build, so do not upload it again.";
|
|
18513
|
+
if (err.status >= 502 && err.status <= 504 && !code.includes("RELEASE_NOT_HELD") && !code.includes("{")) {
|
|
18514
|
+
return `The connection to Wix dropped while the release ran (HTTP ${err.status}); the release most likely went through. Do not call \`prepare\` again and do not send the files again. Wait two minutes, then check once by ${sameCall}: an answer that nothing is staged for this key, or that the repository moved, means the first release went through and the site is live. Any other answer is about the release itself.${body}`;
|
|
18515
|
+
}
|
|
18188
18516
|
switch (err.status) {
|
|
18189
18517
|
case 404:
|
|
18190
|
-
return `Nothing uploaded for uploadKey ${uploadKey}: the build zip was not PUT to its URL,
|
|
18518
|
+
return `Nothing uploaded for uploadKey ${uploadKey}: the build zip was not PUT to its URL, the URL expired, or a release of it already went through (a staged zip is deleted once released). ${afterDrop} Otherwise, to try again, ${startOver(retry)}.${body}`;
|
|
18191
18519
|
case 409:
|
|
18192
|
-
return `The project's source repository moved since the upload URLs were made; nothing was released. ${retry === "prepare" ? "
|
|
18520
|
+
return `The project's source repository moved since the upload URLs were made; nothing was released by this call. ${afterDrop} Otherwise: ${retry === "prepare" ? "call with `prepare: true` and the zip's `size` again, re-upload the source zip to the new URL, and release with the new `uploadKey`, `sourceUploadId` and `expectedHeadRevisionId`." : "download the project again from the repository, carry your change over, and upload with `expectedHeadRevisionId` set to the revision you downloaded."}${body}`;
|
|
18193
18521
|
case 403:
|
|
18194
18522
|
return `Wix refused to deploy this build onto the site's project; nothing was released, and the same call will be refused again. Do not call \`prepare\` again. Tell the user the release is blocked on Wix's side and give them the request id from the error.${body}`;
|
|
18195
18523
|
case 413:
|
|
@@ -18211,8 +18539,9 @@ function describeBundleReleaseError(err, uploadKey, retry = "prepare") {
|
|
|
18211
18539
|
return `The uploaded zip was not accepted. Zip \`dist/\` and \`.wix/build-metadata.json\` from the project root after \`astro build\` with the Wix hosting adapter, then ${startOver(retry)}. A static site goes in \`files\` or \`attachments\` instead, or is dropped as the StartDeployWebsite recipe shows.${body}`;
|
|
18212
18540
|
default:
|
|
18213
18541
|
if (err.status >= 500) {
|
|
18214
|
-
const what =
|
|
18215
|
-
|
|
18542
|
+
const what = code.includes("RELEASE_NOT_HELD") ? "The release could not hold its upload" : `Wix's release step failed on its side (HTTP ${err.status})`;
|
|
18543
|
+
const limits = retry === "prepare" && !code.includes("RELEASE_NOT_HELD") ? ` A build over Wix's limits fails here with no reason given: more than ${MAX_RELEASE_STATIC_FILES} files in its client folder (\`find dist/client -type f | wc -l\`), or a source zip over 64 MiB (\`wc -c < source.zip\`). If either is over, a retry fails the same way: make the build or the source zip smaller, then call with \`prepare: true\` again.` : "";
|
|
18544
|
+
return `${what}; nothing was released.${limits} The staged upload is kept under uploadKey ${uploadKey}, so do not call \`prepare\` again${limits ? " for the same zips" : ""} and do not send the files again: every upload staged and not released counts against the site, which refuses new ones after a few. Retry once, after a minute, by ${sameCall}. If that fails too, stop and tell the user the release is blocked on Wix's side, with the request id from the error.${body}`;
|
|
18216
18545
|
}
|
|
18217
18546
|
return err.message;
|
|
18218
18547
|
}
|
|
@@ -18332,6 +18661,18 @@ function addUploadHeadlessWebsiteFilesTool(server, options) {
|
|
|
18332
18661
|
const { authStrategy, toolDescriptions } = options;
|
|
18333
18662
|
const clientName = () => options.clientName ?? server.clientName?.() ?? server.server?.getClientVersion?.()?.name;
|
|
18334
18663
|
const fetchImpl = options.fetchImpl ?? ((u, i) => fetch(u, i));
|
|
18664
|
+
const guideFetch = options.guideFetch ?? (async (httpClient) => {
|
|
18665
|
+
if (!httpClient) return void 0;
|
|
18666
|
+
return fetchArticleContent(
|
|
18667
|
+
httpClient,
|
|
18668
|
+
HEADLESS_GUIDE_URL,
|
|
18669
|
+
"article",
|
|
18670
|
+
true,
|
|
18671
|
+
options.guide?.skillsPortalUrl,
|
|
18672
|
+
options.guide?.repo,
|
|
18673
|
+
options.guide?.commitHash
|
|
18674
|
+
);
|
|
18675
|
+
});
|
|
18335
18676
|
const d = UPLOAD_HEADLESS_WEBSITE_FILES_PARAM_DESCRIPTIONS;
|
|
18336
18677
|
const registered = server.tool(
|
|
18337
18678
|
UPLOAD_HEADLESS_WEBSITE_FILES_TOOL_NAME,
|
|
@@ -18457,7 +18798,7 @@ function addUploadHeadlessWebsiteFilesTool(server, options) {
|
|
|
18457
18798
|
rethrowIfUnauthorized(err);
|
|
18458
18799
|
await authStrategy.enforceValidWixToken?.(err);
|
|
18459
18800
|
return errorResult2(
|
|
18460
|
-
uploadKey ? describeBundleReleaseError(err, uploadKey) : describeBundleUploadUrlError(err)
|
|
18801
|
+
uploadKey ? describeBundleReleaseError(err, uploadKey, "prepare", siteId) : describeBundleUploadUrlError(err, siteId)
|
|
18461
18802
|
);
|
|
18462
18803
|
}
|
|
18463
18804
|
}
|
|
@@ -18539,7 +18880,7 @@ function addUploadHeadlessWebsiteFilesTool(server, options) {
|
|
|
18539
18880
|
rethrowIfUnauthorized(err);
|
|
18540
18881
|
await authStrategy.enforceValidWixToken?.(err);
|
|
18541
18882
|
return errorResult2(
|
|
18542
|
-
stagedKey ? describeBundleReleaseError(err, stagedKey, "resend") : describeBundleUploadUrlError(err)
|
|
18883
|
+
stagedKey ? describeBundleReleaseError(err, stagedKey, "resend", siteId) : describeBundleUploadUrlError(err, siteId)
|
|
18543
18884
|
);
|
|
18544
18885
|
}
|
|
18545
18886
|
}
|
|
@@ -18563,6 +18904,14 @@ function addUploadHeadlessWebsiteFilesTool(server, options) {
|
|
|
18563
18904
|
siteId: metaSiteId,
|
|
18564
18905
|
upload: { path: "drop", files: siteFiles.length }
|
|
18565
18906
|
});
|
|
18907
|
+
let guide;
|
|
18908
|
+
try {
|
|
18909
|
+
guide = await guideFetch(extra?.httpClient);
|
|
18910
|
+
} catch (e) {
|
|
18911
|
+
logger.log(
|
|
18912
|
+
`[UploadHeadlessWebsiteFiles] guide fetch failed: ${e instanceof Error ? e.message : String(e)}`
|
|
18913
|
+
);
|
|
18914
|
+
}
|
|
18566
18915
|
return {
|
|
18567
18916
|
content: [
|
|
18568
18917
|
{
|
|
@@ -18572,7 +18921,7 @@ function addUploadHeadlessWebsiteFilesTool(server, options) {
|
|
|
18572
18921
|
siteUrl: dropped.siteUrl,
|
|
18573
18922
|
uploadId: dropped.uploadId,
|
|
18574
18923
|
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." : "")
|
|
18924
|
+
}) + (releaseNotes ? "\n\nRelease notes are kept for a Wix Astro build only; a static drop has none, so yours were not stored." : "") + formatInlineGuide(guide)
|
|
18576
18925
|
}
|
|
18577
18926
|
]
|
|
18578
18927
|
};
|
|
@@ -18581,7 +18930,7 @@ function addUploadHeadlessWebsiteFilesTool(server, options) {
|
|
|
18581
18930
|
logger.log(`[UploadHeadlessWebsiteFiles] Error: ${message}`);
|
|
18582
18931
|
rethrowIfUnauthorized(err);
|
|
18583
18932
|
await authStrategy.enforceValidWixToken?.(err);
|
|
18584
|
-
return errorResult2(message);
|
|
18933
|
+
return errorResult2(describeSiteAccessDenied(err, siteId) ?? message);
|
|
18585
18934
|
}
|
|
18586
18935
|
}
|
|
18587
18936
|
);
|
|
@@ -18890,7 +19239,11 @@ function addExecuteTool(server, options) {
|
|
|
18890
19239
|
}
|
|
18891
19240
|
let attachmentFiles = [];
|
|
18892
19241
|
try {
|
|
18893
|
-
attachmentFiles = await resolveAttachments(
|
|
19242
|
+
attachmentFiles = await resolveAttachments(
|
|
19243
|
+
attachments,
|
|
19244
|
+
void 0,
|
|
19245
|
+
WIX_AI_ASSISTANT_ATTACHMENT_HOSTS
|
|
19246
|
+
);
|
|
18894
19247
|
} catch (e) {
|
|
18895
19248
|
if (e instanceof AttachmentError) return executeErrorResult(e.message);
|
|
18896
19249
|
throw e;
|
|
@@ -21020,7 +21373,13 @@ function createWixToolkit(options) {
|
|
|
21020
21373
|
addUploadHeadlessWebsiteFilesTool(server, {
|
|
21021
21374
|
authStrategy,
|
|
21022
21375
|
toolDescriptions: effectiveToolDescriptions,
|
|
21023
|
-
clientName
|
|
21376
|
+
clientName,
|
|
21377
|
+
// The headless guide the drop reply carries is read the way the docs tools read it.
|
|
21378
|
+
guide: {
|
|
21379
|
+
skillsPortalUrl: docs?.skillsPortalUrl,
|
|
21380
|
+
repo: docs?.repo ?? docs?.skillsRepo,
|
|
21381
|
+
commitHash: docs?.commitHash ?? docs?.skillsPr
|
|
21382
|
+
}
|
|
21024
21383
|
});
|
|
21025
21384
|
}
|
|
21026
21385
|
const requestedGraphqlTools = GRAPHQL_TOOL_NAMES.filter(
|