@wix/mcp 1.0.94 → 1.0.96

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +2 -2
  2. package/build/bin-standalone.js +1457 -332
  3. package/build/cjs/index.cjs +3379 -1333
  4. package/build/cjs/toolkit.cjs +2158 -373
  5. package/build/dts/api-call/headless-release.d.ts +56 -0
  6. package/build/dts/api-call/headless-release.js +135 -0
  7. package/build/dts/api-call/index.d.ts +9 -1
  8. package/build/dts/api-call/index.js +36 -8
  9. package/build/dts/api-call/site-files.d.ts +14 -0
  10. package/build/dts/api-call/site-files.js +106 -10
  11. package/build/dts/api-call/upload-headless-website-files.d.ts +117 -5
  12. package/build/dts/api-call/upload-headless-website-files.js +565 -20
  13. package/build/dts/api-call/upload-image-tool.d.ts +2 -0
  14. package/build/dts/api-call/upload-image-tool.js +8 -4
  15. package/build/dts/bin.d.ts +1 -1
  16. package/build/dts/bin.js +3 -1
  17. package/build/dts/code-mode/attachments.d.ts +9 -6
  18. package/build/dts/code-mode/attachments.js +23 -16
  19. package/build/dts/code-mode/index.d.ts +6 -1
  20. package/build/dts/code-mode/index.js +32 -12
  21. package/build/dts/code-mode/truncate-response.js +5 -1
  22. package/build/dts/config/default-config.d.ts +35 -5
  23. package/build/dts/config/default-config.js +339 -182
  24. package/build/dts/config/file-params.d.ts +22 -0
  25. package/build/dts/config/file-params.js +24 -0
  26. package/build/dts/config/param-descriptions.d.ts +14 -2
  27. package/build/dts/config/param-descriptions.js +33 -14
  28. package/build/dts/constants.d.ts +1 -0
  29. package/build/dts/constants.js +1 -0
  30. package/build/dts/docs/docs.d.ts +12 -1
  31. package/build/dts/docs/docs.js +274 -43
  32. package/build/dts/docs/fetch-article.d.ts +21 -0
  33. package/build/dts/docs/fetch-article.js +66 -4
  34. package/build/dts/docs/get-to-know.d.ts +7 -2
  35. package/build/dts/docs/get-to-know.js +36 -14
  36. package/build/dts/docs/guessed-url.d.ts +41 -0
  37. package/build/dts/docs/guessed-url.js +185 -0
  38. package/build/dts/docs/mintlify-docs.d.ts +121 -0
  39. package/build/dts/docs/mintlify-docs.js +391 -0
  40. package/build/dts/docs/semanticSearch.d.ts +2 -1
  41. package/build/dts/docs/semanticSearch.js +14 -7
  42. package/build/dts/docs/start-deploy-website.d.ts +7 -6
  43. package/build/dts/docs/start-deploy-website.js +13 -8
  44. package/build/dts/docs/start-headless-build.d.ts +8 -10
  45. package/build/dts/docs/start-headless-build.js +14 -28
  46. package/build/dts/index.d.ts +2 -2
  47. package/build/dts/index.js +2 -2
  48. package/build/dts/infra/redact-personal-data.d.ts +4 -0
  49. package/build/dts/infra/redact-personal-data.js +148 -0
  50. package/build/dts/site-widget-tools/constants.d.ts +50 -2
  51. package/build/dts/site-widget-tools/constants.js +65 -8
  52. package/build/dts/site-widget-tools/index.d.ts +1 -0
  53. package/build/dts/site-widget-tools/index.js +1 -0
  54. package/build/dts/site-widget-tools/pick-images-tool/index.d.ts +14 -0
  55. package/build/dts/site-widget-tools/pick-images-tool/index.js +138 -0
  56. package/build/dts/site-widget-tools/pull-site-creation-job-tool/index.js +1 -1
  57. package/build/dts/site-widget-tools/search-site-templates-tool/index.js +25 -2
  58. package/build/dts/site-widget-tools/site-builder-tool/index.js +35 -3
  59. package/build/dts/site-widget-tools/site-creation-tool/index.d.ts +7 -1
  60. package/build/dts/site-widget-tools/site-creation-tool/index.js +22 -51
  61. package/build/dts/site-widget-tools/suggested-domain-tool/index.js +26 -12
  62. package/build/dts/support/index.d.ts +4 -0
  63. package/build/dts/support/index.js +13 -7
  64. package/build/dts/tool-utils.d.ts +8 -1
  65. package/build/dts/tool-utils.js +26 -2
  66. package/build/dts/toolkit-entry.d.cts +15 -10
  67. package/build/dts/toolkit-entry.d.ts +15 -10
  68. package/build/dts/toolkit.d.ts +2 -2
  69. package/build/dts/toolkit.js +21 -8
  70. package/build/dts/wix-mcp-server.d.ts +10 -1
  71. package/build/dts/wix-mcp-server.js +25 -7
  72. package/build/dts/wixel/constants.d.ts +8 -1
  73. package/build/dts/wixel/constants.js +8 -1
  74. package/build/dts/wixel/index.d.ts +1 -1
  75. package/build/dts/wixel/index.js +2 -2
  76. package/build/dts/wixel/wixel-a2a-client.d.ts +83 -0
  77. package/build/dts/wixel/wixel-a2a-client.js +166 -0
  78. package/build/dts/wixel/wixel-bi.d.ts +2 -1
  79. package/build/dts/wixel/wixel-bi.js +2 -1
  80. package/build/dts/wixel/{external-agent-api.d.ts → wixel-session-entries.d.ts} +11 -33
  81. package/build/dts/wixel/{external-agent-api.js → wixel-session-entries.js} +33 -54
  82. package/build/dts/wixel/wixel-session-tools.d.ts +21 -26
  83. package/build/dts/wixel/wixel-session-tools.js +494 -599
  84. package/build/esm/index.js +3422 -1388
  85. package/build/esm/toolkit.js +2158 -373
  86. package/package.json +4 -4
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import dedent from 'dedent';
3
- import { fetchArticleContent, RECIPES_INDEX_URL } from './fetch-article.js';
3
+ import { fetchArticleContent, fetchSourceContent, RECIPES_INDEX_URL } from './fetch-article.js';
4
4
  import { getSkillsByPortal } from './fetch-skills.js';
5
5
  import { REST_PORTAL_ID } from '../constants.js';
6
6
  // import { generateDocsNavigationTree } from './normalize-menu-content.js';
@@ -8,8 +8,13 @@ import { rethrowIfUnauthorized } from '../auth/index.js';
8
8
  import { defaultToolDescriptions, defaultReadmeDocs } from '../config/default-config.js';
9
9
  import { getDynamicContext } from '@wix/dynamic-context-enricher';
10
10
  import { desc } from '../config/param-descriptions.js';
11
+ import { isMintlifyDocsSite, rewriteDocsLinks } from './mintlify-docs.js';
11
12
  export { getDynamicContext };
12
- export function addGetSiteContextTool({ server, authStrategy, toolDescriptions }) {
13
+ // The management index as authored in ax-skills, which also holds skills its
14
+ // public mirror (and so the published article) leaves out.
15
+ const AX_SKILLS_REPO = 'wix-private/ax-skills';
16
+ const AX_SKILLS_INDEX_PATH = 'skills/wix-manage/SKILL.md';
17
+ export function addGetSiteContextTool({ server, authStrategy, includeCompanionApp = false, toolDescriptions }) {
13
18
  const d = desc('GetSiteContext');
14
19
  server.tool('GetSiteContext', toolDescriptions?.['GetSiteContext'] ??
15
20
  defaultToolDescriptions.GetSiteContext, {
@@ -35,7 +40,8 @@ export function addGetSiteContextTool({ server, authStrategy, toolDescriptions }
35
40
  try {
36
41
  context = await getDynamicContext(authStrategy, httpClient, filter, {
37
42
  partnerContextEnabled: false,
38
- includeAccountContext: false
43
+ includeAccountContext: false,
44
+ includeCompanionApp
39
45
  });
40
46
  }
41
47
  catch (err) {
@@ -61,14 +67,19 @@ export function addGetSiteContextTool({ server, authStrategy, toolDescriptions }
61
67
  };
62
68
  });
63
69
  }
64
- export async function addGetToKnowTools({ server, disableTools = [], toolDescriptions, readmeDocs, authStrategy, skillsPortalUrl, dynamicContextEnabled, repo, commitHash, skillsTags }) {
70
+ export async function addGetToKnowTools({ server, disableTools = [], toolDescriptions, readmeDocs, authStrategy, skillsPortalUrl, dynamicContextEnabled, repo, commitHash, skillsTags, readmeRecipesIndexEnabled = true, docsSite }) {
65
71
  if (disableTools.includes('WixREADME')) {
66
72
  return;
67
73
  }
68
74
  // Use provided docs or fall back to defaults. The Recipes block is appended
69
75
  // at the end so the hand-authored wix-usage-guide stays on top.
70
76
  const baseDocs = readmeDocs ?? defaultReadmeDocs;
71
- const baseDocsWithoutRecipes = baseDocs.filter((doc) => doc.url !== RECIPES_INDEX_URL);
77
+ // The list's page and its .md form are the same document.
78
+ const isRecipesIndex = (doc) => doc.url?.replace(/\.md$/, '') === RECIPES_INDEX_URL;
79
+ const baseDocsWithoutRecipes = baseDocs.filter((doc) => !isRecipesIndex(doc));
80
+ const recipesIndexDocs = readmeRecipesIndexEnabled
81
+ ? baseDocs.filter(isRecipesIndex)
82
+ : [];
72
83
  server.tool('WixREADME', toolDescriptions?.['WixREADME'] ?? defaultToolDescriptions.WixREADME, {}, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args, { httpClient }) => {
73
84
  // The schema is intentionally argless so the LLM always calls WixREADME
74
85
  // account-wide. A specific site can still be targeted programmatically
@@ -130,6 +141,15 @@ export async function addGetToKnowTools({ server, disableTools = [], toolDescrip
130
141
  if (!url) {
131
142
  throw new Error('URL is required for "include" action');
132
143
  }
144
+ const readsAxSkills = isRecipesIndex({ action, url }) &&
145
+ !skillsPortalUrl &&
146
+ (repo ? repo === AX_SKILLS_REPO : !commitHash);
147
+ if (readsAxSkills) {
148
+ return {
149
+ type: 'text',
150
+ text: await fetchSourceContent(httpClient, AX_SKILLS_REPO, AX_SKILLS_INDEX_PATH, repo === AX_SKILLS_REPO ? commitHash : undefined)
151
+ };
152
+ }
133
153
  const fetchedContent = await fetchArticleContent(httpClient, url, 'article', true, skillsPortalUrl, repo, commitHash);
134
154
  return {
135
155
  type: 'text',
@@ -169,9 +189,6 @@ export async function addGetToKnowTools({ server, disableTools = [], toolDescrip
169
189
  // return '';
170
190
  // });
171
191
  const baseContentsPromise = Promise.all(baseDocsWithoutRecipes.map(fetchDoc));
172
- // Filtered lists still come from the portal. The full management index is
173
- // authored alongside the recipes and is fetched like any other article,
174
- // using its commit version when a PR override is configured.
175
192
  const skillsContentPromise = skillsTags?.length
176
193
  ? getSkillsByPortal(httpClient, {
177
194
  portalId: REST_PORTAL_ID,
@@ -182,7 +199,9 @@ export async function addGetToKnowTools({ server, disableTools = [], toolDescrip
182
199
  console.error('[WixREADME] Failed to fetch skills by portal:', err);
183
200
  return null;
184
201
  })
185
- : fetchDoc({ action: 'include', url: RECIPES_INDEX_URL }).then((doc) => doc?.text ?? null);
202
+ : readmeRecipesIndexEnabled
203
+ ? fetchDoc({ action: 'include', url: RECIPES_INDEX_URL }).then((doc) => doc?.text ?? null)
204
+ : Promise.resolve(null);
186
205
  // Auto-inject site context. With no site targeted this is the account-wide
187
206
  // view: single-site accounts "just work", and accounts with more than the
188
207
  // cap show the first N sites plus a note to use GetSiteContext for the rest.
@@ -207,7 +226,7 @@ export async function addGetToKnowTools({ server, disableTools = [], toolDescrip
207
226
  const recipesIntro = dedent `
208
227
  Reusable how-to guides for common ${isVisitorFlow ? 'Wix site visitor' : 'Wix management'} tasks, grouped by product area. Each recipe is a Wix docs article that bundles the right endpoints, request shapes, field names, and step ordering for one real-world use case.
209
228
 
210
- Scan the index below and open any relevant article with \`ReadFullDocsArticle\`. If the article contains a code example that fits the user's intent — a **complete** example (not a truncated \`... N more lines ...\` snippet) that matches your exact case, same variant/shape — call the API directly. If the closest example is truncated or omits a field your case needs, do NOT call the API off it — continue discovery first: \`ReadFullDocsArticle\` for the complete example, or \`SearchWixAPISpec\` for the exact schema. On query and search methods an example proves only the fields and operators it uses; the endpoint's own declared list covers the rest. Otherwise (no fitting recipe, no usable code example, or persistent errors after retrying), fall back to docs discovery: \`SearchWixRESTDocumentation\` or \`BrowseWixRESTDocsMenu\` to find the right method, then \`SearchWixAPISpec\` for the exact schema.
229
+ Open the recipe that matches the task with \`ReadFullDocsArticle\` and follow it. When its example for a call matches your case (the same variant and shape), call from it; when it doesn't, or leaves out a field your case needs, read the method's schema with \`SearchWixAPISpec\` first. On query and search methods an example proves only the fields and operators it uses; the method's own declared list covers the rest. When no recipe fits, find the method with \`SearchWixRESTDocumentation\` or \`BrowseWixRESTDocsMenu\`, then \`SearchWixAPISpec\` for its schema.
211
230
  `;
212
231
  const recipesContents = skillsContent !== null
213
232
  ? [
@@ -217,9 +236,7 @@ export async function addGetToKnowTools({ server, disableTools = [], toolDescrip
217
236
  content: `${recipesIntro}\n\n${skillsContent}`
218
237
  })
219
238
  ]
220
- : await Promise.all(baseDocs
221
- .filter((doc) => doc.url === RECIPES_INDEX_URL)
222
- .map(fetchDoc));
239
+ : await Promise.all(recipesIndexDocs.map(fetchDoc));
223
240
  // Cross-recipe steering that applies to a family of recipes rather than
224
241
  // any single article; rendered right after the index. Gated on the note's
225
242
  // target family actually appearing in the rendered index — this also
@@ -244,7 +261,12 @@ export async function addGetToKnowTools({ server, disableTools = [], toolDescrip
244
261
  .map((item) => item.text),
245
262
  ...(dynamicContext ? [dynamicContext] : [])
246
263
  ];
247
- const text = parts.join('\n\n');
264
+ const joined = parts.join('\n\n');
265
+ // Mintlify docs: the README links docs pages on dev.wix.com; resolve
266
+ // them to their Mintlify pages, as the docs tools do.
267
+ const text = isMintlifyDocsSite(docsSite)
268
+ ? (await rewriteDocsLinks(joined, { label: 'readme WixREADME' })).text
269
+ : joined;
248
270
  return {
249
271
  content: [{ type: 'text', text }]
250
272
  };
@@ -0,0 +1,41 @@
1
+ export declare const slugWords: (slug: string) => Set<string>;
2
+ /** Shared words over all words (Jaccard): 1 for the same words, 0 for none in common. */
3
+ export declare function slugSimilarity(a: string, b: string): number;
4
+ /** A file at least this close to the asked name, and the only one that close, is read in its place. */
5
+ export declare const SKILL_FILE_MATCH = 0.5;
6
+ export type SkillFileGuess = {
7
+ kind: 'file';
8
+ url: string;
9
+ path: string;
10
+ } | {
11
+ kind: 'folder';
12
+ folder: string;
13
+ files: string[];
14
+ manifestUrl: string;
15
+ };
16
+ /**
17
+ * The closest file to a skills registry URL that 404'd, from the skill's manifest
18
+ * (`https://www.wix.com/skills/<skill>.manifest.json`, `{ base, files: [{ path }] }`).
19
+ * A file of the same name in another folder counts when it is the only one; a near name
20
+ * counts in the same folder only. Null when the URL is not a skill file or the manifest
21
+ * can't be read.
22
+ */
23
+ export declare function closestSkillFile(url: string, manifest: {
24
+ base?: string;
25
+ files?: {
26
+ path: string;
27
+ }[];
28
+ }, manifestUrl: string): SkillFileGuess | null;
29
+ /** The manifest of the skill a registry URL is in, or null for a URL that names no skill. */
30
+ export declare function skillManifestUrl(url: string): string | null;
31
+ /** How far above a docs page the nearest menu is looked for. */
32
+ export declare const MENU_WALK_UP_LEVELS = 3;
33
+ /** The menus above a dev.wix.com docs URL, nearest first. */
34
+ export declare function parentMenuUrls(url: string): string[];
35
+ /** The menu's own entries (links under it) whose last segment is closest to the asked page, best first. */
36
+ export declare function closestMenuLinks(menuText: string, menuUrl: string, askedUrl: string, limit?: number): string[];
37
+ /** Below this, a menu's best entry is no answer, and its closest sub-menus are read too. */
38
+ export declare const MENU_ENTRY_MATCH = 0.75;
39
+ /** The menu's sub-menus worth reading for the asked page: its entries sharing a word with it, closest first. */
40
+ export declare function subMenusToRead(menuText: string, menuUrl: string, askedUrl: string, limit?: number): string[];
41
+ //# sourceMappingURL=guessed-url.d.ts.map
@@ -0,0 +1,185 @@
1
+ /**
2
+ * What ReadFullDocsArticle offers for a docs or skills URL that 404s. Most of these are
3
+ * guessed: a method slug invented under a real resource, a recipe link "tidied" into a
4
+ * path that never existed, a skill file renamed since an agent's notes were written.
5
+ * Instead of a bare not-found, the agent gets the nearest thing that exists:
6
+ *
7
+ * - a skills registry file: the skill's manifest names every file, so a file whose name
8
+ * is close to the asked one, in the same folder, is read in its place; otherwise the
9
+ * folder's files are listed;
10
+ * - a dev.wix.com docs page: the nearest menu above it, with the links whose names are
11
+ * closest to the asked page listed first.
12
+ */
13
+ import { normalizeSkillsUrl } from './fetch-article.js';
14
+ const SKILLS_BASE = 'https://www.wix.com/skills/';
15
+ // Every host and path ReadFullDocsArticle accepts for the skills registry, as the
16
+ // www.wix.com/skills/ URL whose manifest is next to it.
17
+ const registryUrl = (url) => normalizeSkillsUrl(url).replace(/^https:\/\/(?:dev\.wix\.com(?:\/docs)?|www\.wix\.com\/velo\/reference)\/skills\//, SKILLS_BASE);
18
+ const DOCS_BASE = 'https://dev.wix.com/docs/';
19
+ // Words that say nothing about which file or page is meant.
20
+ const FILLER = new Set([
21
+ '',
22
+ 'md',
23
+ 'a',
24
+ 'an',
25
+ 'the',
26
+ 'and',
27
+ 'or',
28
+ 'of',
29
+ 'to',
30
+ 'wix',
31
+ 's'
32
+ ]);
33
+ export const slugWords = (slug) => new Set(slug
34
+ .toLowerCase()
35
+ .replace(/\.md$/, '')
36
+ .split(/[^a-z0-9]+/)
37
+ .filter((w) => !FILLER.has(w))
38
+ // A plural names the same thing: posts, post.
39
+ .map((w) => (w.length > 3 && w.endsWith('s') ? w.slice(0, -1) : w)));
40
+ /** Shared words over all words (Jaccard): 1 for the same words, 0 for none in common. */
41
+ export function slugSimilarity(a, b) {
42
+ const x = slugWords(a);
43
+ const y = slugWords(b);
44
+ if (x.size === 0 || y.size === 0)
45
+ return 0;
46
+ let shared = 0;
47
+ for (const w of x)
48
+ if (y.has(w))
49
+ shared++;
50
+ return shared / (x.size + y.size - shared);
51
+ }
52
+ const lastSegment = (path) => path.slice(path.lastIndexOf('/') + 1);
53
+ const folderOf = (path) => path.includes('/') ? path.slice(0, path.lastIndexOf('/')) : '';
54
+ /** A file at least this close to the asked name, and the only one that close, is read in its place. */
55
+ export const SKILL_FILE_MATCH = 0.5;
56
+ /**
57
+ * The closest file to a skills registry URL that 404'd, from the skill's manifest
58
+ * (`https://www.wix.com/skills/<skill>.manifest.json`, `{ base, files: [{ path }] }`).
59
+ * A file of the same name in another folder counts when it is the only one; a near name
60
+ * counts in the same folder only. Null when the URL is not a skill file or the manifest
61
+ * can't be read.
62
+ */
63
+ export function closestSkillFile(url, manifest, manifestUrl) {
64
+ const normalized = registryUrl(url).replace(/[?#].*$/, '');
65
+ if (!normalized.startsWith(SKILLS_BASE))
66
+ return null;
67
+ const [, ...rest] = normalized.slice(SKILLS_BASE.length).split('/');
68
+ const asked = rest.join('/');
69
+ const paths = (manifest.files ?? []).map((f) => f.path);
70
+ if (!asked || paths.length === 0)
71
+ return null;
72
+ const base = manifest.base ?? manifestUrl.replace(/\.manifest\.json$/, '/');
73
+ const name = lastSegment(asked);
74
+ const folder = folderOf(asked);
75
+ const sameName = paths.filter((p) => lastSegment(p) === name);
76
+ if (sameName.length === 1) {
77
+ return { kind: 'file', path: sameName[0], url: base + sameName[0] };
78
+ }
79
+ const ranked = paths
80
+ .filter((p) => folderOf(p) === folder)
81
+ .map((p) => ({ p, score: slugSimilarity(lastSegment(p), name) }))
82
+ .sort((a, b) => b.score - a.score);
83
+ const [best, next] = ranked;
84
+ if (best &&
85
+ best.score >= SKILL_FILE_MATCH &&
86
+ best.score > (next?.score ?? 0)) {
87
+ return { kind: 'file', path: best.p, url: base + best.p };
88
+ }
89
+ if (ranked.length > 0) {
90
+ return {
91
+ kind: 'folder',
92
+ folder,
93
+ files: ranked.map((r) => base + r.p),
94
+ manifestUrl
95
+ };
96
+ }
97
+ // The folder doesn't exist either: the skill's top-level folders and files.
98
+ const top = [...new Set(paths.map((p) => p.split('/')[0]))].sort();
99
+ return {
100
+ kind: 'folder',
101
+ folder: '',
102
+ files: top.map((t) => base + t),
103
+ manifestUrl
104
+ };
105
+ }
106
+ /** The manifest of the skill a registry URL is in, or null for a URL that names no skill. */
107
+ export function skillManifestUrl(url) {
108
+ const normalized = registryUrl(url);
109
+ if (!normalized.startsWith(SKILLS_BASE))
110
+ return null;
111
+ const skill = normalized.slice(SKILLS_BASE.length).split(/[/?#]/)[0];
112
+ return skill ? `${SKILLS_BASE}${skill}.manifest.json` : null;
113
+ }
114
+ /** How far above a docs page the nearest menu is looked for. */
115
+ export const MENU_WALK_UP_LEVELS = 3;
116
+ // A menu this close to the root (`/docs/velo`, `/docs/api-reference/crm`) lists whole
117
+ // products, which says nothing about the page that was asked for.
118
+ const MIN_MENU_SEGMENTS = 3;
119
+ /** The menus above a dev.wix.com docs URL, nearest first. */
120
+ export function parentMenuUrls(url) {
121
+ const clean = url.replace(/[?#].*$/, '').replace(/\/+$/, '');
122
+ if (!clean.startsWith(DOCS_BASE))
123
+ return [];
124
+ const segments = clean.slice(DOCS_BASE.length).split('/');
125
+ const out = [];
126
+ for (let up = 1; up <= MENU_WALK_UP_LEVELS; up++) {
127
+ const kept = segments.length - up;
128
+ if (kept < MIN_MENU_SEGMENTS)
129
+ break;
130
+ out.push(DOCS_BASE + segments.slice(0, kept).join('/'));
131
+ }
132
+ return out;
133
+ }
134
+ /** The menu's own entries (links under it) whose last segment is closest to the asked page, best first. */
135
+ export function closestMenuLinks(menuText, menuUrl, askedUrl, limit = 3) {
136
+ const asked = lastSegment(askedUrl.replace(/[?#].*$/, '').replace(/\/+$/, ''));
137
+ const under = `${menuUrl.replace(/\/+$/, '')}/`;
138
+ const links = [
139
+ ...new Set(menuText.match(/https:\/\/dev\.wix\.com\/docs\/[^\s)\]>"'`]+/g) ?? [])
140
+ ].filter((l) => l.startsWith(under));
141
+ return links
142
+ .map((l) => ({
143
+ l,
144
+ score: slugSimilarity(lastSegment(l.replace(/\.md$/, '')), asked)
145
+ }))
146
+ .filter((r) => r.score > 0)
147
+ .sort((a, b) => b.score - a.score)
148
+ .slice(0, limit)
149
+ .map((r) => r.l);
150
+ }
151
+ const SMALL_MENU = 3;
152
+ /** Below this, a menu's best entry is no answer, and its closest sub-menus are read too. */
153
+ export const MENU_ENTRY_MATCH = 0.75;
154
+ /** The menu's sub-menus worth reading for the asked page: its entries sharing a word with it, closest first. */
155
+ export function subMenusToRead(menuText, menuUrl, askedUrl, limit = 2) {
156
+ const asked = lastSegment(askedUrl.replace(/[?#].*$/, '').replace(/\/+$/, ''));
157
+ const under = `${menuUrl.replace(/\/+$/, '')}/`;
158
+ // A method entry (`- [Create Order](…) _(POST)_`) is a page, not a menu.
159
+ const methods = new Set([
160
+ ...menuText.matchAll(/\]\((https:\/\/dev\.wix\.com\/docs\/[^)\s]+)\)\s*_\((?:GET|POST|PUT|PATCH|DELETE)\)_/g)
161
+ ].map((m) => m[1]));
162
+ // Direct children only: the legacy menu text also lists pages nested further down.
163
+ const entries = [
164
+ ...new Set(menuText.match(/https:\/\/dev\.wix\.com\/docs\/[^\s)\]>"'`]+/g) ?? [])
165
+ ].filter((l) => l.startsWith(under) &&
166
+ !l.slice(under.length).replace(/\.md$/, '').includes('/') &&
167
+ !methods.has(l));
168
+ const scored = entries
169
+ .map((l) => ({
170
+ l,
171
+ score: slugSimilarity(lastSegment(l.replace(/\.md$/, '')), asked)
172
+ }))
173
+ .sort((a, b) => b.score - a.score);
174
+ if (scored.some((s) => s.score >= MENU_ENTRY_MATCH))
175
+ return [];
176
+ // A menu of a few entries is read whole: none may share a word (catalog-v1 holds
177
+ // catalog and inventory; products sit under catalog).
178
+ if (entries.length <= SMALL_MENU)
179
+ return entries;
180
+ return scored
181
+ .filter((s) => s.score > 0)
182
+ .slice(0, limit)
183
+ .map((s) => s.l);
184
+ }
185
+ //# sourceMappingURL=guessed-url.js.map
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Mintlify docs, `mintlify-links` mode (wix-private/docs:
3
+ * documentation/mintlify-migration/tasks/mcp-pipeline.md).
4
+ *
5
+ * The KBs and every backend stay as they are. With `docsSite: 'mintlify-links'`,
6
+ * the docs tools resolve each dev.wix.com docs link in their output to its page
7
+ * on the Mintlify site, and ReadFullDocsArticle reads pages from Mintlify's
8
+ * `.md`. Off unless a caller passes the option.
9
+ */
10
+ export declare const MINTLIFY_LINKS_DOCS_SITE = "mintlify-links";
11
+ /** Stage 2: the same, with search on the Mintlify KBs indexed from the Mintlify site. */
12
+ export declare const MINTLIFY_DOCS_SITE = "mintlify";
13
+ export type DocsSite = typeof MINTLIFY_LINKS_DOCS_SITE | typeof MINTLIFY_DOCS_SITE;
14
+ export declare const MINTLIFY_HOST = "https://wix-internal.mintlify.site";
15
+ export declare const MAX_CHECK_ENTRIES = 5000;
16
+ export declare function isMintlifyDocsSite(docsSite: string | undefined): boolean;
17
+ /** The Mintlify document types a search tool uses in stage 2, or undefined to search as today. */
18
+ export declare function mintlifyDocumentTypes(docsSite: string | undefined, toolName: string): string[] | undefined;
19
+ export declare function isMintlifyUrl(url: string): boolean;
20
+ /** The URL prefixes the docs tools accept in Mintlify mode, besides dev.wix.com. */
21
+ export declare const MINTLIFY_URL_PREFIXES: string[];
22
+ export interface MintlifyCandidate {
23
+ /** Canonical Mintlify page URL: host + path, no query, anchor or `.md`. */
24
+ url: string;
25
+ path: string;
26
+ /** `sdk` when the source link asked for the SDK view (`?apiView=SDK`). */
27
+ flavour: 'rest' | 'sdk';
28
+ }
29
+ /**
30
+ * Maps a dev.wix.com docs URL, or a Mintlify URL on any alias host, to its
31
+ * Mintlify page by rule: drop `/docs`, map the portal slug to its tab, drop
32
+ * the query, the anchor and `.md`. Returns null for anything else.
33
+ */
34
+ export declare function toMintlifyCandidate(rawUrl: string): MintlifyCandidate | null;
35
+ /**
36
+ * Maps a Mintlify URL back to its dev.wix.com docs URL, keeping `?apiView=SDK`.
37
+ * For the REST portal the paths are the same, so the backends that only know
38
+ * dev.wix.com (ingvar, the docs menu) can serve it.
39
+ */
40
+ export declare function toDevWixUrl(rawUrl: string): string;
41
+ export type ResolveOutcome = 'identity' | 'redirect' | 'missing' | 'other-portal' | 'not-docs' | 'error';
42
+ export interface ResolvedLink {
43
+ outcome: ResolveOutcome;
44
+ /** The URL to hand out: the Mintlify page when resolved, else the input. */
45
+ url: string;
46
+ }
47
+ interface PageCheck {
48
+ outcome: 'identity' | 'redirect' | 'missing' | 'error';
49
+ /** Final page path on the Mintlify site, when the page exists. */
50
+ path?: string;
51
+ }
52
+ /** For tests. */
53
+ export declare function mintlifyCheckCacheSize(): number;
54
+ /** For tests. */
55
+ export declare function clearMintlifyCheckCache(): void;
56
+ /**
57
+ * Asks the Mintlify site whether a page exists, following its own redirects
58
+ * (moves, and containers that send you to their first page) without
59
+ * following them blindly: a page is a 200 at the end of at most 3 hops.
60
+ */
61
+ export declare function checkMintlifyPage(path: string): Promise<PageCheck>;
62
+ /**
63
+ * Resolves one docs link to its Mintlify page. With `verify: false` the
64
+ * mapping is by rule only, which suits menu links (the menu and the Mintlify
65
+ * navigation come from the same Portal Service tree).
66
+ */
67
+ export declare function resolveDocsLink(rawUrl: string, { verify }?: {
68
+ verify?: boolean;
69
+ }): Promise<ResolvedLink>;
70
+ /**
71
+ * Maps every Mintlify page link in `text` back to its dev.wix.com docs URL, keeping
72
+ * `?apiView=SDK` and the anchor. Run before {@link rewriteDocsLinks} on text whose
73
+ * Mintlify links came by rule rather than from a checked page (SearchWixAPISpec's
74
+ * index), so a page the site doesn't have ends up on dev.wix.com, not a dead link.
75
+ */
76
+ export declare function toDevWixHostText(text: string): string;
77
+ export interface RewriteStats {
78
+ links: number;
79
+ byOutcome: Partial<Record<ResolveOutcome, number>>;
80
+ misses: string[];
81
+ }
82
+ /**
83
+ * Rewrites every docs link in `text` to its Mintlify page. Links with no
84
+ * Mintlify page stay as they are, so the agent can still read them through
85
+ * today's backends. Logs one coverage line per call.
86
+ */
87
+ export declare function rewriteDocsLinks(text: string, { verify, label }?: {
88
+ verify?: boolean;
89
+ label?: string;
90
+ }): Promise<{
91
+ text: string;
92
+ stats: RewriteStats;
93
+ }>;
94
+ /**
95
+ * Light cleanup of a method page's .md: drop inlined snippet source, and keep
96
+ * only the tab the reader asked for (REST, or SDK for `?apiView=SDK`). A
97
+ * method page carries both, which roughly doubles its size. Pages without
98
+ * exactly one REST and one SDK tab are left as they are.
99
+ */
100
+ export declare function cleanMintlifyMarkdown(markdown: string, flavour: 'rest' | 'sdk'): string;
101
+ /**
102
+ * Tool and parameter descriptions with their docs URLs on the Mintlify host. The
103
+ * model copies the examples' host when it cites a page, so with dev.wix.com in
104
+ * the descriptions it rewrites Mintlify links back to dev.wix.com.
105
+ */
106
+ export declare function toMintlifyHostText(text: string): string;
107
+ /**
108
+ * Mintlify's .md links the site's own pages by path (`[Order Object](/api-reference/…)`).
109
+ * Make them absolute, so the model doesn't have to guess the host (it picks dev.wix.com).
110
+ */
111
+ export declare function absolutizeSiteLinks(markdown: string): string;
112
+ /**
113
+ * Reads a page from the Mintlify site as Markdown. Returns null when the site
114
+ * has no page for the URL, so the caller can fall back to today's backend.
115
+ */
116
+ export declare function readMintlifyMarkdown(rawUrl: string): Promise<{
117
+ url: string;
118
+ markdown: string;
119
+ } | null>;
120
+ export {};
121
+ //# sourceMappingURL=mintlify-docs.d.ts.map