@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.
- package/README.md +2 -2
- package/build/bin-standalone.js +1457 -332
- package/build/cjs/index.cjs +3379 -1333
- package/build/cjs/toolkit.cjs +2158 -373
- package/build/dts/api-call/headless-release.d.ts +56 -0
- package/build/dts/api-call/headless-release.js +135 -0
- package/build/dts/api-call/index.d.ts +9 -1
- package/build/dts/api-call/index.js +36 -8
- package/build/dts/api-call/site-files.d.ts +14 -0
- package/build/dts/api-call/site-files.js +106 -10
- package/build/dts/api-call/upload-headless-website-files.d.ts +117 -5
- package/build/dts/api-call/upload-headless-website-files.js +565 -20
- package/build/dts/api-call/upload-image-tool.d.ts +2 -0
- package/build/dts/api-call/upload-image-tool.js +8 -4
- package/build/dts/bin.d.ts +1 -1
- package/build/dts/bin.js +3 -1
- 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.d.ts +6 -1
- package/build/dts/code-mode/index.js +32 -12
- package/build/dts/code-mode/truncate-response.js +5 -1
- package/build/dts/config/default-config.d.ts +35 -5
- package/build/dts/config/default-config.js +339 -182
- package/build/dts/config/file-params.d.ts +22 -0
- package/build/dts/config/file-params.js +24 -0
- package/build/dts/config/param-descriptions.d.ts +14 -2
- package/build/dts/config/param-descriptions.js +33 -14
- package/build/dts/constants.d.ts +1 -0
- package/build/dts/constants.js +1 -0
- package/build/dts/docs/docs.d.ts +12 -1
- package/build/dts/docs/docs.js +274 -43
- package/build/dts/docs/fetch-article.d.ts +21 -0
- package/build/dts/docs/fetch-article.js +66 -4
- package/build/dts/docs/get-to-know.d.ts +7 -2
- package/build/dts/docs/get-to-know.js +36 -14
- package/build/dts/docs/guessed-url.d.ts +41 -0
- package/build/dts/docs/guessed-url.js +185 -0
- package/build/dts/docs/mintlify-docs.d.ts +121 -0
- package/build/dts/docs/mintlify-docs.js +391 -0
- package/build/dts/docs/semanticSearch.d.ts +2 -1
- package/build/dts/docs/semanticSearch.js +14 -7
- package/build/dts/docs/start-deploy-website.d.ts +7 -6
- package/build/dts/docs/start-deploy-website.js +13 -8
- package/build/dts/docs/start-headless-build.d.ts +8 -10
- package/build/dts/docs/start-headless-build.js +14 -28
- package/build/dts/index.d.ts +2 -2
- package/build/dts/index.js +2 -2
- package/build/dts/infra/redact-personal-data.d.ts +4 -0
- package/build/dts/infra/redact-personal-data.js +148 -0
- package/build/dts/site-widget-tools/constants.d.ts +50 -2
- package/build/dts/site-widget-tools/constants.js +65 -8
- package/build/dts/site-widget-tools/index.d.ts +1 -0
- package/build/dts/site-widget-tools/index.js +1 -0
- package/build/dts/site-widget-tools/pick-images-tool/index.d.ts +14 -0
- package/build/dts/site-widget-tools/pick-images-tool/index.js +138 -0
- package/build/dts/site-widget-tools/pull-site-creation-job-tool/index.js +1 -1
- package/build/dts/site-widget-tools/search-site-templates-tool/index.js +25 -2
- package/build/dts/site-widget-tools/site-builder-tool/index.js +35 -3
- package/build/dts/site-widget-tools/site-creation-tool/index.d.ts +7 -1
- package/build/dts/site-widget-tools/site-creation-tool/index.js +22 -51
- package/build/dts/site-widget-tools/suggested-domain-tool/index.js +26 -12
- package/build/dts/support/index.d.ts +4 -0
- package/build/dts/support/index.js +13 -7
- package/build/dts/tool-utils.d.ts +8 -1
- package/build/dts/tool-utils.js +26 -2
- package/build/dts/toolkit-entry.d.cts +15 -10
- package/build/dts/toolkit-entry.d.ts +15 -10
- package/build/dts/toolkit.d.ts +2 -2
- package/build/dts/toolkit.js +21 -8
- package/build/dts/wix-mcp-server.d.ts +10 -1
- package/build/dts/wix-mcp-server.js +25 -7
- package/build/dts/wixel/constants.d.ts +8 -1
- package/build/dts/wixel/constants.js +8 -1
- package/build/dts/wixel/index.d.ts +1 -1
- package/build/dts/wixel/index.js +2 -2
- package/build/dts/wixel/wixel-a2a-client.d.ts +83 -0
- package/build/dts/wixel/wixel-a2a-client.js +166 -0
- package/build/dts/wixel/wixel-bi.d.ts +2 -1
- package/build/dts/wixel/wixel-bi.js +2 -1
- package/build/dts/wixel/{external-agent-api.d.ts → wixel-session-entries.d.ts} +11 -33
- package/build/dts/wixel/{external-agent-api.js → wixel-session-entries.js} +33 -54
- package/build/dts/wixel/wixel-session-tools.d.ts +21 -26
- package/build/dts/wixel/wixel-session-tools.js +494 -599
- package/build/esm/index.js +3422 -1388
- package/build/esm/toolkit.js +2158 -373
- 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
|
-
|
|
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
|
-
|
|
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
|
-
:
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|