@wix/mcp 1.0.94 → 1.0.95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +2 -2
  2. package/build/bin-standalone.js +1116 -321
  3. package/build/cjs/index.cjs +2813 -1147
  4. package/build/cjs/toolkit.cjs +1788 -362
  5. package/build/dts/api-call/headless-release.d.ts +56 -0
  6. package/build/dts/api-call/headless-release.js +135 -0
  7. package/build/dts/api-call/index.d.ts +9 -1
  8. package/build/dts/api-call/index.js +13 -7
  9. package/build/dts/api-call/site-files.d.ts +14 -0
  10. package/build/dts/api-call/site-files.js +106 -10
  11. package/build/dts/api-call/upload-headless-website-files.d.ts +88 -5
  12. package/build/dts/api-call/upload-headless-website-files.js +473 -20
  13. package/build/dts/api-call/upload-image-tool.d.ts +2 -0
  14. package/build/dts/api-call/upload-image-tool.js +8 -4
  15. package/build/dts/bin.d.ts +1 -1
  16. package/build/dts/bin.js +3 -1
  17. package/build/dts/code-mode/index.d.ts +6 -1
  18. package/build/dts/code-mode/index.js +30 -10
  19. package/build/dts/code-mode/truncate-response.js +5 -1
  20. package/build/dts/config/default-config.d.ts +35 -5
  21. package/build/dts/config/default-config.js +334 -178
  22. package/build/dts/config/file-params.d.ts +22 -0
  23. package/build/dts/config/file-params.js +24 -0
  24. package/build/dts/config/param-descriptions.d.ts +14 -2
  25. package/build/dts/config/param-descriptions.js +33 -14
  26. package/build/dts/constants.d.ts +1 -0
  27. package/build/dts/constants.js +1 -0
  28. package/build/dts/docs/docs.d.ts +12 -1
  29. package/build/dts/docs/docs.js +210 -42
  30. package/build/dts/docs/fetch-article.d.ts +21 -0
  31. package/build/dts/docs/fetch-article.js +66 -4
  32. package/build/dts/docs/get-to-know.d.ts +7 -2
  33. package/build/dts/docs/get-to-know.js +36 -14
  34. package/build/dts/docs/mintlify-docs.d.ts +121 -0
  35. package/build/dts/docs/mintlify-docs.js +391 -0
  36. package/build/dts/docs/semanticSearch.d.ts +2 -1
  37. package/build/dts/docs/semanticSearch.js +14 -7
  38. package/build/dts/docs/start-deploy-website.d.ts +7 -6
  39. package/build/dts/docs/start-deploy-website.js +13 -8
  40. package/build/dts/docs/start-headless-build.d.ts +8 -10
  41. package/build/dts/docs/start-headless-build.js +14 -28
  42. package/build/dts/index.d.ts +2 -2
  43. package/build/dts/index.js +2 -2
  44. package/build/dts/infra/redact-personal-data.d.ts +4 -0
  45. package/build/dts/infra/redact-personal-data.js +79 -0
  46. package/build/dts/site-widget-tools/constants.d.ts +49 -1
  47. package/build/dts/site-widget-tools/constants.js +64 -7
  48. package/build/dts/site-widget-tools/index.d.ts +1 -0
  49. package/build/dts/site-widget-tools/index.js +1 -0
  50. package/build/dts/site-widget-tools/pick-images-tool/index.d.ts +14 -0
  51. package/build/dts/site-widget-tools/pick-images-tool/index.js +138 -0
  52. package/build/dts/site-widget-tools/search-site-templates-tool/index.js +16 -2
  53. package/build/dts/site-widget-tools/site-builder-tool/index.js +22 -2
  54. package/build/dts/site-widget-tools/site-creation-tool/index.d.ts +7 -1
  55. package/build/dts/site-widget-tools/site-creation-tool/index.js +22 -51
  56. package/build/dts/site-widget-tools/suggested-domain-tool/index.js +6 -3
  57. package/build/dts/support/index.d.ts +4 -0
  58. package/build/dts/support/index.js +13 -7
  59. package/build/dts/toolkit-entry.d.cts +15 -10
  60. package/build/dts/toolkit-entry.d.ts +15 -10
  61. package/build/dts/toolkit.d.ts +2 -2
  62. package/build/dts/toolkit.js +14 -7
  63. package/build/dts/wix-mcp-server.d.ts +10 -1
  64. package/build/dts/wix-mcp-server.js +25 -7
  65. package/build/dts/wixel/constants.d.ts +8 -1
  66. package/build/dts/wixel/constants.js +8 -1
  67. package/build/dts/wixel/index.d.ts +1 -1
  68. package/build/dts/wixel/index.js +2 -2
  69. package/build/dts/wixel/wixel-a2a-client.d.ts +83 -0
  70. package/build/dts/wixel/wixel-a2a-client.js +166 -0
  71. package/build/dts/wixel/wixel-bi.d.ts +2 -1
  72. package/build/dts/wixel/wixel-bi.js +2 -1
  73. package/build/dts/wixel/{external-agent-api.d.ts → wixel-session-entries.d.ts} +11 -33
  74. package/build/dts/wixel/{external-agent-api.js → wixel-session-entries.js} +33 -54
  75. package/build/dts/wixel/wixel-session-tools.d.ts +21 -26
  76. package/build/dts/wixel/wixel-session-tools.js +494 -599
  77. package/build/esm/index.js +2824 -1170
  78. package/build/esm/toolkit.js +1786 -360
  79. package/package.json +4 -4
@@ -0,0 +1,56 @@
1
+ /**
2
+ * A Wix Astro build among the files UploadHeadlessWebsiteFiles received (the output
3
+ * of `astro build` with the Wix hosting adapter, zipped from the project root):
4
+ * recognised here, released by headless-business-setup's bundle routes (the tool
5
+ * PUTs the zip and calls `release-bundle`), not dropped as static files.
6
+ *
7
+ * The build is recognised by `.wix/build-metadata.json`, which the adapter writes
8
+ * beside `dist/`: it names the client folder (served as static files), the server
9
+ * folder (run on the hosting runtime) and the app manifest. The folder that holds
10
+ * `.wix/` is the project root; the project's other files there are its source.
11
+ *
12
+ * Runs in the MCP server (a Cloudflare Worker): no `node:` imports, only web APIs.
13
+ */
14
+ import type { SiteFile } from './site-files.js';
15
+ export declare const BUILD_METADATA_SUFFIX = ".wix/build-metadata.json";
16
+ export interface BuildMetadata {
17
+ appManifestPath: string;
18
+ clientDir?: string;
19
+ serverDir?: string;
20
+ staticsUrlPlaceholder?: string;
21
+ serverUrlPlaceholder?: string;
22
+ generateAppDeploymentGroupId?: boolean;
23
+ }
24
+ export interface HeadlessBuild {
25
+ metadata: BuildMetadata;
26
+ /** The project root in the upload: `''`, or the one folder that wraps the project (`proj/`). */
27
+ root: string;
28
+ /** The manifest as the build wrote it (`{ appId, components }`). */
29
+ manifestText: string;
30
+ clientFiles: SiteFile[];
31
+ serverFiles: SiteFile[];
32
+ /** The client folder in the upload (`dist/client/`), when the metadata names one. */
33
+ clientDir?: string;
34
+ }
35
+ /**
36
+ * The upload without the build's empty static files: the hosting refuses a static file
37
+ * of size 0 (`size got 0, expected 1 or more`), and an empty file serves nothing.
38
+ */
39
+ export declare function withoutEmptyStatics(files: SiteFile[], build: HeadlessBuild): {
40
+ files: SiteFile[];
41
+ dropped: string[];
42
+ };
43
+ /**
44
+ * Recognise a Wix Astro build among the uploaded files: the file set holds
45
+ * `.wix/build-metadata.json` (at the root, or under one folder that wraps the project).
46
+ * Returns null for a static site, so the caller drops it as before. Throws an agent-facing
47
+ * error for a build whose metadata or files are not usable.
48
+ */
49
+ export declare function findHeadlessBuild(files: SiteFile[]): HeadlessBuild | null;
50
+ /**
51
+ * The project's source among the uploaded files: every file under the build's
52
+ * project root outside `dist/`, `.wix/`, `.astro/`, `.agents/`, `node_modules/` and
53
+ * `.env*`, with paths relative to the root. Empty when only the build was uploaded.
54
+ */
55
+ export declare function buildSourceFiles(files: SiteFile[], build: HeadlessBuild): SiteFile[];
56
+ //# sourceMappingURL=headless-release.d.ts.map
@@ -0,0 +1,135 @@
1
+ export const BUILD_METADATA_SUFFIX = '.wix/build-metadata.json';
2
+ /**
3
+ * The upload without the build's empty static files: the hosting refuses a static file
4
+ * of size 0 (`size got 0, expected 1 or more`), and an empty file serves nothing.
5
+ */
6
+ export function withoutEmptyStatics(files, build) {
7
+ const dir = build.clientDir;
8
+ if (dir === undefined)
9
+ return { files, dropped: [] };
10
+ const dropped = [];
11
+ const kept = files.filter((f) => {
12
+ const path = normalizePath(f.path);
13
+ if (f.bytes.byteLength === 0 && path.startsWith(dir)) {
14
+ dropped.push(path.slice(dir.length));
15
+ return false;
16
+ }
17
+ return true;
18
+ });
19
+ return { files: kept, dropped };
20
+ }
21
+ const normalizePath = (p) => p.replace(/\\/g, '/').replace(/^\.?\//, '');
22
+ /**
23
+ * Recognise a Wix Astro build among the uploaded files: the file set holds
24
+ * `.wix/build-metadata.json` (at the root, or under one folder that wraps the project).
25
+ * Returns null for a static site, so the caller drops it as before. Throws an agent-facing
26
+ * error for a build whose metadata or files are not usable.
27
+ */
28
+ export function findHeadlessBuild(files) {
29
+ const entries = new Map(files.map((f) => [normalizePath(f.path), f.bytes]));
30
+ const metadataEntry = [...entries.keys()].find((name) => name === BUILD_METADATA_SUFFIX ||
31
+ name.endsWith(`/${BUILD_METADATA_SUFFIX}`));
32
+ if (metadataEntry === undefined)
33
+ return null;
34
+ const zipRoot = metadataEntry.slice(0, metadataEntry.length - BUILD_METADATA_SUFFIX.length);
35
+ let metadata;
36
+ try {
37
+ metadata = JSON.parse(new TextDecoder().decode(entries.get(metadataEntry)));
38
+ }
39
+ catch {
40
+ throw new Error(`${BUILD_METADATA_SUFFIX} is not valid JSON.`);
41
+ }
42
+ if (typeof metadata.appManifestPath !== 'string') {
43
+ throw new Error(`${BUILD_METADATA_SUFFIX} has no appManifestPath; send the build as the Wix hosting adapter wrote it (.wix/ and dist/ zipped from the project root).`);
44
+ }
45
+ // The metadata holds the builder's absolute paths; the files in the zip are relative to the
46
+ // project root. The manifest is found by the longest tail of its path that exists, and the
47
+ // part of the path before that tail is the builder's project root.
48
+ const manifestPath = resolveZipPath(metadata.appManifestPath, zipRoot, entries);
49
+ if (manifestPath === undefined) {
50
+ throw new Error(`The build's app manifest (${metadata.appManifestPath}) is not in the upload; zip .wix/ and dist/ together from the project root after \`astro build\`.`);
51
+ }
52
+ const builderRoot = metadata.appManifestPath.slice(0, metadata.appManifestPath.length - (manifestPath.length - zipRoot.length));
53
+ const clientDir = metadata.clientDir === undefined
54
+ ? undefined
55
+ : toZipDir(metadata.clientDir, builderRoot, zipRoot);
56
+ const serverDir = metadata.serverDir === undefined
57
+ ? undefined
58
+ : toZipDir(metadata.serverDir, builderRoot, zipRoot);
59
+ const clientFiles = [];
60
+ const serverFiles = [];
61
+ for (const [name, bytes] of entries) {
62
+ if (name === manifestPath)
63
+ continue;
64
+ if (serverDir !== undefined && name.startsWith(serverDir)) {
65
+ serverFiles.push({ path: name.slice(serverDir.length), bytes });
66
+ }
67
+ else if (clientDir !== undefined && name.startsWith(clientDir)) {
68
+ clientFiles.push({ path: name.slice(clientDir.length), bytes });
69
+ }
70
+ }
71
+ if (clientFiles.length + serverFiles.length === 0) {
72
+ throw new Error('The build has no client or server output files; run `astro build` first and zip .wix/ and dist/ from the project root.');
73
+ }
74
+ if (serverFiles.length > 0 &&
75
+ !serverFiles.some((f) => f.path === 'entry.mjs')) {
76
+ throw new Error("The server build has no entry.mjs, which the Wix hosting adapter (@wix/astro-wix-hosting-adapter) writes into the server folder on every build. Build with the project's own `astro build` (the adapter in astro.config) and zip .wix/ and dist/ from the project root as the build left them.");
77
+ }
78
+ const manifestText = new TextDecoder().decode(entries.get(manifestPath));
79
+ return {
80
+ metadata,
81
+ root: zipRoot,
82
+ manifestText,
83
+ clientFiles,
84
+ serverFiles,
85
+ clientDir
86
+ };
87
+ }
88
+ // What the source zip leaves out, relative to the project root: the build output, the
89
+ // adapter's and Astro's caches, the agent's own folder at the top; dependencies and
90
+ // environment files at any depth.
91
+ const NOT_SOURCE_TOP = new Set(['dist', '.wix', '.astro', '.agents']);
92
+ const isSourcePath = (relative) => {
93
+ const segments = relative.split('/');
94
+ return (!NOT_SOURCE_TOP.has(segments[0]) &&
95
+ !segments.some((s) => s === 'node_modules' || s.startsWith('.env')));
96
+ };
97
+ /**
98
+ * The project's source among the uploaded files: every file under the build's
99
+ * project root outside `dist/`, `.wix/`, `.astro/`, `.agents/`, `node_modules/` and
100
+ * `.env*`, with paths relative to the root. Empty when only the build was uploaded.
101
+ */
102
+ export function buildSourceFiles(files, build) {
103
+ return files.flatMap((f) => {
104
+ const path = normalizePath(f.path);
105
+ if (!path.startsWith(build.root))
106
+ return [];
107
+ const relative = path.slice(build.root.length);
108
+ return relative && isSourcePath(relative)
109
+ ? [{ path: relative, bytes: f.bytes }]
110
+ : [];
111
+ });
112
+ }
113
+ function resolveZipPath(builderPath, zipRoot, entries) {
114
+ const segments = normalizePath(builderPath)
115
+ .split('/')
116
+ .filter((s) => s.length > 0);
117
+ for (let k = 0; k < segments.length; k++) {
118
+ const candidate = zipRoot + segments.slice(k).join('/');
119
+ if (entries.has(candidate))
120
+ return candidate;
121
+ }
122
+ return undefined;
123
+ }
124
+ function toZipDir(builderDir, builderRoot, zipRoot) {
125
+ const normalized = normalizePath(builderDir);
126
+ const normalizedRoot = normalizePath(builderRoot);
127
+ const relative = builderDir.startsWith(builderRoot)
128
+ ? builderDir.slice(builderRoot.length)
129
+ : normalized.startsWith(normalizedRoot)
130
+ ? normalized.slice(normalizedRoot.length)
131
+ : normalized;
132
+ const dir = zipRoot + normalizePath(relative);
133
+ return dir.endsWith('/') ? dir : `${dir}/`;
134
+ }
135
+ //# sourceMappingURL=headless-release.js.map
@@ -6,5 +6,13 @@ export interface ToolHintsConfig {
6
6
  openWorldHint?: boolean;
7
7
  }
8
8
  export { addUploadImageTool } from './upload-image-tool.js';
9
- export declare function addApiCallTool(server: WixMcpServer, strategy: McpAuthenticationStrategy, allowedTools?: string[], disableTools?: string[], enableTools?: string[], toolDescriptions?: Record<string, string>, toolHints?: Record<string, ToolHintsConfig>, maxResponseSize?: number): void;
9
+ export declare function addApiCallTool(server: WixMcpServer, strategy: McpAuthenticationStrategy, allowedTools?: string[], disableTools?: string[], enableTools?: string[], toolDescriptions?: Record<string, string>, toolHints?: Record<string, ToolHintsConfig>, maxResponseSize?: number, options?: {
10
+ imagePickerToolName?: string;
11
+ /**
12
+ * This server serves ExecuteWixAPI. Only then does CallWixSiteAPI tell the user to reconnect when
13
+ * the client lists no ExecuteWixAPI (an old or cached tool list); where it is off on purpose, the
14
+ * agent just uses CallWixSiteAPI.
15
+ */
16
+ executeWixAPIServed?: boolean;
17
+ }): void;
10
18
  //# sourceMappingURL=index.d.ts.map
@@ -5,8 +5,8 @@ import { handleWixAPIResponse } from '../tool-utils.js';
5
5
  import { rethrowIfUnauthorized } from '../auth/index.js';
6
6
  import { isApiUrlAllowed } from './validation.js';
7
7
  import { addUploadImageTool } from './upload-image-tool.js';
8
- import { defaultToolDescriptions } from '../config/default-config.js';
9
- import { desc } from '../config/param-descriptions.js';
8
+ import { CALL_WIX_SITE_API_NO_EXECUTE_LINE, CALL_WIX_SITE_API_RECONNECT_LINE, defaultToolDescriptions } from '../config/default-config.js';
9
+ import { desc, WHY_NOT_EXECUTE_NO_EXECUTE, WHY_NOT_EXECUTE_RECONNECT } from '../config/param-descriptions.js';
10
10
  import { MAX_ALLOWED_RESPONSE_CHARS } from '../infra/response-size-limits.js';
11
11
  export { addUploadImageTool } from './upload-image-tool.js';
12
12
  const DEFAULT_MAX_RESPONSE_SIZE = 50000; // ~50KB cap
@@ -56,7 +56,8 @@ function formatApiResponse(responseData, maxResponseSize = DEFAULT_MAX_RESPONSE_
56
56
  }
57
57
  return { text: responseText, isError: false, originalLength, returnedRows };
58
58
  }
59
- export function addApiCallTool(server, strategy, allowedTools = ['CallWixSiteAPI', 'ListWixSites', 'ManageWixSite'], disableTools = [], enableTools = [], toolDescriptions, toolHints, maxResponseSize) {
59
+ export function addApiCallTool(server, strategy, allowedTools = ['CallWixSiteAPI', 'ListWixSites', 'ManageWixSite'], disableTools = [], enableTools = [], toolDescriptions, toolHints, maxResponseSize, options) {
60
+ const executeAdvice = (text, reconnect, plain) => options?.executeWixAPIServed ? text : text.replace(reconnect, plain);
60
61
  // Compute effective allowed tools: (default + enableTools) - disableTools
61
62
  const effectiveAllowedTools = [
62
63
  ...new Set([...allowedTools, ...enableTools])
@@ -71,7 +72,7 @@ export function addApiCallTool(server, strategy, allowedTools = ['CallWixSiteAPI
71
72
  const getDescription = (toolName, defaultDesc) => toolDescriptions?.[toolName] ?? defaultDesc;
72
73
  if (effectiveAllowedTools.includes('CallWixSiteAPI') &&
73
74
  !disableTools.includes('CallWixSiteAPI')) {
74
- server.tool('CallWixSiteAPI', getDescription('CallWixSiteAPI', defaultToolDescriptions.CallWixSiteAPI), (() => {
75
+ server.tool('CallWixSiteAPI', executeAdvice(getDescription('CallWixSiteAPI', defaultToolDescriptions.CallWixSiteAPI), CALL_WIX_SITE_API_RECONNECT_LINE, CALL_WIX_SITE_API_NO_EXECUTE_LINE), (() => {
75
76
  const d = desc('CallWixSiteAPI');
76
77
  return {
77
78
  siteId: z.string().describe(d.siteId),
@@ -82,6 +83,10 @@ export function addApiCallTool(server, strategy, allowedTools = ['CallWixSiteAPI
82
83
  .optional()
83
84
  .describe(d.body),
84
85
  reason: z.string().describe(d.reason),
86
+ whyNotExecuteWixAPI: z
87
+ .string()
88
+ .optional()
89
+ .describe(executeAdvice(d.whyNotExecuteWixAPI, WHY_NOT_EXECUTE_RECONNECT, WHY_NOT_EXECUTE_NO_EXECUTE)),
85
90
  sourceDocUrl: z.string().describe(d.sourceDocUrl)
86
91
  };
87
92
  })(),
@@ -91,8 +96,8 @@ export function addApiCallTool(server, strategy, allowedTools = ['CallWixSiteAPI
91
96
  readOnlyHint: false,
92
97
  destructiveHint: true,
93
98
  openWorldHint: true
94
- }), async ({ url, body, method, siteId, reason, sourceDocUrl }, { httpClient, setToolBiParams }) => {
95
- logger.log(`[CallWixSiteAPI] siteId=${siteId}, method=${method}, url=${url}, sourceDocUrl=${sourceDocUrl}, reason="${reason}", body=${JSON.stringify(body)}`);
99
+ }), async ({ url, body, method, siteId, reason, whyNotExecuteWixAPI, sourceDocUrl }, { httpClient, setToolBiParams }) => {
100
+ logger.log(`[CallWixSiteAPI] siteId=${siteId}, method=${method}, url=${url}, sourceDocUrl=${sourceDocUrl}, reason="${reason}", whyNotExecuteWixAPI="${whyNotExecuteWixAPI ?? ''}", body=${JSON.stringify(body)}`);
96
101
  const requestHeaders = {
97
102
  // Ask the Wix API edge for JSON error bodies (it serves a branded HTML
98
103
  // error page on 4xx otherwise); */* keeps non-JSON responses working.
@@ -328,7 +333,8 @@ export function addApiCallTool(server, strategy, allowedTools = ['CallWixSiteAPI
328
333
  !disableTools.includes('UploadImageToWixSite')) {
329
334
  addUploadImageTool(server, strategy, {
330
335
  description: getDescription('UploadImageToWixSite', ''),
331
- hints: toolHints?.['UploadImageToWixSite']
336
+ hints: toolHints?.['UploadImageToWixSite'],
337
+ imagePickerToolName: options?.imagePickerToolName
332
338
  });
333
339
  }
334
340
  }
@@ -16,12 +16,26 @@ export declare function base64ToBytes(b64: string): Uint8Array;
16
16
  export declare function parseFileBundle(bundle: string | undefined): SiteFile[];
17
17
  /** Unzip (stored + deflate entries) into raw bytes. Validates the EOCD and the declared sizes. */
18
18
  export declare function unzip(z: Uint8Array): Promise<SiteFile[]>;
19
+ export declare function crc32(bytes: Uint8Array): number;
20
+ /** A zip of the files, every entry stored (no compression), names in UTF-8. No zip64: under 4 GB and 65,535 entries. */
21
+ export declare function zipStored(files: SiteFile[]): Uint8Array;
19
22
  export declare const isZip: (f: SiteFile) => boolean;
23
+ /**
24
+ * The file set as uploaded: zips unpacked, paths cleaned, junk dropped, within the file
25
+ * count and the byte budget for the whole upload. No site-shape rule yet: a Wix Astro
26
+ * build is told apart from a static site on this list.
27
+ */
28
+ export declare function expandSiteFiles(input: SiteFile[]): Promise<SiteFile[]>;
20
29
  /**
21
30
  * Turn the raw inputs into the file set to drop: unpack zips, drop junk, strip a
22
31
  * single wrapping folder, require a root index.html. Throws an agent-facing error.
23
32
  */
24
33
  export declare function normalizeSiteFiles(input: SiteFile[]): Promise<SiteFile[]>;
34
+ /**
35
+ * The static-site rules on a list `expandSiteFiles` already made (its zips are not
36
+ * unpacked again): strip a single wrapping folder, require a root index.html.
37
+ */
38
+ export declare function staticSiteFiles(expanded: SiteFile[]): SiteFile[];
25
39
  export declare function guessContentType(path: string): string;
26
40
  /** The drop endpoint's body: one `files` part per file, the path as the filename. */
27
41
  export declare function buildFormData(files: SiteFile[]): FormData;
@@ -58,7 +58,11 @@ export async function unzip(z) {
58
58
  let p = u32(end + 16);
59
59
  let total = 0;
60
60
  const decoder = new TextDecoder();
61
- for (let i = 0, n = u16(end + 10); i < n; i++) {
61
+ const n = u16(end + 10);
62
+ if (n > MAX_SITE_FILES) {
63
+ throw new Error(`zip holds ${n} entries; a site upload is at most ${MAX_SITE_FILES} files`);
64
+ }
65
+ for (let i = 0; i < n; i++) {
62
66
  const path = decoder.decode(z.slice(p + 46, p + 46 + u16(p + 28)));
63
67
  const method = u16(p + 10);
64
68
  const size = u32(p + 24);
@@ -89,6 +93,77 @@ export async function unzip(z) {
89
93
  }
90
94
  return out;
91
95
  }
96
+ const CRC_TABLE = (() => {
97
+ const t = new Uint32Array(256);
98
+ for (let n = 0; n < 256; n++) {
99
+ let c = n;
100
+ for (let k = 0; k < 8; k++)
101
+ c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
102
+ t[n] = c >>> 0;
103
+ }
104
+ return t;
105
+ })();
106
+ export function crc32(bytes) {
107
+ let c = 0xffffffff;
108
+ for (let i = 0; i < bytes.length; i++)
109
+ c = CRC_TABLE[(c ^ bytes[i]) & 0xff] ^ (c >>> 8);
110
+ return (c ^ 0xffffffff) >>> 0;
111
+ }
112
+ /** A zip of the files, every entry stored (no compression), names in UTF-8. No zip64: under 4 GB and 65,535 entries. */
113
+ export function zipStored(files) {
114
+ const encoder = new TextEncoder();
115
+ const entries = files.map((f) => ({
116
+ name: encoder.encode(f.path),
117
+ bytes: f.bytes,
118
+ crc: crc32(f.bytes)
119
+ }));
120
+ const localSize = entries.reduce((sum, e) => sum + 30 + e.name.length + e.bytes.length, 0);
121
+ const centralSize = entries.reduce((sum, e) => sum + 46 + e.name.length, 0);
122
+ const out = new Uint8Array(localSize + centralSize + 22);
123
+ const view = new DataView(out.buffer);
124
+ const UTF8_FLAG = 0x0800;
125
+ let p = 0;
126
+ const offsets = [];
127
+ for (const e of entries) {
128
+ offsets.push(p);
129
+ view.setUint32(p, 0x04034b50, true);
130
+ view.setUint16(p + 4, 20, true); // version needed
131
+ view.setUint16(p + 6, UTF8_FLAG, true);
132
+ view.setUint16(p + 8, 0, true); // stored
133
+ view.setUint32(p + 10, 0, true); // time, date
134
+ view.setUint32(p + 14, e.crc, true);
135
+ view.setUint32(p + 18, e.bytes.length, true);
136
+ view.setUint32(p + 22, e.bytes.length, true);
137
+ view.setUint16(p + 26, e.name.length, true);
138
+ view.setUint16(p + 28, 0, true);
139
+ out.set(e.name, p + 30);
140
+ out.set(e.bytes, p + 30 + e.name.length);
141
+ p += 30 + e.name.length + e.bytes.length;
142
+ }
143
+ const centralStart = p;
144
+ entries.forEach((e, i) => {
145
+ view.setUint32(p, 0x02014b50, true);
146
+ view.setUint16(p + 4, 20, true); // version made by
147
+ view.setUint16(p + 6, 20, true); // version needed
148
+ view.setUint16(p + 8, UTF8_FLAG, true);
149
+ view.setUint16(p + 10, 0, true);
150
+ view.setUint32(p + 12, 0, true);
151
+ view.setUint32(p + 16, e.crc, true);
152
+ view.setUint32(p + 20, e.bytes.length, true);
153
+ view.setUint32(p + 24, e.bytes.length, true);
154
+ view.setUint16(p + 28, e.name.length, true);
155
+ // extra, comment, disk, internal attrs, external attrs: zero
156
+ view.setUint32(p + 42, offsets[i], true);
157
+ out.set(e.name, p + 46);
158
+ p += 46 + e.name.length;
159
+ });
160
+ view.setUint32(p, 0x06054b50, true);
161
+ view.setUint16(p + 8, entries.length, true);
162
+ view.setUint16(p + 10, entries.length, true);
163
+ view.setUint32(p + 12, p - centralStart, true);
164
+ view.setUint32(p + 16, centralStart, true);
165
+ return out;
166
+ }
92
167
  // A zip is recognised by its bytes (local-file-header or empty-archive signature), not its name:
93
168
  // an attachment can arrive without a file name, and a zip's path then carries no extension.
94
169
  export const isZip = (f) => (f.bytes.length >= 4 &&
@@ -99,18 +174,25 @@ export const isZip = (f) => (f.bytes.length >= 4 &&
99
174
  /\.zip$/i.test(f.path);
100
175
  const isRootIndex = (p) => /^index\.html?$/i.test(p);
101
176
  /**
102
- * Turn the raw inputs into the file set to drop: unpack zips, drop junk, strip a
103
- * single wrapping folder, require a root index.html. Throws an agent-facing error.
177
+ * The file set as uploaded: zips unpacked, paths cleaned, junk dropped, within the file
178
+ * count and the byte budget for the whole upload. No site-shape rule yet: a Wix Astro
179
+ * build is told apart from a static site on this list.
104
180
  */
105
- export async function normalizeSiteFiles(input) {
106
- let list = [];
181
+ export async function expandSiteFiles(input) {
182
+ const list = [];
183
+ let bytes = 0;
107
184
  for (const f of input) {
108
- if (isZip(f))
109
- list.push(...(await unzip(f.bytes)));
110
- else
111
- list.push(f);
185
+ const added = isZip(f) ? await unzip(f.bytes) : [f];
186
+ list.push(...added);
187
+ bytes += added.reduce((sum, a) => sum + a.bytes.byteLength, 0);
188
+ if (list.length > MAX_SITE_FILES) {
189
+ throw new Error(`Too many files (more than ${MAX_SITE_FILES}); a site upload is at most ${MAX_SITE_FILES}.`);
190
+ }
191
+ if (bytes > MAX_UNZIP_BYTES) {
192
+ throw new Error(`The upload expands past ${MAX_UNZIP_BYTES} bytes; send only the site's files.`);
193
+ }
112
194
  }
113
- list = list
195
+ return list
114
196
  .map((f) => ({
115
197
  ...f,
116
198
  path: f.path.replace(/\\/g, '/').replace(/^\.?\//, '')
@@ -119,6 +201,20 @@ export async function normalizeSiteFiles(input) {
119
201
  !f.path.startsWith('__MACOSX/') &&
120
202
  !/(^|\/)\.DS_Store$/.test(f.path) &&
121
203
  !f.path.split('/').includes('..'));
204
+ }
205
+ /**
206
+ * Turn the raw inputs into the file set to drop: unpack zips, drop junk, strip a
207
+ * single wrapping folder, require a root index.html. Throws an agent-facing error.
208
+ */
209
+ export async function normalizeSiteFiles(input) {
210
+ return staticSiteFiles(await expandSiteFiles(input));
211
+ }
212
+ /**
213
+ * The static-site rules on a list `expandSiteFiles` already made (its zips are not
214
+ * unpacked again): strip a single wrapping folder, require a root index.html.
215
+ */
216
+ export function staticSiteFiles(expanded) {
217
+ let list = expanded;
122
218
  if (list.length === 0) {
123
219
  throw new Error('No files to upload: pass the site in `files` or `attachments`.');
124
220
  }
@@ -1,6 +1,9 @@
1
+ import { type SiteFile } from './site-files.js';
1
2
  import { WixMcpServer } from '../wix-mcp-server.js';
2
3
  import { type McpAuthenticationStrategy } from '../auth/index.js';
3
4
  export declare const UPLOAD_HEADLESS_WEBSITE_FILES_TOOL_NAME = "UploadHeadlessWebsiteFiles";
5
+ /** The largest site zip `bundle-upload-url` presigns (headless-business-setup's MAX_BUNDLE_BYTES). */
6
+ export declare const MAX_BUNDLE_BYTES: number;
4
7
  /**
5
8
  * Attribution the drop endpoint stamps on its upload BI event (`source` from
6
9
  * `campaign`, `agent` from `agent`, both ≤ 64 chars) — the same parser as the
@@ -17,12 +20,18 @@ export interface UploadHeadlessWebsiteFilesOptions {
17
20
  clientName?: string;
18
21
  }
19
22
  export declare const UPLOAD_HEADLESS_WEBSITE_FILES_PARAM_DESCRIPTIONS: {
20
- readonly files: "The site files as text, so file bytes never pass through a code string. Sections delimited by a line `=== FILE: <path> ===` (text, raw) or `=== FILE: <path> base64 ===` (binary, base64); `<path>` is relative to the site root and must include `index.html`. Use base64 for small binaries (icons, logos, fonts); large images/video belong behind a URL in the HTML, not in the bundle. Optional when `attachments` carries the files.";
21
- readonly attachments: "ChatGPT only. Files attached in the chat or created in your sandbox — any type: a zip of the whole site, HTML, CSS, JS, images. Pass each file as you have it, its file id or its /mnt/data path, and ChatGPT swaps it for { download_url, file_id, file_name?, mime_type? } before the call reaches Wix. No other client does this swap, so outside ChatGPT a path or file:// URL here fails at once; leave this out and use `files`, or, with a shell that reaches the internet, drop the files with curl as the StartDeployWebsite recipe shows. A zip is unpacked; a top-level folder wrapping the site is stripped. Prefer this over base64 in `files` whenever you have a file handle. Up to 10 MB per call.";
22
- readonly siteId: "The metaSiteId of the Wix Headless site to upload into: the one returned by the provision call of the StartDeployWebsite recipe for a new site, or of a site deployed earlier to replace its files. Every upload replaces the whole file set, so send all files; the URL stays the same.";
23
+ readonly files: "The site files as text, so file bytes never pass through a code string. Sections delimited by a line `=== FILE: <path> ===` (text, raw) or `=== FILE: <path> base64 ===` (binary, base64); `<path>` is relative to the site root; a static site must include `index.html`. Use base64 for small binaries (icons, logos, fonts); large images/video belong behind a URL in the HTML, not in the bundle. Optional when `attachments` carries the files. A Wix Astro build (`.wix/build-metadata.json` and `dist/` as `astro build` with the Wix hosting adapter wrote them, zipped from the project root) needs no `index.html`: it is released to the site's Wix hosting instead of dropped, and the project's other files in the upload are stored as its source when the site's repository accepts it.";
24
+ readonly attachments: string;
25
+ readonly prepare: "Phase one of the two-call upload of a Wix Astro build, for a shell that reaches the internet and no file handle (a sandbox whose network is limited to a few hosts cannot upload to the URLs): zip the build first, then `true` with its `size` returns presigned upload URLs for the build zip and, when the site's repository takes it, the project source zip, as `curl` commands to run verbatim, with the exact second call. The two calls: {siteId, prepare: true, size}, then {siteId, uploadKey, sourceUploadId?, expectedHeadRevisionId?, releaseNotes?}. No `files`, `attachments`, `uploadKey` or `sourceUploadId` on the first; `expectedHeadRevisionId` and `releaseNotes` given on the first are echoed into the second. Not for a static site: send its files in `files` or `attachments`, or drop them as the StartDeployWebsite recipe shows.";
26
+ readonly size: "Phase one, with `prepare: true` only, and required there: the build zip's length in bytes (`wc -c < build.zip`), 1 to 104857600 (100 MB). The upload URL takes a body of exactly this length; a zip changed after the prepare needs a new prepare.";
27
+ readonly uploadKey: "Phase two of the two-call upload: the `uploadKey` the `prepare` call returned, after the build zip was PUT to its URL. Releases that build to the site: {siteId, uploadKey, sourceUploadId?, expectedHeadRevisionId?, releaseNotes?}. Excludes `files` and `attachments`.";
28
+ readonly sourceUploadId: "Phase two: the `sourceUploadId` the `prepare` call returned, after the source zip was PUT to its URL. Stores the project source with the release. Needs `uploadKey`; leave it out when the prepare returned no source upload.";
29
+ readonly expectedHeadRevisionId: "The repository revision your project started from, so the source replaces only that and a commit made since (another chat, a Dev Machine) is never dropped unseen: the revision you downloaded the project at; for a project started here, the `headRevisionId` the `prepare` call returned. Leave it out only for a repository with nothing in it yet. Goes with `uploadKey`, or with `files`/`attachments` holding a build.";
30
+ readonly releaseNotes: "Optional: one line on what this release changes, at most 200 characters, kept with the deployment of a Wix Astro build, whether it comes in `files`/`attachments` or as the second call with `uploadKey` (given on the `prepare` call it is echoed into the second). A static drop keeps no notes.";
31
+ readonly siteId: "The metaSiteId of the Wix Headless site to upload into: the one returned by the provision call of the StartDeployWebsite recipe for a new site, or of a site deployed earlier to replace its files. Every upload replaces the whole file set, so send all files; the URL stays the same. A build released here replaces static pages dropped earlier, at the same URL.";
23
32
  readonly calledStartDeployWebsite: "Whether you called StartDeployWebsite earlier in this conversation and are following the recipe it returned. Answer truthfully; it is recorded, not checked.";
24
- readonly agentIdentity: "Which agent or product you are and the harness you run in.";
25
- readonly reason: "One sentence: the user request that led here and what is being published.";
33
+ readonly agentIdentity: "Which agent or product you are, which model if you know it, and the harness you run in. No personal data: no names, emails, phones, passwords, keys or local paths.";
34
+ readonly reason: "One sentence: the user request that led here and what is being published. No personal data: no names, emails, phones, passwords, keys or local paths.";
26
35
  };
27
36
  export interface DropResult {
28
37
  metaSiteId: string;
@@ -31,5 +40,79 @@ export interface DropResult {
31
40
  files: string[];
32
41
  }
33
42
  export declare function formatDropResult(r: DropResult): string;
43
+ /** One presigned PUT: where the zip goes and the headers the storage requires on it. */
44
+ interface PresignedPut {
45
+ putUrl: string;
46
+ headers?: Record<string, string>;
47
+ expiresAt?: string;
48
+ }
49
+ /** `POST /{siteId}/bundle-upload-url`: the site half, plus the Code Store half when the site has a source repository. */
50
+ export interface BundleUploadUrls extends PresignedPut {
51
+ uploadKey: string;
52
+ source?: PresignedPut & {
53
+ uploadId: string;
54
+ headRevisionId?: string;
55
+ };
56
+ }
57
+ /** `POST /{siteId}/release-bundle`. */
58
+ /**
59
+ * What the tool BI event records about an upload, beside the result text: the path it
60
+ * took (`drop` static pages, `build` an attached Astro build over the bundle routes,
61
+ * `prepare` and `release-bundle` the two calls) and whether the source was stored.
62
+ */
63
+ export interface UploadBiFields {
64
+ path: 'drop' | 'build' | 'prepare' | 'release-bundle';
65
+ sourceOffered?: boolean;
66
+ sourceStored?: boolean;
67
+ sourceFiles?: number;
68
+ emptyStaticsDropped?: number;
69
+ files?: number;
70
+ }
71
+ export interface BundleReleaseResult {
72
+ deploymentId?: string;
73
+ siteUrl?: string;
74
+ revision?: {
75
+ id?: string;
76
+ };
77
+ }
78
+ /** The prepare reply: what the agent runs, verbatim, and the exact second call. */
79
+ export declare function formatPrepareResult(siteId: string, urls: BundleUploadUrls, size?: number, given?: {
80
+ expectedHeadRevisionId?: string;
81
+ releaseNotes?: string;
82
+ }): string;
83
+ /** The release-bundle reply, for both the two-call form and a build uploaded as files. */
84
+ export declare function formatBundleReleaseResult(siteId: string, r: BundleReleaseResult, notes?: string[]): string;
85
+ /**
86
+ * How the agent starts over: in the two-call form a new `prepare`, after a build
87
+ * uploaded as files the same call again (the tool asks for new URLs itself).
88
+ */
89
+ export type BundleRetry = 'prepare' | 'resend';
90
+ /** The bundle-upload-url route's errors, as the agent should act on them. */
91
+ export declare function describeBundleUploadUrlError(err: unknown): string;
92
+ /** The deployment label the service keeps is 200 characters; longer notes are cut, not refused. */
93
+ export declare const RELEASE_NOTES_MAX_CHARS = 200;
94
+ export declare function releaseNotesForService(notes: string): string;
95
+ /** The release-bundle route's errors, as the agent should act on them. */
96
+ export declare function describeBundleReleaseError(err: unknown, uploadKey: string, retry?: BundleRetry): string;
97
+ /**
98
+ * A build that came as files, released over the bundle routes: an upload URL for the
99
+ * site zip's size, the zip PUT there, the source zip PUT to the repository's URL when
100
+ * the service offered one and the upload holds source files, then the release with
101
+ * the ids. `onStaged` hears the uploadKey once the site zip is staged.
102
+ */
103
+ export declare function releaseBuildByUrl(o: {
104
+ siteId: string;
105
+ siteZip: Uint8Array;
106
+ sourceFiles: SiteFile[];
107
+ siteHeaders: Record<string, string>;
108
+ fetchImpl: typeof fetch;
109
+ /** The revision the agent's project started from; never the head read here, which would replace work committed since. */
110
+ expectedHeadRevisionId?: string;
111
+ /** One line kept with the deployment; cut to the service's limit. */
112
+ releaseNotes?: string;
113
+ onStaged?: (uploadKey: string) => void;
114
+ log?: (line: string) => void;
115
+ }): Promise<BundleReleaseResult>;
34
116
  export declare function addUploadHeadlessWebsiteFilesTool(server: WixMcpServer, options: UploadHeadlessWebsiteFilesOptions): void;
117
+ export {};
35
118
  //# sourceMappingURL=upload-headless-website-files.d.ts.map