@wix/mcp 1.0.82 → 1.0.84
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 +3 -1
- package/build/bin-standalone.js +836 -2
- package/build/cjs/index.cjs +1468 -173
- package/build/cjs/toolkit.cjs +838 -2
- package/build/dts/bin.d.ts +1 -1
- package/build/dts/bin.js +13 -1
- package/build/dts/config/default-config.d.ts +1 -0
- package/build/dts/config/default-config.js +28 -1
- package/build/dts/config/param-descriptions.d.ts +21 -0
- package/build/dts/config/param-descriptions.js +21 -0
- package/build/dts/docs/get-to-know.js +17 -1
- package/build/dts/graphql/constants.d.ts +8 -0
- package/build/dts/graphql/constants.js +11 -0
- package/build/dts/graphql/index.d.ts +30 -0
- package/build/dts/graphql/index.js +259 -0
- package/build/dts/graphql/introspection.d.ts +106 -0
- package/build/dts/graphql/introspection.js +282 -0
- package/build/dts/graphql/search.d.ts +25 -0
- package/build/dts/graphql/search.js +277 -0
- package/build/dts/index.d.ts +2 -1
- package/build/dts/index.js +3 -1
- package/build/dts/site-widget-tools/suggested-domain-tool/index.js +21 -8
- package/build/dts/toolkit-entry.d.cts +12 -0
- package/build/dts/toolkit-entry.d.ts +12 -0
- package/build/dts/toolkit-entry.js +4 -0
- package/build/dts/toolkit.js +14 -0
- package/build/dts/wixel/wixel-session-tools.d.ts +43 -0
- package/build/dts/wixel/wixel-session-tools.js +701 -198
- package/build/esm/index.js +1460 -173
- package/build/esm/toolkit.js +833 -2
- package/package.json +2 -2
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Introspection client for the Wix GraphQL schema.
|
|
3
|
+
*
|
|
4
|
+
* The full schema is ~4MB (about 4,100 types), far too large to hand to an
|
|
5
|
+
* agent, so the tools disclose it progressively: the root operations (queries
|
|
6
|
+
* and mutations with their descriptions) are fetched once and cached
|
|
7
|
+
* in-process for ranking, and individual types are fetched on demand by name.
|
|
8
|
+
* Introspection needs no auth, so this works for anonymous sessions too.
|
|
9
|
+
*/
|
|
10
|
+
import { WIX_GRAPHQL_ENDPOINT } from './constants.js';
|
|
11
|
+
const TYPE_REF_FRAGMENT = `
|
|
12
|
+
fragment TR on __Type { kind name ofType { kind name ofType { kind name ofType { kind name } } } }`;
|
|
13
|
+
const ROOT_OPERATIONS_QUERY = `{
|
|
14
|
+
__schema {
|
|
15
|
+
queryType { fields { name description args { name type { ...TR } } type { ...TR } } }
|
|
16
|
+
mutationType { fields { name description args { name type { ...TR } } type { ...TR } } }
|
|
17
|
+
}
|
|
18
|
+
}${TYPE_REF_FRAGMENT}`;
|
|
19
|
+
const TYPE_DETAILS_FRAGMENT = `
|
|
20
|
+
fragment TD on __Type {
|
|
21
|
+
kind name description
|
|
22
|
+
fields { name description type { ...TR } args { name type { ...TR } } }
|
|
23
|
+
inputFields { name description type { ...TR } defaultValue }
|
|
24
|
+
enumValues { name description }
|
|
25
|
+
possibleTypes { name }
|
|
26
|
+
}`;
|
|
27
|
+
const ROOT_CACHE_TTL_MS = 6 * 60 * 60 * 1000;
|
|
28
|
+
let rootCache;
|
|
29
|
+
const typeCache = new Map();
|
|
30
|
+
/** Test hook: forget every cached introspection result. */
|
|
31
|
+
export function resetGraphqlSchemaCache() {
|
|
32
|
+
rootCache = undefined;
|
|
33
|
+
typeCache.clear();
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The endpoint answers bursts with transient 503s (an HTML page). Retry a few
|
|
37
|
+
* times with backoff; introspection is idempotent so this is always safe.
|
|
38
|
+
*/
|
|
39
|
+
export const RETRY_DELAYS_MS = [800, 2000, 4000];
|
|
40
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
41
|
+
export async function fetchWithRetry(fetchImpl, url, init, delays = RETRY_DELAYS_MS) {
|
|
42
|
+
let attempt = 0;
|
|
43
|
+
for (;;) {
|
|
44
|
+
const resp = await fetchImpl(url, init);
|
|
45
|
+
if (resp.status < 500 || attempt >= delays.length)
|
|
46
|
+
return resp;
|
|
47
|
+
await sleep(delays[attempt++]);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function postIntrospection(query, options) {
|
|
51
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
52
|
+
const endpoint = options.endpoint ?? WIX_GRAPHQL_ENDPOINT;
|
|
53
|
+
const resp = await fetchWithRetry(fetchImpl, endpoint, {
|
|
54
|
+
method: 'POST',
|
|
55
|
+
headers: {
|
|
56
|
+
'Content-Type': 'application/json',
|
|
57
|
+
Accept: 'application/json'
|
|
58
|
+
},
|
|
59
|
+
body: JSON.stringify({ query })
|
|
60
|
+
}, options.retryDelaysMs);
|
|
61
|
+
const text = await resp.text();
|
|
62
|
+
if (!resp.ok) {
|
|
63
|
+
const body = /^\s*<(!doctype|html)/i.test(text)
|
|
64
|
+
? '(HTML error page — the endpoint is temporarily unavailable; try again in a moment)'
|
|
65
|
+
: text.slice(0, 500);
|
|
66
|
+
throw new Error(`GraphQL introspection failed (${resp.status} ${resp.statusText}): ${body}`);
|
|
67
|
+
}
|
|
68
|
+
const json = JSON.parse(text);
|
|
69
|
+
if (json.errors?.length && !json.data) {
|
|
70
|
+
throw new Error(`GraphQL introspection returned errors: ${JSON.stringify(json.errors).slice(0, 500)}`);
|
|
71
|
+
}
|
|
72
|
+
return json.data;
|
|
73
|
+
}
|
|
74
|
+
/** All root query + mutation fields, cached in-process. */
|
|
75
|
+
export function getRootOperations(options = {}) {
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
if (rootCache && now - rootCache.fetchedAt < ROOT_CACHE_TTL_MS) {
|
|
78
|
+
return rootCache.promise;
|
|
79
|
+
}
|
|
80
|
+
const promise = postIntrospection(ROOT_OPERATIONS_QUERY, options).then((data) => {
|
|
81
|
+
const toOps = (fields, kind) => (fields ?? []).map((f) => ({
|
|
82
|
+
kind,
|
|
83
|
+
name: f.name,
|
|
84
|
+
description: f.description ?? '',
|
|
85
|
+
args: f.args ?? [],
|
|
86
|
+
type: f.type
|
|
87
|
+
}));
|
|
88
|
+
return [
|
|
89
|
+
...toOps(data?.__schema?.queryType?.fields, 'query'),
|
|
90
|
+
...toOps(data?.__schema?.mutationType?.fields, 'mutation')
|
|
91
|
+
];
|
|
92
|
+
});
|
|
93
|
+
// Don't pin a failed fetch for the TTL.
|
|
94
|
+
promise.catch(() => {
|
|
95
|
+
if (rootCache?.promise === promise)
|
|
96
|
+
rootCache = undefined;
|
|
97
|
+
});
|
|
98
|
+
rootCache = { promise, fetchedAt: now };
|
|
99
|
+
return promise;
|
|
100
|
+
}
|
|
101
|
+
const GRAPHQL_NAME = /^[_A-Za-z][_0-9A-Za-z]*$/;
|
|
102
|
+
/** Aliases per introspection document — the server rejects more than 15. */
|
|
103
|
+
const TYPE_BATCH_SIZE = 10;
|
|
104
|
+
/**
|
|
105
|
+
* Fetch several named types in one round-trip (aliased `__type` lookups).
|
|
106
|
+
* Unknown names resolve to null. Results are cached per name.
|
|
107
|
+
*/
|
|
108
|
+
export async function getTypes(names, options = {}) {
|
|
109
|
+
const unique = [...new Set(names)].filter((n) => GRAPHQL_NAME.test(n));
|
|
110
|
+
const missing = unique.filter((n) => !typeCache.has(n));
|
|
111
|
+
// The endpoint caps aliases per document (15) and answers bursts with
|
|
112
|
+
// 503s, so look types up in small chunks, one chunk at a time.
|
|
113
|
+
for (let start = 0; start < missing.length; start += TYPE_BATCH_SIZE) {
|
|
114
|
+
const chunk = missing.slice(start, start + TYPE_BATCH_SIZE);
|
|
115
|
+
const selections = chunk
|
|
116
|
+
.map((n, i) => `t${i}: __type(name: "${n}") { ...TD }`)
|
|
117
|
+
.join('\n');
|
|
118
|
+
const query = `{\n${selections}\n}${TYPE_DETAILS_FRAGMENT}${TYPE_REF_FRAGMENT}`;
|
|
119
|
+
const batch = postIntrospection(query, options);
|
|
120
|
+
chunk.forEach((n, i) => {
|
|
121
|
+
const p = batch.then((data) => (data?.[`t${i}`] ?? null));
|
|
122
|
+
// A failed batch must not poison the cache for later calls.
|
|
123
|
+
p.catch(() => {
|
|
124
|
+
if (typeCache.get(n) === p)
|
|
125
|
+
typeCache.delete(n);
|
|
126
|
+
});
|
|
127
|
+
typeCache.set(n, p);
|
|
128
|
+
});
|
|
129
|
+
// Sequential on purpose (see above); a failure surfaces via the cached
|
|
130
|
+
// promise when the caller awaits it.
|
|
131
|
+
await batch.catch(() => undefined);
|
|
132
|
+
}
|
|
133
|
+
const result = new Map();
|
|
134
|
+
await Promise.all(unique.map(async (n) => {
|
|
135
|
+
result.set(n, await typeCache.get(n));
|
|
136
|
+
}));
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
/** `[Foo!]!` style rendering of a (possibly wrapped) type ref. */
|
|
140
|
+
export function renderTypeRef(ref) {
|
|
141
|
+
if (!ref)
|
|
142
|
+
return 'Unknown';
|
|
143
|
+
if (ref.kind === 'NON_NULL')
|
|
144
|
+
return `${renderTypeRef(ref.ofType)}!`;
|
|
145
|
+
if (ref.kind === 'LIST')
|
|
146
|
+
return `[${renderTypeRef(ref.ofType)}]`;
|
|
147
|
+
return ref.name ?? 'Unknown';
|
|
148
|
+
}
|
|
149
|
+
/** The named type at the bottom of LIST/NON_NULL wrappers. */
|
|
150
|
+
export function namedType(ref) {
|
|
151
|
+
let cur = ref;
|
|
152
|
+
while (cur && (cur.kind === 'NON_NULL' || cur.kind === 'LIST')) {
|
|
153
|
+
cur = cur.ofType ?? null;
|
|
154
|
+
}
|
|
155
|
+
return cur?.name ?? null;
|
|
156
|
+
}
|
|
157
|
+
const BUILTIN_SCALARS = new Set([
|
|
158
|
+
'String',
|
|
159
|
+
'Int',
|
|
160
|
+
'Float',
|
|
161
|
+
'Boolean',
|
|
162
|
+
'ID',
|
|
163
|
+
'JSON'
|
|
164
|
+
]);
|
|
165
|
+
export function isBuiltinScalar(name) {
|
|
166
|
+
return !!name && BUILTIN_SCALARS.has(name);
|
|
167
|
+
}
|
|
168
|
+
function oneLine(text, max) {
|
|
169
|
+
if (!text)
|
|
170
|
+
return '';
|
|
171
|
+
const flat = text.replace(/\s+/g, ' ').trim();
|
|
172
|
+
return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
|
|
173
|
+
}
|
|
174
|
+
function renderField(field, isInput) {
|
|
175
|
+
const args = !isInput && field.args?.length
|
|
176
|
+
? `(${field.args.map((a) => `${a.name}: ${renderTypeRef(a.type)}`).join(', ')})`
|
|
177
|
+
: '';
|
|
178
|
+
const def = isInput && field.defaultValue != null ? ` = ${field.defaultValue}` : '';
|
|
179
|
+
const comment = field.description
|
|
180
|
+
? ` # ${oneLine(field.description, 140)}`
|
|
181
|
+
: '';
|
|
182
|
+
return ` ${field.name}${args}: ${renderTypeRef(field.type)}${def}${comment}`;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Compact SDL for one type. Returns the text and the named types it references
|
|
186
|
+
* (so callers can expand the next level).
|
|
187
|
+
*/
|
|
188
|
+
export function renderType(type) {
|
|
189
|
+
const references = new Set();
|
|
190
|
+
const collect = (ref) => {
|
|
191
|
+
const n = namedType(ref);
|
|
192
|
+
if (n && !isBuiltinScalar(n) && n !== type.name)
|
|
193
|
+
references.add(n);
|
|
194
|
+
};
|
|
195
|
+
const header = type.description
|
|
196
|
+
? `# ${oneLine(type.description, 200)}\n`
|
|
197
|
+
: '';
|
|
198
|
+
let body;
|
|
199
|
+
switch (type.kind) {
|
|
200
|
+
case 'ENUM':
|
|
201
|
+
body = `enum ${type.name} {\n${(type.enumValues ?? [])
|
|
202
|
+
.map((v) => ` ${v.name}${v.description ? ` # ${oneLine(v.description, 100)}` : ''}`)
|
|
203
|
+
.join('\n')}\n}`;
|
|
204
|
+
break;
|
|
205
|
+
case 'INPUT_OBJECT':
|
|
206
|
+
(type.inputFields ?? []).forEach((f) => collect(f.type));
|
|
207
|
+
body = `input ${type.name} {\n${(type.inputFields ?? [])
|
|
208
|
+
.map((f) => renderField(f, true))
|
|
209
|
+
.join('\n')}\n}`;
|
|
210
|
+
break;
|
|
211
|
+
case 'UNION':
|
|
212
|
+
(type.possibleTypes ?? []).forEach((p) => references.add(p.name));
|
|
213
|
+
body = `union ${type.name} = ${(type.possibleTypes ?? []).map((p) => p.name).join(' | ')}`;
|
|
214
|
+
break;
|
|
215
|
+
case 'SCALAR':
|
|
216
|
+
body = `scalar ${type.name}`;
|
|
217
|
+
break;
|
|
218
|
+
default: {
|
|
219
|
+
(type.fields ?? []).forEach((f) => {
|
|
220
|
+
collect(f.type);
|
|
221
|
+
f.args?.forEach((a) => collect(a.type));
|
|
222
|
+
});
|
|
223
|
+
const keyword = type.kind === 'INTERFACE' ? 'interface' : 'type';
|
|
224
|
+
body = `${keyword} ${type.name} {\n${(type.fields ?? [])
|
|
225
|
+
.map((f) => renderField(f, false))
|
|
226
|
+
.join('\n')}\n}`;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return { text: header + body, references: [...references] };
|
|
230
|
+
}
|
|
231
|
+
/** One-line signature of a root operation. */
|
|
232
|
+
export function renderOperationSignature(op) {
|
|
233
|
+
const args = op.args
|
|
234
|
+
.map((a) => `${a.name}: ${renderTypeRef(a.type)}`)
|
|
235
|
+
.join(', ');
|
|
236
|
+
return `${op.kind === 'query' ? 'Query' : 'Mutation'}.${op.name}(${args}): ${renderTypeRef(op.type)}`;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Breadth-first expansion of `rootTypeNames`. Level 1 is the given names,
|
|
240
|
+
* level 2 the types they reference, and so on up to `depth`. Anything
|
|
241
|
+
* referenced but not expanded is returned as `unexpanded` for a follow-up.
|
|
242
|
+
*/
|
|
243
|
+
export async function expandTypes(rootTypeNames, options = {}) {
|
|
244
|
+
const depth = Math.max(1, Math.min(options.depth ?? 2, 5));
|
|
245
|
+
const maxTypes = options.maxTypes ?? 40;
|
|
246
|
+
const rendered = [];
|
|
247
|
+
const unknown = [];
|
|
248
|
+
const seen = new Set();
|
|
249
|
+
let frontier = [...new Set(rootTypeNames)].filter((n) => !isBuiltinScalar(n));
|
|
250
|
+
let level = 1;
|
|
251
|
+
let overflow = [];
|
|
252
|
+
while (frontier.length > 0 && level <= depth) {
|
|
253
|
+
const room = maxTypes - seen.size;
|
|
254
|
+
if (room <= 0) {
|
|
255
|
+
overflow = frontier;
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
const batch = frontier.slice(0, room);
|
|
259
|
+
overflow = frontier.slice(room);
|
|
260
|
+
batch.forEach((n) => seen.add(n));
|
|
261
|
+
const types = await getTypes(batch, options);
|
|
262
|
+
const next = new Set();
|
|
263
|
+
for (const name of batch) {
|
|
264
|
+
const type = types.get(name);
|
|
265
|
+
if (!type) {
|
|
266
|
+
unknown.push(name);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const { text, references } = renderType(type);
|
|
270
|
+
rendered.push(text);
|
|
271
|
+
references.forEach((r) => {
|
|
272
|
+
if (!seen.has(r))
|
|
273
|
+
next.add(r);
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
frontier = [...next];
|
|
277
|
+
level++;
|
|
278
|
+
}
|
|
279
|
+
const unexpanded = [...new Set([...overflow, ...frontier])].filter((n) => !seen.has(n) && !isBuiltinScalar(n));
|
|
280
|
+
return { rendered, unexpanded, unknown };
|
|
281
|
+
}
|
|
282
|
+
//# sourceMappingURL=introspection.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type RootOperation } from './introspection.js';
|
|
2
|
+
export interface OperationMatch {
|
|
3
|
+
operation: string;
|
|
4
|
+
kind: 'query' | 'mutation';
|
|
5
|
+
/** `name(arg: Type): ReturnType` */
|
|
6
|
+
signature: string;
|
|
7
|
+
description: string;
|
|
8
|
+
inputType: string | null;
|
|
9
|
+
returnType: string | null;
|
|
10
|
+
score: number;
|
|
11
|
+
}
|
|
12
|
+
/** Split camelCase / digits / punctuation into lowercase tokens. */
|
|
13
|
+
export declare function tokenize(text: string): string[];
|
|
14
|
+
/** Crude singularisation so "products" matches "product" and vice versa. */
|
|
15
|
+
export declare function stem(token: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Rank root operations for a free-text intent. Name-token matches weigh most
|
|
18
|
+
* (the operation name encodes app, service, version, verb and entity), then
|
|
19
|
+
* description matches; exact full-name matches win outright.
|
|
20
|
+
*/
|
|
21
|
+
export declare function searchOperations(ops: RootOperation[], query: string, options?: {
|
|
22
|
+
kind?: 'query' | 'mutation' | 'all';
|
|
23
|
+
maxResults?: number;
|
|
24
|
+
}): OperationMatch[];
|
|
25
|
+
//# sourceMappingURL=search.d.ts.map
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { namedType, renderTypeRef } from './introspection.js';
|
|
2
|
+
/** Split camelCase / digits / punctuation into lowercase tokens. */
|
|
3
|
+
export function tokenize(text) {
|
|
4
|
+
return text
|
|
5
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
6
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
7
|
+
.replace(/([a-zA-Z])([0-9])/g, '$1 $2')
|
|
8
|
+
.replace(/([0-9])([a-zA-Z])/g, '$1 $2')
|
|
9
|
+
.toLowerCase()
|
|
10
|
+
.split(/[^a-z0-9]+/)
|
|
11
|
+
.filter((t) => t.length > 0);
|
|
12
|
+
}
|
|
13
|
+
const STOP_WORDS = new Set([
|
|
14
|
+
'a',
|
|
15
|
+
'an',
|
|
16
|
+
'the',
|
|
17
|
+
'of',
|
|
18
|
+
'for',
|
|
19
|
+
'to',
|
|
20
|
+
'in',
|
|
21
|
+
'on',
|
|
22
|
+
'with',
|
|
23
|
+
'and',
|
|
24
|
+
'or',
|
|
25
|
+
'by',
|
|
26
|
+
'all',
|
|
27
|
+
'my',
|
|
28
|
+
'me',
|
|
29
|
+
'i',
|
|
30
|
+
'from',
|
|
31
|
+
'is',
|
|
32
|
+
'are',
|
|
33
|
+
'be',
|
|
34
|
+
'it',
|
|
35
|
+
'this',
|
|
36
|
+
'that',
|
|
37
|
+
'wix',
|
|
38
|
+
'api',
|
|
39
|
+
'graphql'
|
|
40
|
+
]);
|
|
41
|
+
/** Crude singularisation so "products" matches "product" and vice versa. */
|
|
42
|
+
export function stem(token) {
|
|
43
|
+
if (token.length > 4 && token.endsWith('ies'))
|
|
44
|
+
return `${token.slice(0, -3)}y`;
|
|
45
|
+
if (token.length > 3 && token.endsWith('es') && !token.endsWith('ses'))
|
|
46
|
+
return token.slice(0, -1);
|
|
47
|
+
if (token.length > 3 && token.endsWith('s') && !token.endsWith('ss'))
|
|
48
|
+
return token.slice(0, -1);
|
|
49
|
+
return token;
|
|
50
|
+
}
|
|
51
|
+
/** Common intent verbs → the verbs Wix operation names use. */
|
|
52
|
+
const VERB_SYNONYMS = {
|
|
53
|
+
list: ['query', 'list', 'search'],
|
|
54
|
+
find: ['query', 'search', 'get'],
|
|
55
|
+
fetch: ['get', 'query'],
|
|
56
|
+
read: ['get', 'query'],
|
|
57
|
+
show: ['get', 'query'],
|
|
58
|
+
count: ['count', 'query'],
|
|
59
|
+
add: ['create', 'add'],
|
|
60
|
+
new: ['create'],
|
|
61
|
+
insert: ['create'],
|
|
62
|
+
edit: ['update'],
|
|
63
|
+
change: ['update'],
|
|
64
|
+
modify: ['update'],
|
|
65
|
+
set: ['update'],
|
|
66
|
+
remove: ['delete', 'remove'],
|
|
67
|
+
destroy: ['delete'],
|
|
68
|
+
cancel: ['cancel', 'delete'],
|
|
69
|
+
customer: ['contact', 'member'],
|
|
70
|
+
client: ['contact', 'member'],
|
|
71
|
+
item: ['product', 'item'],
|
|
72
|
+
inventory: ['inventory', 'stock'],
|
|
73
|
+
stock: ['inventory', 'stock'],
|
|
74
|
+
post: ['post', 'blog'],
|
|
75
|
+
article: ['post', 'blog'],
|
|
76
|
+
appointment: ['booking', 'session'],
|
|
77
|
+
collection: ['collection', 'category', 'data'],
|
|
78
|
+
cms: ['data', 'collection'],
|
|
79
|
+
store: ['stores', 'store']
|
|
80
|
+
};
|
|
81
|
+
function expandQueryTokens(tokens) {
|
|
82
|
+
const out = new Set();
|
|
83
|
+
for (const raw of tokens) {
|
|
84
|
+
if (STOP_WORDS.has(raw))
|
|
85
|
+
continue;
|
|
86
|
+
const s = stem(raw);
|
|
87
|
+
out.add(s);
|
|
88
|
+
for (const syn of VERB_SYNONYMS[raw] ?? VERB_SYNONYMS[s] ?? []) {
|
|
89
|
+
out.add(stem(syn));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
const READ_VERBS = new Set([
|
|
95
|
+
'list',
|
|
96
|
+
'get',
|
|
97
|
+
'query',
|
|
98
|
+
'find',
|
|
99
|
+
'fetch',
|
|
100
|
+
'read',
|
|
101
|
+
'show',
|
|
102
|
+
'retrieve',
|
|
103
|
+
'search',
|
|
104
|
+
'count',
|
|
105
|
+
'view',
|
|
106
|
+
'see'
|
|
107
|
+
]);
|
|
108
|
+
const WRITE_VERBS = new Set([
|
|
109
|
+
'create',
|
|
110
|
+
'add',
|
|
111
|
+
'new',
|
|
112
|
+
'insert',
|
|
113
|
+
'update',
|
|
114
|
+
'edit',
|
|
115
|
+
'change',
|
|
116
|
+
'modify',
|
|
117
|
+
'set',
|
|
118
|
+
'delete',
|
|
119
|
+
'remove',
|
|
120
|
+
'destroy',
|
|
121
|
+
'cancel',
|
|
122
|
+
'bulk',
|
|
123
|
+
'merge',
|
|
124
|
+
'publish',
|
|
125
|
+
'send'
|
|
126
|
+
]);
|
|
127
|
+
/** Version tokens ("v", "3") carry no intent and are never penalised. */
|
|
128
|
+
const isVersionToken = (t) => t === 'v' || /^\d+$/.test(t);
|
|
129
|
+
/** Markdown links → their text, so descriptions read as plain prose. */
|
|
130
|
+
function stripMarkdown(text) {
|
|
131
|
+
return text.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1').replace(/[`*_]/g, '');
|
|
132
|
+
}
|
|
133
|
+
function firstSentence(text, max = 220) {
|
|
134
|
+
const flat = stripMarkdown(text).replace(/\s+/g, ' ').trim();
|
|
135
|
+
const cut = flat.search(/(?<=[.!?])\s/);
|
|
136
|
+
const sentence = cut > 0 ? flat.slice(0, cut) : flat;
|
|
137
|
+
return sentence.length > max ? `${sentence.slice(0, max - 1)}…` : sentence;
|
|
138
|
+
}
|
|
139
|
+
const indexCache = new WeakMap();
|
|
140
|
+
function indexOperations(ops) {
|
|
141
|
+
let indexed = indexCache.get(ops);
|
|
142
|
+
if (!indexed) {
|
|
143
|
+
indexed = ops.map((op) => ({
|
|
144
|
+
op,
|
|
145
|
+
nameTokens: new Set(tokenize(op.name).map(stem)),
|
|
146
|
+
descTokens: new Set(tokenize(op.description.slice(0, 600)).map(stem))
|
|
147
|
+
}));
|
|
148
|
+
indexCache.set(ops, indexed);
|
|
149
|
+
}
|
|
150
|
+
return indexed;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Rank root operations for a free-text intent. Name-token matches weigh most
|
|
154
|
+
* (the operation name encodes app, service, version, verb and entity), then
|
|
155
|
+
* description matches; exact full-name matches win outright.
|
|
156
|
+
*/
|
|
157
|
+
export function searchOperations(ops, query, options = {}) {
|
|
158
|
+
const { kind = 'all', maxResults = 10 } = options;
|
|
159
|
+
const rawTokens = tokenize(query);
|
|
160
|
+
const terms = expandQueryTokens(rawTokens);
|
|
161
|
+
const originalTerms = new Set(rawTokens.filter((t) => !STOP_WORDS.has(t)).map(stem));
|
|
162
|
+
const exactName = query.trim();
|
|
163
|
+
const hasWriteVerb = rawTokens.some((t) => WRITE_VERBS.has(t));
|
|
164
|
+
// "contacts" wants the list root, "contact" the single-entity root.
|
|
165
|
+
const nounTokens = rawTokens.filter((t) => !STOP_WORDS.has(t) && !READ_VERBS.has(t) && !WRITE_VERBS.has(t));
|
|
166
|
+
const pluralIntent = nounTokens.length > 0 && nounTokens.some((t) => stem(t) !== t);
|
|
167
|
+
// A read intent (or no verb at all) prefers query roots: in this schema a
|
|
168
|
+
// query root IS the read verb, so "list/get/find" are satisfied by the kind.
|
|
169
|
+
const readIntent = !hasWriteVerb;
|
|
170
|
+
const matches = [];
|
|
171
|
+
for (const { op, nameTokens, descTokens } of indexOperations(ops)) {
|
|
172
|
+
if (kind !== 'all' && op.kind !== kind)
|
|
173
|
+
continue;
|
|
174
|
+
let score = 0;
|
|
175
|
+
let originalHits = 0;
|
|
176
|
+
const matchedNameTokens = new Set();
|
|
177
|
+
for (const term of terms) {
|
|
178
|
+
const isOriginal = originalTerms.has(term);
|
|
179
|
+
if (op.kind === 'query' && readIntent && READ_VERBS.has(term)) {
|
|
180
|
+
// The query root itself is the "list/get" — count the verb as matched.
|
|
181
|
+
score += isOriginal ? 4 : 2;
|
|
182
|
+
if (isOriginal)
|
|
183
|
+
originalHits++;
|
|
184
|
+
if (nameTokens.has(term))
|
|
185
|
+
matchedNameTokens.add(term);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (nameTokens.has(term)) {
|
|
189
|
+
matchedNameTokens.add(term);
|
|
190
|
+
score += isOriginal ? 4 : 2;
|
|
191
|
+
if (isOriginal)
|
|
192
|
+
originalHits++;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
let prefixHit = false;
|
|
196
|
+
for (const nt of nameTokens) {
|
|
197
|
+
if (term.length >= 3 && (nt.startsWith(term) || term.startsWith(nt))) {
|
|
198
|
+
prefixHit = true;
|
|
199
|
+
matchedNameTokens.add(nt);
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (prefixHit) {
|
|
204
|
+
score += isOriginal ? 2 : 1;
|
|
205
|
+
if (isOriginal)
|
|
206
|
+
originalHits++;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (descTokens.has(term)) {
|
|
210
|
+
score += 1;
|
|
211
|
+
if (isOriginal)
|
|
212
|
+
originalHits++;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// Nothing matched: biases below must not surface unrelated operations.
|
|
216
|
+
if (score <= 0 && op.name !== exactName)
|
|
217
|
+
continue;
|
|
218
|
+
if (op.name === exactName)
|
|
219
|
+
score += 100;
|
|
220
|
+
if (originalTerms.size > 0 && originalHits === originalTerms.size) {
|
|
221
|
+
score += 3;
|
|
222
|
+
}
|
|
223
|
+
// Plural intent ↔ list root (plural last token); singular ↔ get-one root.
|
|
224
|
+
const lastToken = tokenize(op.name)
|
|
225
|
+
.filter((t) => !isVersionToken(t))
|
|
226
|
+
.pop();
|
|
227
|
+
if (lastToken && nounTokens.length > 0) {
|
|
228
|
+
const pluralOp = stem(lastToken) !== lastToken;
|
|
229
|
+
score += pluralOp === pluralIntent ? 0.5 : -0.5;
|
|
230
|
+
}
|
|
231
|
+
// Deprecated/internal duplicates ("DoNotCall") rank below their twins.
|
|
232
|
+
if (/DoNotCall/i.test(op.name))
|
|
233
|
+
score -= 2;
|
|
234
|
+
// Name tokens the intent never mentioned dilute the match (a "Facets" or
|
|
235
|
+
// "Labels" op is not what "contacts" asked for). App prefixes count too,
|
|
236
|
+
// equally for every op, so they don't reorder siblings.
|
|
237
|
+
for (const nt of nameTokens) {
|
|
238
|
+
if (!isVersionToken(nt) && !matchedNameTokens.has(nt))
|
|
239
|
+
score -= 0.3;
|
|
240
|
+
}
|
|
241
|
+
// A read intent almost never wants a create/update/delete mutation even
|
|
242
|
+
// when its name shares the nouns ("products with inventory" is not
|
|
243
|
+
// CreateProductWithInventory).
|
|
244
|
+
if (readIntent &&
|
|
245
|
+
op.kind === 'mutation' &&
|
|
246
|
+
[...nameTokens].some((t) => WRITE_VERBS.has(t))) {
|
|
247
|
+
score -= 6;
|
|
248
|
+
}
|
|
249
|
+
// Read intents lean to query roots; newer API versions and documented
|
|
250
|
+
// operations edge out older/undocumented twins with the same tokens.
|
|
251
|
+
if (op.kind === 'query' && readIntent)
|
|
252
|
+
score += 1;
|
|
253
|
+
const version = op.name.match(/V(\d+)/);
|
|
254
|
+
if (version)
|
|
255
|
+
score += Math.min(Number(version[1]), 5) * 0.1;
|
|
256
|
+
if (!op.description)
|
|
257
|
+
score -= 0.5;
|
|
258
|
+
// Shorter names are the canonical entry for a family of similar ops.
|
|
259
|
+
score -= op.name.length / 200;
|
|
260
|
+
if (score <= 0)
|
|
261
|
+
continue;
|
|
262
|
+
matches.push({
|
|
263
|
+
operation: op.name,
|
|
264
|
+
kind: op.kind,
|
|
265
|
+
signature: `${op.name}(${op.args
|
|
266
|
+
.map((a) => `${a.name}: ${renderTypeRef(a.type)}`)
|
|
267
|
+
.join(', ')}): ${renderTypeRef(op.type)}`,
|
|
268
|
+
description: firstSentence(op.description),
|
|
269
|
+
inputType: op.args[0] ? namedType(op.args[0].type) : null,
|
|
270
|
+
returnType: namedType(op.type),
|
|
271
|
+
score: Math.round(score * 100) / 100
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
matches.sort((a, b) => b.score - a.score || a.operation.localeCompare(b.operation));
|
|
275
|
+
return matches.slice(0, Math.max(1, Math.min(maxResults, 50)));
|
|
276
|
+
}
|
|
277
|
+
//# sourceMappingURL=search.js.map
|
package/build/dts/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { handleWixAPIResponse } from './tool-utils.js';
|
|
|
6
6
|
export { addDocsResources } from './resources/docs.js';
|
|
7
7
|
export { addSupportTool } from './support/index.js';
|
|
8
8
|
export { addGetProfileTool } from './account-tools/get-profile-tool.js';
|
|
9
|
-
export { addCreateSiteWidgetTool, addEditSiteWidgetTool, addPullSiteJobTool, addClaimSiteTool, addSiteCreationWidgetTools, addWixSiteCreationTool, addSearchSiteTemplatesTool, addCreateSiteFromTemplateTool, type SiteWidgetToolOptions } from './site-widget-tools/index.js';
|
|
9
|
+
export { addCreateSiteWidgetTool, addEditSiteWidgetTool, addPullSiteJobTool, addClaimSiteTool, addSuggestedDomainTool, addSiteCreationWidgetTools, addWixSiteCreationTool, addSearchSiteTemplatesTool, addCreateSiteFromTemplateTool, type SiteWidgetToolOptions } from './site-widget-tools/index.js';
|
|
10
10
|
export * from './wixel/index.js';
|
|
11
11
|
export { addImportClaudeDesignTool, type ImportClaudeDesignToolOptions } from './import-claude-design-tool/index.js';
|
|
12
12
|
export { IMPORT_CLAUDE_DESIGN_TOOL_NAME } from './import-claude-design-tool/constants.js';
|
|
@@ -23,6 +23,7 @@ export { fetchRecipesIndex, fetchArticle, RECIPES_INDEX_URL } from './docs/fetch
|
|
|
23
23
|
export { generateDocsNavigationTree, fetchMenuContent, MENU_CONTENT_THRESHOLD, normalizeMenuContent, summarizeLargeMenu, parseDirectChildren } from './docs/normalize-menu-content.js';
|
|
24
24
|
export { addCodeModeTools, addExecuteCodeModeTool, addSearchCodeModeTool, type CodeModeOptions } from './code-mode/index.js';
|
|
25
25
|
export { validateExecuteCode, EXECUTE_FUNCTION_EXPRESSION_HINT, type CodeValidationResult } from './code-mode/validation.js';
|
|
26
|
+
export { addGraphqlTools, resetGraphqlSchemaCache, GRAPHQL_TOOL_NAMES, SEARCH_GRAPHQL_TOOL_NAME, INSPECT_GRAPHQL_TOOL_NAME, EXECUTE_GRAPHQL_TOOL_NAME, WIX_GRAPHQL_ENDPOINT, type GraphqlToolsOptions, type GraphqlToolName } from './graphql/index.js';
|
|
26
27
|
export { defaultConfig, defaultToolDescriptions, defaultReadmeDocs, widgetSiteCreationConfig, SITE_CREATION_COPY, toWidget, executeCodeModeToolDescriptions, SYSTEM_REMINDER_GUIDELINES, SYSTEM_REMINDER, mergeConfig, type ModeDefaultConfig } from './config/default-config.js';
|
|
27
28
|
export * from './site-widget-tools/constants.js';
|
|
28
29
|
//# sourceMappingURL=index.d.ts.map
|
package/build/dts/index.js
CHANGED
|
@@ -6,7 +6,7 @@ export { handleWixAPIResponse } from './tool-utils.js';
|
|
|
6
6
|
export { addDocsResources } from './resources/docs.js';
|
|
7
7
|
export { addSupportTool } from './support/index.js';
|
|
8
8
|
export { addGetProfileTool } from './account-tools/get-profile-tool.js';
|
|
9
|
-
export { addCreateSiteWidgetTool, addEditSiteWidgetTool, addPullSiteJobTool, addClaimSiteTool, addSiteCreationWidgetTools, addWixSiteCreationTool, addSearchSiteTemplatesTool, addCreateSiteFromTemplateTool } from './site-widget-tools/index.js';
|
|
9
|
+
export { addCreateSiteWidgetTool, addEditSiteWidgetTool, addPullSiteJobTool, addClaimSiteTool, addSuggestedDomainTool, addSiteCreationWidgetTools, addWixSiteCreationTool, addSearchSiteTemplatesTool, addCreateSiteFromTemplateTool } from './site-widget-tools/index.js';
|
|
10
10
|
export * from './wixel/index.js';
|
|
11
11
|
export { addImportClaudeDesignTool } from './import-claude-design-tool/index.js';
|
|
12
12
|
export { IMPORT_CLAUDE_DESIGN_TOOL_NAME } from './import-claude-design-tool/constants.js';
|
|
@@ -27,6 +27,8 @@ export { generateDocsNavigationTree, fetchMenuContent, MENU_CONTENT_THRESHOLD, n
|
|
|
27
27
|
// Code mode (search + execute via sandboxed JS)
|
|
28
28
|
export { addCodeModeTools, addExecuteCodeModeTool, addSearchCodeModeTool } from './code-mode/index.js';
|
|
29
29
|
export { validateExecuteCode, EXECUTE_FUNCTION_EXPRESSION_HINT } from './code-mode/validation.js';
|
|
30
|
+
// GraphQL tools (search operations + inspect schema + execute)
|
|
31
|
+
export { addGraphqlTools, resetGraphqlSchemaCache, GRAPHQL_TOOL_NAMES, SEARCH_GRAPHQL_TOOL_NAME, INSPECT_GRAPHQL_TOOL_NAME, EXECUTE_GRAPHQL_TOOL_NAME, WIX_GRAPHQL_ENDPOINT } from './graphql/index.js';
|
|
30
32
|
// Config exports
|
|
31
33
|
export { defaultConfig, defaultToolDescriptions, defaultReadmeDocs, widgetSiteCreationConfig, SITE_CREATION_COPY, toWidget, executeCodeModeToolDescriptions, SYSTEM_REMINDER_GUIDELINES, SYSTEM_REMINDER, mergeConfig } from './config/default-config.js';
|
|
32
34
|
export * from './site-widget-tools/constants.js';
|