@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/cjs/toolkit.cjs
CHANGED
|
@@ -17596,6 +17596,140 @@ async function fetchPlatformizedMenu(menuUrl) {
|
|
|
17596
17596
|
return data.content;
|
|
17597
17597
|
}
|
|
17598
17598
|
|
|
17599
|
+
// src/docs/guessed-url.ts
|
|
17600
|
+
var SKILLS_BASE = "https://www.wix.com/skills/";
|
|
17601
|
+
var registryUrl = (url) => normalizeSkillsUrl(url).replace(
|
|
17602
|
+
/^https:\/\/(?:dev\.wix\.com(?:\/docs)?|www\.wix\.com\/velo\/reference)\/skills\//,
|
|
17603
|
+
SKILLS_BASE
|
|
17604
|
+
);
|
|
17605
|
+
var DOCS_BASE = "https://dev.wix.com/docs/";
|
|
17606
|
+
var FILLER = /* @__PURE__ */ new Set([
|
|
17607
|
+
"",
|
|
17608
|
+
"md",
|
|
17609
|
+
"a",
|
|
17610
|
+
"an",
|
|
17611
|
+
"the",
|
|
17612
|
+
"and",
|
|
17613
|
+
"or",
|
|
17614
|
+
"of",
|
|
17615
|
+
"to",
|
|
17616
|
+
"wix",
|
|
17617
|
+
"s"
|
|
17618
|
+
]);
|
|
17619
|
+
var slugWords = (slug) => new Set(
|
|
17620
|
+
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)
|
|
17621
|
+
);
|
|
17622
|
+
function slugSimilarity(a, b) {
|
|
17623
|
+
const x = slugWords(a);
|
|
17624
|
+
const y = slugWords(b);
|
|
17625
|
+
if (x.size === 0 || y.size === 0) return 0;
|
|
17626
|
+
let shared = 0;
|
|
17627
|
+
for (const w of x) if (y.has(w)) shared++;
|
|
17628
|
+
return shared / (x.size + y.size - shared);
|
|
17629
|
+
}
|
|
17630
|
+
var lastSegment = (path) => path.slice(path.lastIndexOf("/") + 1);
|
|
17631
|
+
var folderOf = (path) => path.includes("/") ? path.slice(0, path.lastIndexOf("/")) : "";
|
|
17632
|
+
var SKILL_FILE_MATCH = 0.5;
|
|
17633
|
+
function closestSkillFile(url, manifest, manifestUrl) {
|
|
17634
|
+
const normalized = registryUrl(url).replace(/[?#].*$/, "");
|
|
17635
|
+
if (!normalized.startsWith(SKILLS_BASE)) return null;
|
|
17636
|
+
const [, ...rest] = normalized.slice(SKILLS_BASE.length).split("/");
|
|
17637
|
+
const asked = rest.join("/");
|
|
17638
|
+
const paths = (manifest.files ?? []).map((f) => f.path);
|
|
17639
|
+
if (!asked || paths.length === 0) return null;
|
|
17640
|
+
const base = manifest.base ?? manifestUrl.replace(/\.manifest\.json$/, "/");
|
|
17641
|
+
const name = lastSegment(asked);
|
|
17642
|
+
const folder = folderOf(asked);
|
|
17643
|
+
const sameName = paths.filter((p) => lastSegment(p) === name);
|
|
17644
|
+
if (sameName.length === 1) {
|
|
17645
|
+
return { kind: "file", path: sameName[0], url: base + sameName[0] };
|
|
17646
|
+
}
|
|
17647
|
+
const ranked = paths.filter((p) => folderOf(p) === folder).map((p) => ({ p, score: slugSimilarity(lastSegment(p), name) })).sort((a, b) => b.score - a.score);
|
|
17648
|
+
const [best, next] = ranked;
|
|
17649
|
+
if (best && best.score >= SKILL_FILE_MATCH && best.score > (next?.score ?? 0)) {
|
|
17650
|
+
return { kind: "file", path: best.p, url: base + best.p };
|
|
17651
|
+
}
|
|
17652
|
+
if (ranked.length > 0) {
|
|
17653
|
+
return {
|
|
17654
|
+
kind: "folder",
|
|
17655
|
+
folder,
|
|
17656
|
+
files: ranked.map((r) => base + r.p),
|
|
17657
|
+
manifestUrl
|
|
17658
|
+
};
|
|
17659
|
+
}
|
|
17660
|
+
const top = [...new Set(paths.map((p) => p.split("/")[0]))].sort();
|
|
17661
|
+
return {
|
|
17662
|
+
kind: "folder",
|
|
17663
|
+
folder: "",
|
|
17664
|
+
files: top.map((t) => base + t),
|
|
17665
|
+
manifestUrl
|
|
17666
|
+
};
|
|
17667
|
+
}
|
|
17668
|
+
function skillManifestUrl(url) {
|
|
17669
|
+
const normalized = registryUrl(url);
|
|
17670
|
+
if (!normalized.startsWith(SKILLS_BASE)) return null;
|
|
17671
|
+
const skill = normalized.slice(SKILLS_BASE.length).split(/[/?#]/)[0];
|
|
17672
|
+
return skill ? `${SKILLS_BASE}${skill}.manifest.json` : null;
|
|
17673
|
+
}
|
|
17674
|
+
var MENU_WALK_UP_LEVELS = 3;
|
|
17675
|
+
var MIN_MENU_SEGMENTS = 3;
|
|
17676
|
+
function parentMenuUrls(url) {
|
|
17677
|
+
const clean = url.replace(/[?#].*$/, "").replace(/\/+$/, "");
|
|
17678
|
+
if (!clean.startsWith(DOCS_BASE)) return [];
|
|
17679
|
+
const segments = clean.slice(DOCS_BASE.length).split("/");
|
|
17680
|
+
const out = [];
|
|
17681
|
+
for (let up = 1; up <= MENU_WALK_UP_LEVELS; up++) {
|
|
17682
|
+
const kept = segments.length - up;
|
|
17683
|
+
if (kept < MIN_MENU_SEGMENTS) break;
|
|
17684
|
+
out.push(DOCS_BASE + segments.slice(0, kept).join("/"));
|
|
17685
|
+
}
|
|
17686
|
+
return out;
|
|
17687
|
+
}
|
|
17688
|
+
function closestMenuLinks(menuText, menuUrl, askedUrl, limit = 3) {
|
|
17689
|
+
const asked = lastSegment(
|
|
17690
|
+
askedUrl.replace(/[?#].*$/, "").replace(/\/+$/, "")
|
|
17691
|
+
);
|
|
17692
|
+
const under = `${menuUrl.replace(/\/+$/, "")}/`;
|
|
17693
|
+
const links = [
|
|
17694
|
+
...new Set(
|
|
17695
|
+
menuText.match(/https:\/\/dev\.wix\.com\/docs\/[^\s)\]>"'`]+/g) ?? []
|
|
17696
|
+
)
|
|
17697
|
+
].filter((l) => l.startsWith(under));
|
|
17698
|
+
return links.map((l) => ({
|
|
17699
|
+
l,
|
|
17700
|
+
score: slugSimilarity(lastSegment(l.replace(/\.md$/, "")), asked)
|
|
17701
|
+
})).filter((r) => r.score > 0).sort((a, b) => b.score - a.score).slice(0, limit).map((r) => r.l);
|
|
17702
|
+
}
|
|
17703
|
+
var SMALL_MENU = 3;
|
|
17704
|
+
var MENU_ENTRY_MATCH = 0.75;
|
|
17705
|
+
function subMenusToRead(menuText, menuUrl, askedUrl, limit = 2) {
|
|
17706
|
+
const asked = lastSegment(
|
|
17707
|
+
askedUrl.replace(/[?#].*$/, "").replace(/\/+$/, "")
|
|
17708
|
+
);
|
|
17709
|
+
const under = `${menuUrl.replace(/\/+$/, "")}/`;
|
|
17710
|
+
const methods = new Set(
|
|
17711
|
+
[
|
|
17712
|
+
...menuText.matchAll(
|
|
17713
|
+
/\]\((https:\/\/dev\.wix\.com\/docs\/[^)\s]+)\)\s*_\((?:GET|POST|PUT|PATCH|DELETE)\)_/g
|
|
17714
|
+
)
|
|
17715
|
+
].map((m) => m[1])
|
|
17716
|
+
);
|
|
17717
|
+
const entries = [
|
|
17718
|
+
...new Set(
|
|
17719
|
+
menuText.match(/https:\/\/dev\.wix\.com\/docs\/[^\s)\]>"'`]+/g) ?? []
|
|
17720
|
+
)
|
|
17721
|
+
].filter(
|
|
17722
|
+
(l) => l.startsWith(under) && !l.slice(under.length).replace(/\.md$/, "").includes("/") && !methods.has(l)
|
|
17723
|
+
);
|
|
17724
|
+
const scored = entries.map((l) => ({
|
|
17725
|
+
l,
|
|
17726
|
+
score: slugSimilarity(lastSegment(l.replace(/\.md$/, "")), asked)
|
|
17727
|
+
})).sort((a, b) => b.score - a.score);
|
|
17728
|
+
if (scored.some((s) => s.score >= MENU_ENTRY_MATCH)) return [];
|
|
17729
|
+
if (entries.length <= SMALL_MENU) return entries;
|
|
17730
|
+
return scored.filter((s) => s.score > 0).slice(0, limit).map((s) => s.l);
|
|
17731
|
+
}
|
|
17732
|
+
|
|
17599
17733
|
// ../../node_modules/dedent/dist/dedent.mjs
|
|
17600
17734
|
function ownKeys2(object, enumerableOnly) {
|
|
17601
17735
|
var keys = Object.keys(object);
|
|
@@ -17800,7 +17934,8 @@ var DISCOVERY_FLOWS = [
|
|
|
17800
17934
|
function directRouteLines(includeReadme, includeImageUploadGuidance) {
|
|
17801
17935
|
return [
|
|
17802
17936
|
...includeReadme ? [
|
|
17803
|
-
`- **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.
|
|
17937
|
+
`- **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.`,
|
|
17938
|
+
"- **Suggesting domain names**: call GetSuggestedDomains directly, with the business or site name as the query."
|
|
17804
17939
|
] : [],
|
|
17805
17940
|
"- **Listing, showing or finding their Wix sites**: call ListWixSites directly.",
|
|
17806
17941
|
...includeImageUploadGuidance ? [
|
|
@@ -17874,7 +18009,7 @@ var toWidget = (text) => Object.values(SITE_CREATION_COPY).reduce(
|
|
|
17874
18009
|
(acc, { nonWidget, widget }) => acc.replaceAll(nonWidget, widget),
|
|
17875
18010
|
text
|
|
17876
18011
|
);
|
|
17877
|
-
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";
|
|
18012
|
+
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)";
|
|
17878
18013
|
var EXECUTE_WIX_API_INTRO = dedent_default`
|
|
17879
18014
|
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.
|
|
17880
18015
|
One function holds the whole task: chained calls, pagination, loops, branching, and the reads that feed its writes.
|
|
@@ -18190,8 +18325,7 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true,
|
|
|
18190
18325
|
${reminder}
|
|
18191
18326
|
`,
|
|
18192
18327
|
ManageWixSite: dedent_default`
|
|
18193
|
-
|
|
18194
|
-
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\`.
|
|
18328
|
+
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\`.
|
|
18195
18329
|
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"}]).
|
|
18196
18330
|
The API endpoint url param MUST ALWAYS be taken from the conversation context.
|
|
18197
18331
|
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.
|
|
@@ -18261,7 +18395,7 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true,
|
|
|
18261
18395
|
${reminder}
|
|
18262
18396
|
`,
|
|
18263
18397
|
WixSiteCreation: dedent_default`
|
|
18264
|
-
|
|
18398
|
+
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.
|
|
18265
18399
|
`,
|
|
18266
18400
|
"import-claude-design-from-url": dedent_default`
|
|
18267
18401
|
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.
|
|
@@ -18703,16 +18837,16 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true,
|
|
|
18703
18837
|
`,
|
|
18704
18838
|
ExecuteWixAPI: executeWixApiDescription,
|
|
18705
18839
|
// Named in SERVER_INSTRUCTIONS: when this tool's name or what it is for changes, update them to match.
|
|
18706
|
-
|
|
18707
|
-
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.
|
|
18840
|
+
WixHarmonyEditorBuild: dedent_default`
|
|
18841
|
+
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.
|
|
18708
18842
|
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.
|
|
18709
|
-
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
|
|
18843
|
+
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}
|
|
18710
18844
|
`,
|
|
18711
18845
|
// Named in SERVER_INSTRUCTIONS: when this tool's name or what it is for changes, update them to match.
|
|
18712
18846
|
SearchSiteTemplates: dedent_default`
|
|
18713
18847
|
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.
|
|
18714
18848
|
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.
|
|
18715
|
-
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
|
|
18849
|
+
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}
|
|
18716
18850
|
`,
|
|
18717
18851
|
CreateSiteFromTemplate: dedent_default`
|
|
18718
18852
|
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.
|
|
@@ -18749,20 +18883,20 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true,
|
|
|
18749
18883
|
UploadHeadlessWebsiteFiles: dedent_default`
|
|
18750
18884
|
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.
|
|
18751
18885
|
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.
|
|
18752
|
-
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 (
|
|
18886
|
+
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).
|
|
18753
18887
|
`,
|
|
18754
18888
|
// Named in SERVER_INSTRUCTIONS: when this tool's name or what it is for changes, update them to match.
|
|
18755
18889
|
StartDeployWebsite: dedent_default`
|
|
18756
18890
|
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.
|
|
18757
18891
|
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.
|
|
18758
18892
|
Returns the guide; follow it and do the work yourself. Upload with UploadHeadlessWebsiteFiles once the site exists; the drop page is only the fallback.
|
|
18759
|
-
NOT for: a user who asked for Wix's editor, the Wix Harmony Editor, a template, Wix Studio or the Classic Editor (
|
|
18893
|
+
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).
|
|
18760
18894
|
`,
|
|
18761
18895
|
// Named in SERVER_INSTRUCTIONS: when this tool's name or what it is for changes, update them to match.
|
|
18762
18896
|
StartHeadlessBuild: dedent_default`
|
|
18763
18897
|
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.
|
|
18764
18898
|
Call it right away: it only returns the guide, and the site's details are not needed first.
|
|
18765
|
-
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 (
|
|
18899
|
+
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).
|
|
18766
18900
|
`
|
|
18767
18901
|
};
|
|
18768
18902
|
descriptions.SupportAndFeedback = descriptions.ReportIssueToWix;
|
|
@@ -18990,7 +19124,7 @@ var defaultConfig = {
|
|
|
18990
19124
|
var widgetSiteCreationConfig = {
|
|
18991
19125
|
toolDescriptions: Object.fromEntries(
|
|
18992
19126
|
[
|
|
18993
|
-
"
|
|
19127
|
+
"WixHarmonyEditorBuild",
|
|
18994
19128
|
"WixSiteCreation",
|
|
18995
19129
|
"SearchSiteTemplates",
|
|
18996
19130
|
"CreateSiteFromTemplate"
|
|
@@ -20413,9 +20547,16 @@ function shouldTryMenuFallbackForDocsRead(error, articleUrl) {
|
|
|
20413
20547
|
if (!message.includes("404") || !isMenuFallbackUrl) {
|
|
20414
20548
|
return false;
|
|
20415
20549
|
}
|
|
20416
|
-
return !
|
|
20417
|
-
|
|
20418
|
-
|
|
20550
|
+
return !isKnownArticleError(error);
|
|
20551
|
+
}
|
|
20552
|
+
function isKnownArticleError(error) {
|
|
20553
|
+
const message = getErrorMessage(error);
|
|
20554
|
+
return [
|
|
20555
|
+
"This resource is deprecated",
|
|
20556
|
+
"This method is deprecated",
|
|
20557
|
+
"This article is deprecated",
|
|
20558
|
+
"No schema found"
|
|
20559
|
+
].some((knownArticleError) => message.includes(knownArticleError));
|
|
20419
20560
|
}
|
|
20420
20561
|
function formatDocsReadError(toolAction, error) {
|
|
20421
20562
|
return `Could not fetch ${toolAction}: "${getErrorMessage(error)}".`;
|
|
@@ -20502,6 +20643,58 @@ var addDocsTools = (server, allowedTools = [
|
|
|
20502
20643
|
}
|
|
20503
20644
|
return menuContent.length > MENU_CONTENT_THRESHOLD ? summarizeLargeMenu(menuContent, url) : normalizeMenuContent(menuContent);
|
|
20504
20645
|
};
|
|
20646
|
+
const nearestToMissingPage = async (httpClient, url, isSkillsPage) => {
|
|
20647
|
+
try {
|
|
20648
|
+
if (isSkillsPage) {
|
|
20649
|
+
const manifestUrl = skillManifestUrl(url);
|
|
20650
|
+
if (!manifestUrl) return null;
|
|
20651
|
+
const manifest = JSON.parse(
|
|
20652
|
+
await fetchArticleContent(httpClient, manifestUrl, "article")
|
|
20653
|
+
);
|
|
20654
|
+
const guess = closestSkillFile(url, manifest, manifestUrl);
|
|
20655
|
+
if (!guess) return null;
|
|
20656
|
+
if (guess.kind === "file") {
|
|
20657
|
+
const text = await fetchArticleContent(
|
|
20658
|
+
httpClient,
|
|
20659
|
+
guess.url,
|
|
20660
|
+
"article"
|
|
20661
|
+
);
|
|
20662
|
+
return `Note: "${url}" does not exist. Found a similar file in the skill, ${guess.url}, and read it:
|
|
20663
|
+
|
|
20664
|
+
${text}`;
|
|
20665
|
+
}
|
|
20666
|
+
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}):
|
|
20667
|
+
|
|
20668
|
+
${guess.files.map((f) => `- ${f}`).join("\n")}`;
|
|
20669
|
+
}
|
|
20670
|
+
for (const menuUrl of parentMenuUrls(url)) {
|
|
20671
|
+
const text = await fetchMenuTextForUrl(httpClient, menuUrl);
|
|
20672
|
+
if (!text) continue;
|
|
20673
|
+
const subMenus = (await Promise.all(
|
|
20674
|
+
subMenusToRead(text, menuUrl, url).map(async (sub) => ({
|
|
20675
|
+
sub,
|
|
20676
|
+
text: await fetchMenuTextForUrl(httpClient, sub).catch(() => null)
|
|
20677
|
+
}))
|
|
20678
|
+
)).filter((m) => !!m.text);
|
|
20679
|
+
const closest = closestMenuLinks(
|
|
20680
|
+
[text, ...subMenus.map((m) => m.text)].join("\n"),
|
|
20681
|
+
menuUrl,
|
|
20682
|
+
url
|
|
20683
|
+
);
|
|
20684
|
+
return [
|
|
20685
|
+
`Note: "${url}" does not exist. Showing the nearest documentation menu above it, ${menuUrl}; read the page you need from its links.`,
|
|
20686
|
+
...closest.length ? [
|
|
20687
|
+
`Closest to what you asked for:
|
|
20688
|
+
${closest.map((l) => `- ${l}`).join("\n")}`
|
|
20689
|
+
] : [],
|
|
20690
|
+
text
|
|
20691
|
+
].join("\n\n");
|
|
20692
|
+
}
|
|
20693
|
+
} catch (error) {
|
|
20694
|
+
logger.error(`[docs] no nearest page for ${url}: ${error}`);
|
|
20695
|
+
}
|
|
20696
|
+
return null;
|
|
20697
|
+
};
|
|
20505
20698
|
if (startDeployWebsiteEnabled) {
|
|
20506
20699
|
addStartDeployWebsiteTool(server, disableTools || [], toolDescriptions, {
|
|
20507
20700
|
skillsPortalUrl,
|
|
@@ -21238,6 +21431,16 @@ ${text}`
|
|
|
21238
21431
|
} catch {
|
|
21239
21432
|
}
|
|
21240
21433
|
}
|
|
21434
|
+
if (getErrorMessage(error).includes("404") && !isKnownArticleError(error)) {
|
|
21435
|
+
const nearest = await nearestToMissingPage(
|
|
21436
|
+
httpClient,
|
|
21437
|
+
articleUrl,
|
|
21438
|
+
isSkillsPage
|
|
21439
|
+
);
|
|
21440
|
+
if (nearest) {
|
|
21441
|
+
return { content: [{ type: "text", text: nearest }] };
|
|
21442
|
+
}
|
|
21443
|
+
}
|
|
21241
21444
|
return {
|
|
21242
21445
|
isError: true,
|
|
21243
21446
|
content: [
|
|
@@ -21431,6 +21634,16 @@ var getRetryHint = (errorDetails) => {
|
|
|
21431
21634
|
}
|
|
21432
21635
|
return /"operator":"\{/.test(errorDetails) ? SINGLE_OPERATOR_PER_FIELD_HINT : FIELD_CAPABILITIES_HINT;
|
|
21433
21636
|
};
|
|
21637
|
+
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;
|
|
21638
|
+
function missingSiteContext(status, responseData) {
|
|
21639
|
+
const details = typeof responseData === "string" ? responseData : JSON.stringify(responseData ?? "");
|
|
21640
|
+
if (NO_SITE_IN_REQUEST.test(details)) return "certain";
|
|
21641
|
+
const parsed = typeof responseData === "string" ? safeParseJSON(responseData) : responseData;
|
|
21642
|
+
if (status === 403 && parsed && typeof parsed === "object" && !parsed.message) {
|
|
21643
|
+
return "likely";
|
|
21644
|
+
}
|
|
21645
|
+
return null;
|
|
21646
|
+
}
|
|
21434
21647
|
var safeParseJSON = (text) => {
|
|
21435
21648
|
try {
|
|
21436
21649
|
return JSON.parse(text);
|
|
@@ -21442,7 +21655,8 @@ var buildErrorMessage = ({
|
|
|
21442
21655
|
status,
|
|
21443
21656
|
statusText,
|
|
21444
21657
|
requestId,
|
|
21445
|
-
responseData
|
|
21658
|
+
responseData,
|
|
21659
|
+
retryHint
|
|
21446
21660
|
}) => {
|
|
21447
21661
|
const responseDataObject = typeof responseData === "string" ? safeParseJSON(responseData) : responseData;
|
|
21448
21662
|
const errorDetails = typeof responseDataObject === "object" ? JSON.stringify(responseDataObject) : responseDataObject;
|
|
@@ -21454,7 +21668,7 @@ var buildErrorMessage = ({
|
|
|
21454
21668
|
].filter((str) => !!str).join("\n");
|
|
21455
21669
|
return `${baseMessage}
|
|
21456
21670
|
|
|
21457
|
-
${getRetryHint(String(errorDetails))}`;
|
|
21671
|
+
${retryHint ?? getRetryHint(String(errorDetails))}`;
|
|
21458
21672
|
};
|
|
21459
21673
|
var handleWixAPIResponse = async (response) => {
|
|
21460
21674
|
const requestId = response.headers?.["x-wix-request-id"];
|
|
@@ -21866,6 +22080,9 @@ If you can't deliver the image any of these ways, call ${imagePickerToolName} so
|
|
|
21866
22080
|
var MAX_ALLOWED_RESPONSE_CHARS = 2e6;
|
|
21867
22081
|
|
|
21868
22082
|
// src/api-call/index.ts
|
|
22083
|
+
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`";
|
|
22084
|
+
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)}.`;
|
|
22085
|
+
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.`;
|
|
21869
22086
|
var DEFAULT_MAX_RESPONSE_SIZE = 5e4;
|
|
21870
22087
|
function extractReturnedRows(responseData) {
|
|
21871
22088
|
if (responseData === null || typeof responseData !== "object") {
|
|
@@ -22189,6 +22406,18 @@ function addApiCallTool(server, strategy, allowedTools = ["CallWixSiteAPI", "Lis
|
|
|
22189
22406
|
if (!response) {
|
|
22190
22407
|
throw new Error(`Failed to call Wix Account API: ${error}`);
|
|
22191
22408
|
}
|
|
22409
|
+
const noSite = missingSiteContext(response.status, response.data);
|
|
22410
|
+
if (noSite) {
|
|
22411
|
+
throw new Error(
|
|
22412
|
+
buildErrorMessage({
|
|
22413
|
+
status: response.status,
|
|
22414
|
+
statusText: response.statusText,
|
|
22415
|
+
requestId: response.headers?.["x-wix-request-id"] ?? null,
|
|
22416
|
+
responseData: response.data,
|
|
22417
|
+
retryHint: (noSite === "certain" ? manageNoSiteHint : manageEmpty403Hint)(!!options?.executeWixAPIServed)
|
|
22418
|
+
})
|
|
22419
|
+
);
|
|
22420
|
+
}
|
|
22192
22421
|
return handleWixAPIResponse(response);
|
|
22193
22422
|
}
|
|
22194
22423
|
}
|
|
@@ -23015,15 +23244,18 @@ var OAI_BLOB_ACCOUNTS = new Set(
|
|
|
23015
23244
|
"westus3"
|
|
23016
23245
|
].map((region) => `oaisdmntpr${region}.blob.core.windows.net`)
|
|
23017
23246
|
);
|
|
23018
|
-
|
|
23247
|
+
var WIX_AI_ASSISTANT_ATTACHMENT_HOSTS = /* @__PURE__ */ new Set([
|
|
23248
|
+
"wixmp-a85a29aaf55baa3e5171a82d.wixmp.com"
|
|
23249
|
+
]);
|
|
23250
|
+
function isAllowedAttachmentHost(hostname, extraHosts) {
|
|
23019
23251
|
const h = hostname.toLowerCase();
|
|
23020
|
-
return OAI_USERCONTENT_HOST.test(h) || OAI_BLOB_ACCOUNTS.has(h);
|
|
23252
|
+
return OAI_USERCONTENT_HOST.test(h) || OAI_BLOB_ACCOUNTS.has(h) || !!extraHosts?.has(h);
|
|
23021
23253
|
}
|
|
23022
|
-
function checkAttachmentUrl(url) {
|
|
23254
|
+
function checkAttachmentUrl(url, extraHosts) {
|
|
23023
23255
|
if (url.protocol !== "https:") return "is not an https URL";
|
|
23024
23256
|
if (url.port) return "uses a non-default port";
|
|
23025
|
-
if (!isAllowedAttachmentHost(url.hostname)) {
|
|
23026
|
-
return `host "${url.hostname}" is not a
|
|
23257
|
+
if (!isAllowedAttachmentHost(url.hostname, extraHosts)) {
|
|
23258
|
+
return `host "${url.hostname}" is not a chat attachment host`;
|
|
23027
23259
|
}
|
|
23028
23260
|
return void 0;
|
|
23029
23261
|
}
|
|
@@ -23050,7 +23282,7 @@ function toBase64(bytes) {
|
|
|
23050
23282
|
}
|
|
23051
23283
|
return btoa(binary);
|
|
23052
23284
|
}
|
|
23053
|
-
async function fetchAllowed(start, fetchImpl, label) {
|
|
23285
|
+
async function fetchAllowed(start, fetchImpl, label, extraHosts) {
|
|
23054
23286
|
let url = start;
|
|
23055
23287
|
for (let hop = 0; ; hop++) {
|
|
23056
23288
|
const res = await fetchImpl(url.toString(), { redirect: "manual" });
|
|
@@ -23067,7 +23299,7 @@ async function fetchAllowed(start, fetchImpl, label) {
|
|
|
23067
23299
|
} catch {
|
|
23068
23300
|
throw new AttachmentError(`${label}: redirect to an invalid URL.`);
|
|
23069
23301
|
}
|
|
23070
|
-
const problem = checkAttachmentUrl(next);
|
|
23302
|
+
const problem = checkAttachmentUrl(next, extraHosts);
|
|
23071
23303
|
if (problem) {
|
|
23072
23304
|
throw new AttachmentError(
|
|
23073
23305
|
`${label}: the download redirected to a URL that ${problem}; refused.`
|
|
@@ -23105,7 +23337,7 @@ async function readWithin(res, budget, overBudget) {
|
|
|
23105
23337
|
}
|
|
23106
23338
|
return out;
|
|
23107
23339
|
}
|
|
23108
|
-
async function resolveAttachments(attachments, fetchImpl = fetch) {
|
|
23340
|
+
async function resolveAttachments(attachments, fetchImpl = fetch, extraHosts) {
|
|
23109
23341
|
if (!Array.isArray(attachments) || attachments.length === 0) return [];
|
|
23110
23342
|
const items = [];
|
|
23111
23343
|
attachments.forEach((raw, i) => {
|
|
@@ -23124,10 +23356,10 @@ async function resolveAttachments(attachments, fetchImpl = fetch) {
|
|
|
23124
23356
|
)}). \`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).`
|
|
23125
23357
|
);
|
|
23126
23358
|
}
|
|
23127
|
-
const problem = checkAttachmentUrl(url);
|
|
23359
|
+
const problem = checkAttachmentUrl(url, extraHosts);
|
|
23128
23360
|
if (problem) {
|
|
23129
23361
|
throw new AttachmentError(
|
|
23130
|
-
`attachments[${i}].download_url ${problem}.
|
|
23362
|
+
`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.`
|
|
23131
23363
|
);
|
|
23132
23364
|
}
|
|
23133
23365
|
items.push({ a, url });
|
|
@@ -23139,7 +23371,7 @@ async function resolveAttachments(attachments, fetchImpl = fetch) {
|
|
|
23139
23371
|
const label = `attachments[${i}] (${path})`;
|
|
23140
23372
|
let res;
|
|
23141
23373
|
try {
|
|
23142
|
-
res = await fetchAllowed(url, fetchImpl, label);
|
|
23374
|
+
res = await fetchAllowed(url, fetchImpl, label, extraHosts);
|
|
23143
23375
|
} catch (e) {
|
|
23144
23376
|
if (e instanceof AttachmentError) throw e;
|
|
23145
23377
|
throw new AttachmentError(
|
|
@@ -23569,7 +23801,9 @@ var FREE_TEXT_INFO_PARAMS = [
|
|
|
23569
23801
|
"agentIdentity",
|
|
23570
23802
|
"wixAccess",
|
|
23571
23803
|
"reason",
|
|
23572
|
-
"whyNotExecuteWixAPI"
|
|
23804
|
+
"whyNotExecuteWixAPI",
|
|
23805
|
+
"editorRequest",
|
|
23806
|
+
"templateRequest"
|
|
23573
23807
|
];
|
|
23574
23808
|
var EMAIL = /[A-Z0-9._%+-]+@[A-Z0-9-]+(?:\.[A-Z0-9-]+)*\.[A-Z]{2,}/gi;
|
|
23575
23809
|
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;
|
|
@@ -23617,9 +23851,65 @@ function redactInfoParams(value) {
|
|
|
23617
23851
|
}
|
|
23618
23852
|
return redacted;
|
|
23619
23853
|
}
|
|
23854
|
+
var MAX_LISTED_PATHS_CHARS = 2e3;
|
|
23855
|
+
function summarizeFileBundle(bundle) {
|
|
23856
|
+
let sections;
|
|
23857
|
+
try {
|
|
23858
|
+
sections = parseFileBundle(bundle).map((file) => ({
|
|
23859
|
+
path: file.path,
|
|
23860
|
+
bytes: file.bytes.length
|
|
23861
|
+
}));
|
|
23862
|
+
} catch {
|
|
23863
|
+
sections = [];
|
|
23864
|
+
}
|
|
23865
|
+
if (sections.length === 0) {
|
|
23866
|
+
return `${new TextEncoder().encode(bundle).length} bytes, no file sections`;
|
|
23867
|
+
}
|
|
23868
|
+
return formatFileSummary(sections);
|
|
23869
|
+
}
|
|
23870
|
+
function formatFileSummary(files) {
|
|
23871
|
+
const total = files.reduce((sum, file) => sum + file.bytes, 0);
|
|
23872
|
+
let listed = "";
|
|
23873
|
+
let shown = 0;
|
|
23874
|
+
for (const file of files) {
|
|
23875
|
+
const entry = `${shown ? ", " : ""}${file.path} (${file.bytes})`;
|
|
23876
|
+
if (listed.length + entry.length > MAX_LISTED_PATHS_CHARS) break;
|
|
23877
|
+
listed += entry;
|
|
23878
|
+
shown++;
|
|
23879
|
+
}
|
|
23880
|
+
const more = files.length - shown;
|
|
23881
|
+
return `${files.length} file(s), ${total} bytes: ${listed}${more ? `${shown ? ", " : ""}and ${more} more` : ""}`;
|
|
23882
|
+
}
|
|
23883
|
+
function compactFileParams(value) {
|
|
23884
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
23885
|
+
return value;
|
|
23886
|
+
}
|
|
23887
|
+
const record = value;
|
|
23888
|
+
const files = record.files;
|
|
23889
|
+
if (typeof files === "string") {
|
|
23890
|
+
return { ...record, files: summarizeFileBundle(files) };
|
|
23891
|
+
}
|
|
23892
|
+
if (Array.isArray(files) && files.length > 0 && files.every(
|
|
23893
|
+
(entry) => entry && typeof entry === "object" && typeof entry.content === "string"
|
|
23894
|
+
)) {
|
|
23895
|
+
return {
|
|
23896
|
+
...record,
|
|
23897
|
+
files: formatFileSummary(
|
|
23898
|
+
files.map((entry) => ({
|
|
23899
|
+
path: entry.path ?? "?",
|
|
23900
|
+
bytes: new TextEncoder().encode(entry.content).length
|
|
23901
|
+
}))
|
|
23902
|
+
)
|
|
23903
|
+
};
|
|
23904
|
+
}
|
|
23905
|
+
return value;
|
|
23906
|
+
}
|
|
23907
|
+
function prepareForBi(value) {
|
|
23908
|
+
return compactFileParams(redactInfoParams(value));
|
|
23909
|
+
}
|
|
23620
23910
|
function serializeToolParamsForBi(args) {
|
|
23621
23911
|
return JSON.stringify(
|
|
23622
|
-
Array.isArray(args) ? args.map(
|
|
23912
|
+
Array.isArray(args) ? args.map(prepareForBi) : prepareForBi(args)
|
|
23623
23913
|
);
|
|
23624
23914
|
}
|
|
23625
23915
|
|
|
@@ -23627,6 +23917,8 @@ function serializeToolParamsForBi(args) {
|
|
|
23627
23917
|
var UPLOAD_HEADLESS_WEBSITE_FILES_TOOL_NAME = "UploadHeadlessWebsiteFiles";
|
|
23628
23918
|
var HEADLESS_BUSINESS = "https://www.wixapis.com/headless-business-setup/v1/headless-business";
|
|
23629
23919
|
var MAX_BUNDLE_BYTES = 100 * 1024 * 1024;
|
|
23920
|
+
var MAX_RELEASE_STATIC_FILES = 2500;
|
|
23921
|
+
var MAX_SOURCE_ZIP_BYTES = 64 * 1024 * 1024;
|
|
23630
23922
|
function dropAttribution(clientName) {
|
|
23631
23923
|
const slug = (clientName ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
|
|
23632
23924
|
return `?campaign=mcp&agent=${encodeURIComponent(slug || "wix-mcp")}`;
|
|
@@ -23649,6 +23941,17 @@ var UPLOAD_HEADLESS_WEBSITE_FILES_PARAM_DESCRIPTIONS = {
|
|
|
23649
23941
|
};
|
|
23650
23942
|
var UPLOAD_RECIPE_SKILL_URL = "https://www.wix.com/skills/wix-manage/references/sites/upload-static-site.md";
|
|
23651
23943
|
var DOMAIN_RECIPE_SKILL_URL = "https://www.wix.com/skills/wix-manage/references/domains/domain-search-purchase-and-connect.md";
|
|
23944
|
+
function formatInlineGuide(guide) {
|
|
23945
|
+
if (!guide || !guide.trim()) return "";
|
|
23946
|
+
return [
|
|
23947
|
+
"",
|
|
23948
|
+
"---",
|
|
23949
|
+
"",
|
|
23950
|
+
`Wix's headless guide, the build of a site that needs Wix business features (also at https://www.wix.com/headless/skill.md):`,
|
|
23951
|
+
"",
|
|
23952
|
+
guide.trim()
|
|
23953
|
+
].join("\n");
|
|
23954
|
+
}
|
|
23652
23955
|
function formatDropResult(r) {
|
|
23653
23956
|
const shown = r.files.slice(0, 60);
|
|
23654
23957
|
return [
|
|
@@ -23661,7 +23964,7 @@ function formatDropResult(r) {
|
|
|
23661
23964
|
`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}).`,
|
|
23662
23965
|
"",
|
|
23663
23966
|
`- 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.`,
|
|
23664
|
-
`- Wix business features (${WIX_BUSINESS_FEATURES}):
|
|
23967
|
+
`- 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.`
|
|
23665
23968
|
].join("\n");
|
|
23666
23969
|
}
|
|
23667
23970
|
function curlPut(zipName, put, fallback) {
|
|
@@ -23689,7 +23992,7 @@ function formatPrepareResult(siteId, urls, size, given = {}) {
|
|
|
23689
23992
|
const lines = [
|
|
23690
23993
|
`Upload URLs ready for site ${siteId}.${expiry} Two steps, then call this tool again.`,
|
|
23691
23994
|
"",
|
|
23692
|
-
`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:`,
|
|
23995
|
+
`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:`,
|
|
23693
23996
|
` ${curlPut("build.zip", urls, zipHeaders)}`
|
|
23694
23997
|
];
|
|
23695
23998
|
if (given.expectedHeadRevisionId)
|
|
@@ -23701,7 +24004,7 @@ function formatPrepareResult(siteId, urls, size, given = {}) {
|
|
|
23701
24004
|
secondCall.expectedHeadRevisionId = s.headRevisionId;
|
|
23702
24005
|
lines.push(
|
|
23703
24006
|
"",
|
|
23704
|
-
`2. ${SOURCE_ZIP_CONTENTS} Upload it:`,
|
|
24007
|
+
`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:`,
|
|
23705
24008
|
` ${curlPut("source.zip", s, { "content-type": "application/zip" })}`,
|
|
23706
24009
|
...s.expiresAt && s.expiresAt !== urls.expiresAt ? [` This URL expires at ${s.expiresAt}.`] : [],
|
|
23707
24010
|
...s.headRevisionId ? [
|
|
@@ -23720,6 +24023,13 @@ function formatPrepareResult(siteId, urls, size, given = {}) {
|
|
|
23720
24023
|
"",
|
|
23721
24024
|
`3. Call UploadHeadlessWebsiteFiles again with ${JSON.stringify(secondCall)} (plus \`calledStartDeployWebsite\`, \`agentIdentity\`, \`reason\`). No \`files\` or \`attachments\` on that call.`
|
|
23722
24025
|
);
|
|
24026
|
+
if (secondCall.sourceUploadId) {
|
|
24027
|
+
const withoutSource = { ...secondCall };
|
|
24028
|
+
delete withoutSource.sourceUploadId;
|
|
24029
|
+
lines.push(
|
|
24030
|
+
` 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.`
|
|
24031
|
+
);
|
|
24032
|
+
}
|
|
23723
24033
|
return lines.join("\n");
|
|
23724
24034
|
}
|
|
23725
24035
|
function formatBundleReleaseResult(siteId, r, notes = []) {
|
|
@@ -23737,10 +24047,21 @@ function formatBundleReleaseResult(siteId, r, notes = []) {
|
|
|
23737
24047
|
}
|
|
23738
24048
|
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";
|
|
23739
24049
|
var apiBody = (err) => err.response.data ? ` (${err.response.data.slice(0, 300)})` : "";
|
|
23740
|
-
|
|
24050
|
+
var SITE_ACCESS_PERMISSION = "OAUTH_APP.APP_CREATE";
|
|
24051
|
+
function describeSiteAccessDenied(err, siteId, stagedKey) {
|
|
24052
|
+
if (!(err instanceof WixFetchError) || err.status !== 403 || !err.response.data.includes(SITE_ACCESS_PERMISSION)) {
|
|
24053
|
+
return null;
|
|
24054
|
+
}
|
|
24055
|
+
const site = siteId ? `site ${siteId}` : "this site";
|
|
24056
|
+
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.";
|
|
24057
|
+
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)}`;
|
|
24058
|
+
}
|
|
24059
|
+
function describeBundleUploadUrlError(err, siteId) {
|
|
23741
24060
|
if (!(err instanceof WixFetchError)) {
|
|
23742
24061
|
return err instanceof Error ? err.message : String(err);
|
|
23743
24062
|
}
|
|
24063
|
+
const denied = describeSiteAccessDenied(err, siteId);
|
|
24064
|
+
if (denied) return denied;
|
|
23744
24065
|
const body = apiBody(err);
|
|
23745
24066
|
switch (err.status) {
|
|
23746
24067
|
case 400:
|
|
@@ -23760,17 +24081,24 @@ function releaseNotesForService(notes) {
|
|
|
23760
24081
|
const chars = [...notes.replace(/\s+/g, " ").trim()];
|
|
23761
24082
|
return chars.length <= RELEASE_NOTES_MAX_CHARS ? chars.join("") : `${chars.slice(0, RELEASE_NOTES_MAX_CHARS - 1).join("").trimEnd()}\u2026`;
|
|
23762
24083
|
}
|
|
23763
|
-
function describeBundleReleaseError(err, uploadKey, retry = "prepare") {
|
|
24084
|
+
function describeBundleReleaseError(err, uploadKey, retry = "prepare", siteId) {
|
|
23764
24085
|
if (!(err instanceof WixFetchError)) {
|
|
23765
24086
|
return err instanceof Error ? err.message : String(err);
|
|
23766
24087
|
}
|
|
24088
|
+
const denied = describeSiteAccessDenied(err, siteId, uploadKey);
|
|
24089
|
+
if (denied) return denied;
|
|
23767
24090
|
const body = apiBody(err);
|
|
23768
24091
|
const code = err.response.data ?? "";
|
|
24092
|
+
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`"}`;
|
|
24093
|
+
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.";
|
|
24094
|
+
if (err.status >= 502 && err.status <= 504 && !code.includes("RELEASE_NOT_HELD") && !code.includes("{")) {
|
|
24095
|
+
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}`;
|
|
24096
|
+
}
|
|
23769
24097
|
switch (err.status) {
|
|
23770
24098
|
case 404:
|
|
23771
|
-
return `Nothing uploaded for uploadKey ${uploadKey}: the build zip was not PUT to its URL,
|
|
24099
|
+
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}`;
|
|
23772
24100
|
case 409:
|
|
23773
|
-
return `The project's source repository moved since the upload URLs were made; nothing was released. ${retry === "prepare" ? "
|
|
24101
|
+
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}`;
|
|
23774
24102
|
case 403:
|
|
23775
24103
|
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}`;
|
|
23776
24104
|
case 413:
|
|
@@ -23792,8 +24120,9 @@ function describeBundleReleaseError(err, uploadKey, retry = "prepare") {
|
|
|
23792
24120
|
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}`;
|
|
23793
24121
|
default:
|
|
23794
24122
|
if (err.status >= 500) {
|
|
23795
|
-
const what =
|
|
23796
|
-
|
|
24123
|
+
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})`;
|
|
24124
|
+
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.` : "";
|
|
24125
|
+
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}`;
|
|
23797
24126
|
}
|
|
23798
24127
|
return err.message;
|
|
23799
24128
|
}
|
|
@@ -23913,6 +24242,18 @@ function addUploadHeadlessWebsiteFilesTool(server, options) {
|
|
|
23913
24242
|
const { authStrategy, toolDescriptions } = options;
|
|
23914
24243
|
const clientName = () => options.clientName ?? server.clientName?.() ?? server.server?.getClientVersion?.()?.name;
|
|
23915
24244
|
const fetchImpl = options.fetchImpl ?? ((u, i) => fetch(u, i));
|
|
24245
|
+
const guideFetch = options.guideFetch ?? (async (httpClient) => {
|
|
24246
|
+
if (!httpClient) return void 0;
|
|
24247
|
+
return fetchArticleContent(
|
|
24248
|
+
httpClient,
|
|
24249
|
+
HEADLESS_GUIDE_URL,
|
|
24250
|
+
"article",
|
|
24251
|
+
true,
|
|
24252
|
+
options.guide?.skillsPortalUrl,
|
|
24253
|
+
options.guide?.repo,
|
|
24254
|
+
options.guide?.commitHash
|
|
24255
|
+
);
|
|
24256
|
+
});
|
|
23916
24257
|
const d = UPLOAD_HEADLESS_WEBSITE_FILES_PARAM_DESCRIPTIONS;
|
|
23917
24258
|
const registered = server.tool(
|
|
23918
24259
|
UPLOAD_HEADLESS_WEBSITE_FILES_TOOL_NAME,
|
|
@@ -24038,7 +24379,7 @@ function addUploadHeadlessWebsiteFilesTool(server, options) {
|
|
|
24038
24379
|
rethrowIfUnauthorized(err);
|
|
24039
24380
|
await authStrategy.enforceValidWixToken?.(err);
|
|
24040
24381
|
return errorResult2(
|
|
24041
|
-
uploadKey ? describeBundleReleaseError(err, uploadKey) : describeBundleUploadUrlError(err)
|
|
24382
|
+
uploadKey ? describeBundleReleaseError(err, uploadKey, "prepare", siteId) : describeBundleUploadUrlError(err, siteId)
|
|
24042
24383
|
);
|
|
24043
24384
|
}
|
|
24044
24385
|
}
|
|
@@ -24120,7 +24461,7 @@ function addUploadHeadlessWebsiteFilesTool(server, options) {
|
|
|
24120
24461
|
rethrowIfUnauthorized(err);
|
|
24121
24462
|
await authStrategy.enforceValidWixToken?.(err);
|
|
24122
24463
|
return errorResult2(
|
|
24123
|
-
stagedKey ? describeBundleReleaseError(err, stagedKey, "resend") : describeBundleUploadUrlError(err)
|
|
24464
|
+
stagedKey ? describeBundleReleaseError(err, stagedKey, "resend", siteId) : describeBundleUploadUrlError(err, siteId)
|
|
24124
24465
|
);
|
|
24125
24466
|
}
|
|
24126
24467
|
}
|
|
@@ -24144,6 +24485,14 @@ function addUploadHeadlessWebsiteFilesTool(server, options) {
|
|
|
24144
24485
|
siteId: metaSiteId,
|
|
24145
24486
|
upload: { path: "drop", files: siteFiles.length }
|
|
24146
24487
|
});
|
|
24488
|
+
let guide;
|
|
24489
|
+
try {
|
|
24490
|
+
guide = await guideFetch(extra?.httpClient);
|
|
24491
|
+
} catch (e) {
|
|
24492
|
+
logger.log(
|
|
24493
|
+
`[UploadHeadlessWebsiteFiles] guide fetch failed: ${e instanceof Error ? e.message : String(e)}`
|
|
24494
|
+
);
|
|
24495
|
+
}
|
|
24147
24496
|
return {
|
|
24148
24497
|
content: [
|
|
24149
24498
|
{
|
|
@@ -24153,7 +24502,7 @@ function addUploadHeadlessWebsiteFilesTool(server, options) {
|
|
|
24153
24502
|
siteUrl: dropped.siteUrl,
|
|
24154
24503
|
uploadId: dropped.uploadId,
|
|
24155
24504
|
files: siteFiles.map((f) => f.path)
|
|
24156
|
-
}) + (releaseNotes ? "\n\nRelease notes are kept for a Wix Astro build only; a static drop has none, so yours were not stored." : "")
|
|
24505
|
+
}) + (releaseNotes ? "\n\nRelease notes are kept for a Wix Astro build only; a static drop has none, so yours were not stored." : "") + formatInlineGuide(guide)
|
|
24157
24506
|
}
|
|
24158
24507
|
]
|
|
24159
24508
|
};
|
|
@@ -24162,7 +24511,7 @@ function addUploadHeadlessWebsiteFilesTool(server, options) {
|
|
|
24162
24511
|
logger.log(`[UploadHeadlessWebsiteFiles] Error: ${message}`);
|
|
24163
24512
|
rethrowIfUnauthorized(err);
|
|
24164
24513
|
await authStrategy.enforceValidWixToken?.(err);
|
|
24165
|
-
return errorResult2(message);
|
|
24514
|
+
return errorResult2(describeSiteAccessDenied(err, siteId) ?? message);
|
|
24166
24515
|
}
|
|
24167
24516
|
}
|
|
24168
24517
|
);
|
|
@@ -24471,7 +24820,11 @@ function addExecuteTool(server, options) {
|
|
|
24471
24820
|
}
|
|
24472
24821
|
let attachmentFiles = [];
|
|
24473
24822
|
try {
|
|
24474
|
-
attachmentFiles = await resolveAttachments(
|
|
24823
|
+
attachmentFiles = await resolveAttachments(
|
|
24824
|
+
attachments,
|
|
24825
|
+
void 0,
|
|
24826
|
+
WIX_AI_ASSISTANT_ATTACHMENT_HOSTS
|
|
24827
|
+
);
|
|
24475
24828
|
} catch (e) {
|
|
24476
24829
|
if (e instanceof AttachmentError) return executeErrorResult(e.message);
|
|
24477
24830
|
throw e;
|
|
@@ -24849,7 +25202,13 @@ function createWixToolkit(options) {
|
|
|
24849
25202
|
addUploadHeadlessWebsiteFilesTool(server, {
|
|
24850
25203
|
authStrategy,
|
|
24851
25204
|
toolDescriptions: effectiveToolDescriptions,
|
|
24852
|
-
clientName
|
|
25205
|
+
clientName,
|
|
25206
|
+
// The headless guide the drop reply carries is read the way the docs tools read it.
|
|
25207
|
+
guide: {
|
|
25208
|
+
skillsPortalUrl: docs?.skillsPortalUrl,
|
|
25209
|
+
repo: docs?.repo ?? docs?.skillsRepo,
|
|
25210
|
+
commitHash: docs?.commitHash ?? docs?.skillsPr
|
|
25211
|
+
}
|
|
24853
25212
|
});
|
|
24854
25213
|
}
|
|
24855
25214
|
const requestedGraphqlTools = GRAPHQL_TOOL_NAMES.filter(
|