@hasna/connectors 1.2.1 → 1.3.1
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/bin/index.js +432 -10
- package/bin/mcp.js +548 -167
- package/bin/serve.js +119 -0
- package/connectors/connect-huggingface/src/api/client.ts +5 -0
- package/connectors/connect-huggingface/src/api/datasets.ts +73 -0
- package/connectors/connect-huggingface/src/api/index.ts +17 -19
- package/connectors/connect-huggingface/src/api/inference.ts +100 -0
- package/connectors/connect-huggingface/src/api/models.ts +66 -0
- package/connectors/connect-huggingface/src/api/spaces.ts +42 -0
- package/connectors/connect-huggingface/src/cli/index.ts +148 -36
- package/connectors/connect-openai/src/api/images.ts +52 -8
- package/dist/db/promotions.d.ts +5 -0
- package/dist/db/promotions.test.d.ts +1 -0
- package/dist/db/usage.d.ts +17 -0
- package/dist/db/usage.test.d.ts +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +234 -3
- package/dist/lib/fuzzy.d.ts +16 -0
- package/dist/lib/fuzzy.test.d.ts +1 -0
- package/dist/lib/registry.d.ts +22 -1
- package/dist/lib/synonyms.d.ts +12 -0
- package/dist/lib/synonyms.test.d.ts +1 -0
- package/package.json +1 -1
- package/connectors/connect-huggingface/src/api/example.ts +0 -48
|
@@ -188,55 +188,167 @@ configCmd
|
|
|
188
188
|
});
|
|
189
189
|
|
|
190
190
|
// ============================================
|
|
191
|
-
//
|
|
191
|
+
// Models Commands
|
|
192
192
|
// ============================================
|
|
193
|
-
const
|
|
194
|
-
.command('example')
|
|
195
|
-
.description('Example API commands (replace with HuggingFace commands)');
|
|
193
|
+
const modelsCmd = program.command('models').description('Search and browse HuggingFace models');
|
|
196
194
|
|
|
197
|
-
|
|
198
|
-
.command('
|
|
199
|
-
.description('
|
|
200
|
-
.
|
|
201
|
-
.
|
|
195
|
+
modelsCmd
|
|
196
|
+
.command('search')
|
|
197
|
+
.description('Search models')
|
|
198
|
+
.argument('[query]', 'Search query')
|
|
199
|
+
.option('--task <task>', 'Filter by task (text-generation, text2text-generation, etc)')
|
|
200
|
+
.option('--library <lib>', 'Filter by library (transformers, gguf, pytorch)')
|
|
201
|
+
.option('--author <author>', 'Filter by author')
|
|
202
|
+
.option('--sort <field>', 'Sort by: likes, downloads, trending, lastModified', 'trending')
|
|
203
|
+
.option('--limit <n>', 'Max results', '20')
|
|
204
|
+
.action(async (query, opts) => {
|
|
202
205
|
try {
|
|
203
206
|
const client = getClient();
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
207
|
+
const results = await client.models.search({
|
|
208
|
+
search: query, filter: opts.task, library: opts.library,
|
|
209
|
+
author: opts.author, sort: opts.sort, limit: parseInt(opts.limit),
|
|
210
|
+
});
|
|
211
|
+
print(results.map(m => ({ id: m.id, task: m.pipeline_tag, library: m.library_name, downloads: m.downloads, likes: m.likes })), getFormat(modelsCmd));
|
|
212
|
+
} catch (err) { error(String(err)); process.exit(1); }
|
|
210
213
|
});
|
|
211
214
|
|
|
212
|
-
|
|
215
|
+
modelsCmd
|
|
213
216
|
.command('get <id>')
|
|
214
|
-
.description('Get
|
|
215
|
-
.action(async (id
|
|
217
|
+
.description('Get model details (e.g. meta-llama/Meta-Llama-3-8B)')
|
|
218
|
+
.action(async (id) => {
|
|
216
219
|
try {
|
|
217
220
|
const client = getClient();
|
|
218
|
-
const result = await client.
|
|
219
|
-
print(result, getFormat(
|
|
220
|
-
} catch (err) {
|
|
221
|
-
error(String(err));
|
|
222
|
-
process.exit(1);
|
|
223
|
-
}
|
|
221
|
+
const result = await client.models.get(id);
|
|
222
|
+
print(result, getFormat(modelsCmd));
|
|
223
|
+
} catch (err) { error(String(err)); process.exit(1); }
|
|
224
224
|
});
|
|
225
225
|
|
|
226
|
-
|
|
227
|
-
.command('
|
|
228
|
-
.description('
|
|
229
|
-
.
|
|
230
|
-
.action(async (opts) => {
|
|
226
|
+
modelsCmd
|
|
227
|
+
.command('files <id>')
|
|
228
|
+
.description('List files in a model repo')
|
|
229
|
+
.action(async (id) => {
|
|
231
230
|
try {
|
|
232
231
|
const client = getClient();
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
232
|
+
const files = await client.models.files(id);
|
|
233
|
+
print(files.map(f => ({ name: f.rfilename, size: f.lfs?.size ?? f.size ?? null })), getFormat(modelsCmd));
|
|
234
|
+
} catch (err) { error(String(err)); process.exit(1); }
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// ============================================
|
|
238
|
+
// Inference Commands
|
|
239
|
+
// ============================================
|
|
240
|
+
const inferCmd = program.command('inference').description('Run model inference via HF Inference API');
|
|
241
|
+
|
|
242
|
+
inferCmd
|
|
243
|
+
.command('text-generation <model>')
|
|
244
|
+
.description('Generate text from a prompt')
|
|
245
|
+
.requiredOption('--prompt <text>', 'Input prompt')
|
|
246
|
+
.option('--max-tokens <n>', 'Max new tokens', '256')
|
|
247
|
+
.option('--temperature <t>', 'Temperature', '0.7')
|
|
248
|
+
.action(async (model, opts) => {
|
|
249
|
+
try {
|
|
250
|
+
const client = getClient();
|
|
251
|
+
const results = await client.inference.textGeneration(model, opts.prompt, {
|
|
252
|
+
max_new_tokens: parseInt(opts.maxTokens), temperature: parseFloat(opts.temperature),
|
|
253
|
+
});
|
|
254
|
+
print(results, getFormat(inferCmd));
|
|
255
|
+
} catch (err) { error(String(err)); process.exit(1); }
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
inferCmd
|
|
259
|
+
.command('chat <model>')
|
|
260
|
+
.description('Chat completion (for chat models)')
|
|
261
|
+
.requiredOption('--messages <json>', 'Messages JSON array')
|
|
262
|
+
.option('--max-tokens <n>', 'Max new tokens', '256')
|
|
263
|
+
.option('--temperature <t>', 'Temperature', '0.7')
|
|
264
|
+
.action(async (model, opts) => {
|
|
265
|
+
try {
|
|
266
|
+
const messages = JSON.parse(opts.messages);
|
|
267
|
+
const client = getClient();
|
|
268
|
+
const result = await client.inference.chat(model, messages, {
|
|
269
|
+
max_new_tokens: parseInt(opts.maxTokens), temperature: parseFloat(opts.temperature),
|
|
270
|
+
});
|
|
271
|
+
print(result, getFormat(inferCmd));
|
|
272
|
+
} catch (err) { error(String(err)); process.exit(1); }
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// ============================================
|
|
276
|
+
// Datasets Commands
|
|
277
|
+
// ============================================
|
|
278
|
+
const datasetsCmd = program.command('datasets').description('Search and browse HuggingFace datasets');
|
|
279
|
+
|
|
280
|
+
datasetsCmd
|
|
281
|
+
.command('search')
|
|
282
|
+
.description('Search datasets')
|
|
283
|
+
.argument('[query]', 'Search query')
|
|
284
|
+
.option('--author <author>', 'Filter by author')
|
|
285
|
+
.option('--sort <field>', 'Sort by: likes, downloads, trending', 'trending')
|
|
286
|
+
.option('--limit <n>', 'Max results', '20')
|
|
287
|
+
.action(async (query, opts) => {
|
|
288
|
+
try {
|
|
289
|
+
const client = getClient();
|
|
290
|
+
const results = await client.datasets.search({
|
|
291
|
+
search: query, author: opts.author, sort: opts.sort, limit: parseInt(opts.limit),
|
|
292
|
+
});
|
|
293
|
+
print(results.map(d => ({ id: d.id, downloads: d.downloads, likes: d.likes, tags: d.tags?.slice(0, 5) })), getFormat(datasetsCmd));
|
|
294
|
+
} catch (err) { error(String(err)); process.exit(1); }
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
datasetsCmd
|
|
298
|
+
.command('get <id>')
|
|
299
|
+
.description('Get dataset details')
|
|
300
|
+
.action(async (id) => {
|
|
301
|
+
try {
|
|
302
|
+
const client = getClient();
|
|
303
|
+
const result = await client.datasets.get(id);
|
|
304
|
+
print(result, getFormat(datasetsCmd));
|
|
305
|
+
} catch (err) { error(String(err)); process.exit(1); }
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
datasetsCmd
|
|
309
|
+
.command('preview <id>')
|
|
310
|
+
.description('Preview first N rows of a dataset')
|
|
311
|
+
.option('--split <split>', 'Dataset split', 'train')
|
|
312
|
+
.option('--rows <n>', 'Number of rows', '10')
|
|
313
|
+
.action(async (id, opts) => {
|
|
314
|
+
try {
|
|
315
|
+
const client = getClient();
|
|
316
|
+
const result = await client.datasets.preview(id, 'default', opts.split, parseInt(opts.rows));
|
|
317
|
+
print(result, getFormat(datasetsCmd));
|
|
318
|
+
} catch (err) { error(String(err)); process.exit(1); }
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// ============================================
|
|
322
|
+
// Spaces Commands
|
|
323
|
+
// ============================================
|
|
324
|
+
const spacesCmd = program.command('spaces').description('Search and browse HuggingFace Spaces');
|
|
325
|
+
|
|
326
|
+
spacesCmd
|
|
327
|
+
.command('search')
|
|
328
|
+
.description('Search spaces')
|
|
329
|
+
.argument('[query]', 'Search query')
|
|
330
|
+
.option('--author <author>', 'Filter by author')
|
|
331
|
+
.option('--sort <field>', 'Sort by: likes, trending', 'trending')
|
|
332
|
+
.option('--limit <n>', 'Max results', '20')
|
|
333
|
+
.action(async (query, opts) => {
|
|
334
|
+
try {
|
|
335
|
+
const client = getClient();
|
|
336
|
+
const results = await client.spaces.search({
|
|
337
|
+
search: query, author: opts.author, sort: opts.sort, limit: parseInt(opts.limit),
|
|
338
|
+
});
|
|
339
|
+
print(results.map(s => ({ id: s.id, sdk: s.sdk, likes: s.likes })), getFormat(spacesCmd));
|
|
340
|
+
} catch (err) { error(String(err)); process.exit(1); }
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
spacesCmd
|
|
344
|
+
.command('get <id>')
|
|
345
|
+
.description('Get space details')
|
|
346
|
+
.action(async (id) => {
|
|
347
|
+
try {
|
|
348
|
+
const client = getClient();
|
|
349
|
+
const result = await client.spaces.get(id);
|
|
350
|
+
print(result, getFormat(spacesCmd));
|
|
351
|
+
} catch (err) { error(String(err)); process.exit(1); }
|
|
240
352
|
});
|
|
241
353
|
|
|
242
354
|
// Parse and execute
|
|
@@ -5,8 +5,20 @@ import type {
|
|
|
5
5
|
ImageOptions,
|
|
6
6
|
} from '../types';
|
|
7
7
|
|
|
8
|
+
/** Models that use the new gpt-image API (different params than DALL-E) */
|
|
9
|
+
const GPT_IMAGE_MODELS = ['gpt-image-1'];
|
|
10
|
+
|
|
11
|
+
function isGptImage(model: string): boolean {
|
|
12
|
+
return GPT_IMAGE_MODELS.some((m) => model.startsWith(m));
|
|
13
|
+
}
|
|
14
|
+
|
|
8
15
|
/**
|
|
9
|
-
* Images API
|
|
16
|
+
* Images API — supports both DALL-E 3 and gpt-image-1
|
|
17
|
+
*
|
|
18
|
+
* gpt-image-1 differences:
|
|
19
|
+
* - Uses `output_format` instead of `response_format`
|
|
20
|
+
* - Does NOT support `style` parameter
|
|
21
|
+
* - Does NOT support `response_format` parameter
|
|
10
22
|
*/
|
|
11
23
|
export class ImagesApi {
|
|
12
24
|
constructor(private readonly client: OpenAIClient) {}
|
|
@@ -18,13 +30,29 @@ export class ImagesApi {
|
|
|
18
30
|
prompt: string,
|
|
19
31
|
options: ImageOptions = {}
|
|
20
32
|
): Promise<ImageResponse> {
|
|
33
|
+
const model = options.model || 'dall-e-3';
|
|
34
|
+
|
|
35
|
+
if (isGptImage(model)) {
|
|
36
|
+
// gpt-image-1: different parameter set
|
|
37
|
+
const request: Record<string, unknown> = {
|
|
38
|
+
model,
|
|
39
|
+
prompt,
|
|
40
|
+
n: options.n || 1,
|
|
41
|
+
};
|
|
42
|
+
if (options.size !== undefined) request.size = options.size;
|
|
43
|
+
if (options.quality !== undefined) request.quality = options.quality;
|
|
44
|
+
// gpt-image-1 uses output_format, not response_format. No style param.
|
|
45
|
+
request.output_format = 'url';
|
|
46
|
+
return this.client.post<ImageResponse>('/images/generations', request);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// DALL-E 3: original parameter set
|
|
21
50
|
const request: ImageGenerateRequest = {
|
|
22
|
-
model
|
|
51
|
+
model,
|
|
23
52
|
prompt,
|
|
24
53
|
n: options.n || 1,
|
|
25
54
|
response_format: 'url',
|
|
26
55
|
};
|
|
27
|
-
|
|
28
56
|
if (options.size !== undefined) request.size = options.size;
|
|
29
57
|
if (options.quality !== undefined) request.quality = options.quality;
|
|
30
58
|
if (options.style !== undefined) request.style = options.style;
|
|
@@ -54,22 +82,38 @@ export class ImagesApi {
|
|
|
54
82
|
prompt: string,
|
|
55
83
|
options: Omit<ImageOptions, 'n'> = {}
|
|
56
84
|
): Promise<string> {
|
|
85
|
+
const model = options.model || 'dall-e-3';
|
|
86
|
+
|
|
87
|
+
if (isGptImage(model)) {
|
|
88
|
+
const request: Record<string, unknown> = {
|
|
89
|
+
model,
|
|
90
|
+
prompt,
|
|
91
|
+
n: 1,
|
|
92
|
+
output_format: 'b64_json',
|
|
93
|
+
};
|
|
94
|
+
if (options.size !== undefined) request.size = options.size;
|
|
95
|
+
if (options.quality !== undefined) request.quality = options.quality;
|
|
96
|
+
|
|
97
|
+
const response = await this.client.post<ImageResponse>('/images/generations', request);
|
|
98
|
+
const b64 = response.data[0]?.b64_json;
|
|
99
|
+
if (!b64) throw new Error('No image data in response');
|
|
100
|
+
return b64;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// DALL-E 3
|
|
57
104
|
const request: ImageGenerateRequest = {
|
|
58
|
-
model
|
|
105
|
+
model,
|
|
59
106
|
prompt,
|
|
60
107
|
n: 1,
|
|
61
108
|
response_format: 'b64_json',
|
|
62
109
|
};
|
|
63
|
-
|
|
64
110
|
if (options.size !== undefined) request.size = options.size;
|
|
65
111
|
if (options.quality !== undefined) request.quality = options.quality;
|
|
66
112
|
if (options.style !== undefined) request.style = options.style;
|
|
67
113
|
|
|
68
114
|
const response = await this.client.post<ImageResponse>('/images/generations', request);
|
|
69
115
|
const b64 = response.data[0]?.b64_json;
|
|
70
|
-
if (!b64)
|
|
71
|
-
throw new Error('No image data in response');
|
|
72
|
-
}
|
|
116
|
+
if (!b64) throw new Error('No image data in response');
|
|
73
117
|
return b64;
|
|
74
118
|
}
|
|
75
119
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite";
|
|
2
|
+
export declare function promoteConnector(name: string, db?: Database): void;
|
|
3
|
+
export declare function demoteConnector(name: string, db?: Database): boolean;
|
|
4
|
+
export declare function getPromotedConnectors(db?: Database): string[];
|
|
5
|
+
export declare function isPromoted(name: string, db?: Database): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite";
|
|
2
|
+
export declare function logUsage(connector: string, action: string, agentId?: string, db?: Database): void;
|
|
3
|
+
export interface UsageStats {
|
|
4
|
+
connector: string;
|
|
5
|
+
total: number;
|
|
6
|
+
last7d: number;
|
|
7
|
+
last24h: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function getUsageStats(connector: string, db?: Database): UsageStats;
|
|
10
|
+
export interface TopConnector {
|
|
11
|
+
connector: string;
|
|
12
|
+
count: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function getTopConnectors(limit?: number, days?: number, db?: Database): TopConnector[];
|
|
15
|
+
/** Get usage counts as a Map for search context */
|
|
16
|
+
export declare function getUsageMap(days?: number, db?: Database): Map<string, number>;
|
|
17
|
+
export declare function cleanOldUsage(days?: number, db?: Database): number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,6 @@
|
|
|
7
7
|
* Or use the interactive CLI:
|
|
8
8
|
* npx @hasna/connectors
|
|
9
9
|
*/
|
|
10
|
-
export { CONNECTORS, CATEGORIES, getConnector, getConnectorsByCategory, searchConnectors, loadConnectorVersions, type ConnectorMeta, type Category, } from "./lib/registry.js";
|
|
10
|
+
export { CONNECTORS, CATEGORIES, getConnector, getConnectorsByCategory, searchConnectors, loadConnectorVersions, type ConnectorMeta, type Category, type ScoredResult, type SearchContext, } from "./lib/registry.js";
|
|
11
11
|
export { installConnector, installConnectors, getInstalledConnectors, removeConnector, connectorExists, getConnectorPath, getConnectorDocs, type InstallResult, type InstallOptions, type ConnectorDocs, } from "./lib/installer.js";
|
|
12
12
|
export { runConnectorCommand, getConnectorOperations, getConnectorCommandHelp, getConnectorCliPath, getConnectorsWithCli, type RunResult, } from "./lib/runner.js";
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,94 @@ var __require = import.meta.require;
|
|
|
5
5
|
import { existsSync, readFileSync } from "fs";
|
|
6
6
|
import { join, dirname } from "path";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
|
+
|
|
9
|
+
// src/lib/fuzzy.ts
|
|
10
|
+
function levenshtein(a, b) {
|
|
11
|
+
const m = a.length;
|
|
12
|
+
const n = b.length;
|
|
13
|
+
if (m === 0)
|
|
14
|
+
return n;
|
|
15
|
+
if (n === 0)
|
|
16
|
+
return m;
|
|
17
|
+
let prev = new Array(n + 1);
|
|
18
|
+
let curr = new Array(n + 1);
|
|
19
|
+
for (let j = 0;j <= n; j++)
|
|
20
|
+
prev[j] = j;
|
|
21
|
+
for (let i = 1;i <= m; i++) {
|
|
22
|
+
curr[0] = i;
|
|
23
|
+
for (let j = 1;j <= n; j++) {
|
|
24
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
25
|
+
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
26
|
+
}
|
|
27
|
+
[prev, curr] = [curr, prev];
|
|
28
|
+
}
|
|
29
|
+
return prev[n];
|
|
30
|
+
}
|
|
31
|
+
function bestFuzzyScore(token, candidates, maxDistance = 2) {
|
|
32
|
+
if (token.length < 3)
|
|
33
|
+
return 0;
|
|
34
|
+
let bestDist = maxDistance + 1;
|
|
35
|
+
for (const c of candidates) {
|
|
36
|
+
if (Math.abs(token.length - c.length) > maxDistance)
|
|
37
|
+
continue;
|
|
38
|
+
const d = levenshtein(token, c);
|
|
39
|
+
if (d < bestDist)
|
|
40
|
+
bestDist = d;
|
|
41
|
+
if (d === 0)
|
|
42
|
+
return maxDistance + 1;
|
|
43
|
+
}
|
|
44
|
+
if (bestDist > maxDistance)
|
|
45
|
+
return 0;
|
|
46
|
+
return maxDistance - bestDist + 1;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/lib/synonyms.ts
|
|
50
|
+
var SYNONYM_MAP = {
|
|
51
|
+
email: ["smtp", "mail", "inbox", "resend", "ses"],
|
|
52
|
+
chat: ["messaging", "im", "slack", "discord", "teams"],
|
|
53
|
+
sms: ["text", "twilio", "messaging"],
|
|
54
|
+
payment: ["billing", "invoicing", "commerce", "checkout", "stripe"],
|
|
55
|
+
payments: ["billing", "invoicing", "commerce", "checkout", "stripe"],
|
|
56
|
+
ecommerce: ["shop", "store", "commerce", "shopify"],
|
|
57
|
+
finance: ["banking", "accounting", "invoicing"],
|
|
58
|
+
crypto: ["blockchain", "web3", "wallet"],
|
|
59
|
+
ai: ["llm", "ml", "model", "gpt", "claude", "anthropic", "openai"],
|
|
60
|
+
llm: ["ai", "model", "gpt", "claude"],
|
|
61
|
+
auth: ["oauth", "sso", "login", "identity", "authentication"],
|
|
62
|
+
database: ["db", "sql", "nosql", "postgres", "mongo", "supabase"],
|
|
63
|
+
deploy: ["hosting", "infrastructure", "ci", "cd", "vercel"],
|
|
64
|
+
storage: ["files", "drive", "s3", "bucket", "upload"],
|
|
65
|
+
cloud: ["aws", "gcp", "azure", "infrastructure"],
|
|
66
|
+
api: ["rest", "graphql", "endpoint", "webhook"],
|
|
67
|
+
monitoring: ["logs", "observability", "alerting", "datadog", "sentry"],
|
|
68
|
+
ci: ["cd", "deploy", "pipeline", "github", "actions"],
|
|
69
|
+
crm: ["sales", "leads", "contacts", "hubspot", "salesforce"],
|
|
70
|
+
analytics: ["data", "metrics", "tracking", "mixpanel", "amplitude"],
|
|
71
|
+
project: ["task", "issue", "board", "jira", "linear", "asana"],
|
|
72
|
+
docs: ["documentation", "wiki", "notion", "confluence"],
|
|
73
|
+
design: ["figma", "sketch", "ui", "ux"],
|
|
74
|
+
security: ["auth", "encryption", "compliance", "vault"]
|
|
75
|
+
};
|
|
76
|
+
function expandQuery(tokens) {
|
|
77
|
+
const synonyms = new Set;
|
|
78
|
+
for (const token of tokens) {
|
|
79
|
+
const matches = SYNONYM_MAP[token];
|
|
80
|
+
if (matches) {
|
|
81
|
+
for (const syn of matches) {
|
|
82
|
+
if (!tokens.includes(syn))
|
|
83
|
+
synonyms.add(syn);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
for (const [key, values] of Object.entries(SYNONYM_MAP)) {
|
|
87
|
+
if (values.includes(token) && !tokens.includes(key)) {
|
|
88
|
+
synonyms.add(key);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return { original: tokens, expanded: [...synonyms] };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/lib/registry.ts
|
|
8
96
|
var CATEGORIES = [
|
|
9
97
|
"AI & ML",
|
|
10
98
|
"Developer Tools",
|
|
@@ -5923,9 +6011,152 @@ var CONNECTORS = [
|
|
|
5923
6011
|
function getConnectorsByCategory(category) {
|
|
5924
6012
|
return CONNECTORS.filter((c) => c.category === category);
|
|
5925
6013
|
}
|
|
5926
|
-
function searchConnectors(query) {
|
|
5927
|
-
const
|
|
5928
|
-
|
|
6014
|
+
function searchConnectors(query, context) {
|
|
6015
|
+
const tokens = query.toLowerCase().trim().split(/\s+/).filter(Boolean);
|
|
6016
|
+
if (tokens.length === 0)
|
|
6017
|
+
return [];
|
|
6018
|
+
const limit = context?.limit ?? 20;
|
|
6019
|
+
const installed = new Set(context?.installed ?? []);
|
|
6020
|
+
const promoted = new Set(context?.promoted ?? []);
|
|
6021
|
+
const usage = context?.usage ?? new Map;
|
|
6022
|
+
const results = [];
|
|
6023
|
+
for (const c of CONNECTORS) {
|
|
6024
|
+
const nameLow = c.name.toLowerCase();
|
|
6025
|
+
const displayLow = c.displayName.toLowerCase();
|
|
6026
|
+
const descLow = c.description.toLowerCase();
|
|
6027
|
+
const tagsLow = c.tags.map((t) => t.toLowerCase());
|
|
6028
|
+
let score = 0;
|
|
6029
|
+
const matchReasons = [];
|
|
6030
|
+
let allTokensMatch = true;
|
|
6031
|
+
for (const token of tokens) {
|
|
6032
|
+
let tokenMatched = false;
|
|
6033
|
+
if (nameLow === token) {
|
|
6034
|
+
score += 100;
|
|
6035
|
+
matchReasons.push(`name="${token}"`);
|
|
6036
|
+
tokenMatched = true;
|
|
6037
|
+
} else if (nameLow.includes(token)) {
|
|
6038
|
+
score += 10;
|
|
6039
|
+
matchReasons.push(`name~${token}`);
|
|
6040
|
+
tokenMatched = true;
|
|
6041
|
+
}
|
|
6042
|
+
if (tagsLow.includes(token)) {
|
|
6043
|
+
score += 8;
|
|
6044
|
+
if (!tokenMatched)
|
|
6045
|
+
matchReasons.push(`tag="${token}"`);
|
|
6046
|
+
tokenMatched = true;
|
|
6047
|
+
} else if (tagsLow.some((t) => t.includes(token))) {
|
|
6048
|
+
score += 5;
|
|
6049
|
+
if (!tokenMatched)
|
|
6050
|
+
matchReasons.push(`tag~${token}`);
|
|
6051
|
+
tokenMatched = true;
|
|
6052
|
+
}
|
|
6053
|
+
if (displayLow.includes(token)) {
|
|
6054
|
+
score += 3;
|
|
6055
|
+
if (!tokenMatched)
|
|
6056
|
+
matchReasons.push(`display~${token}`);
|
|
6057
|
+
tokenMatched = true;
|
|
6058
|
+
}
|
|
6059
|
+
if (descLow.includes(token)) {
|
|
6060
|
+
score += 1;
|
|
6061
|
+
if (!tokenMatched)
|
|
6062
|
+
matchReasons.push(`desc~${token}`);
|
|
6063
|
+
tokenMatched = true;
|
|
6064
|
+
}
|
|
6065
|
+
if (!tokenMatched && token.length >= 3) {
|
|
6066
|
+
const nameFuzzy = bestFuzzyScore(token, [nameLow], 1);
|
|
6067
|
+
if (nameFuzzy > 0) {
|
|
6068
|
+
score += nameFuzzy * 6;
|
|
6069
|
+
matchReasons.push(`fuzzy:name\u2248${token}`);
|
|
6070
|
+
tokenMatched = true;
|
|
6071
|
+
}
|
|
6072
|
+
if (!tokenMatched) {
|
|
6073
|
+
const tagFuzzy = bestFuzzyScore(token, tagsLow, 2);
|
|
6074
|
+
if (tagFuzzy > 0) {
|
|
6075
|
+
score += tagFuzzy * 3;
|
|
6076
|
+
matchReasons.push(`fuzzy:tag\u2248${token}`);
|
|
6077
|
+
tokenMatched = true;
|
|
6078
|
+
}
|
|
6079
|
+
}
|
|
6080
|
+
if (!tokenMatched) {
|
|
6081
|
+
const displayFuzzy = bestFuzzyScore(token, [displayLow], 2);
|
|
6082
|
+
if (displayFuzzy > 0) {
|
|
6083
|
+
score += displayFuzzy * 2;
|
|
6084
|
+
matchReasons.push(`fuzzy:display\u2248${token}`);
|
|
6085
|
+
tokenMatched = true;
|
|
6086
|
+
}
|
|
6087
|
+
}
|
|
6088
|
+
}
|
|
6089
|
+
if (!tokenMatched) {
|
|
6090
|
+
allTokensMatch = false;
|
|
6091
|
+
break;
|
|
6092
|
+
}
|
|
6093
|
+
}
|
|
6094
|
+
if (!allTokensMatch)
|
|
6095
|
+
continue;
|
|
6096
|
+
const badges = [];
|
|
6097
|
+
if (installed.has(c.name)) {
|
|
6098
|
+
score += 50;
|
|
6099
|
+
badges.push("installed");
|
|
6100
|
+
}
|
|
6101
|
+
if (promoted.has(c.name)) {
|
|
6102
|
+
score += 30;
|
|
6103
|
+
badges.push("promoted");
|
|
6104
|
+
}
|
|
6105
|
+
const usageCount = usage.get(c.name) ?? 0;
|
|
6106
|
+
if (usageCount > 0) {
|
|
6107
|
+
score += Math.min(usageCount * 2, 40);
|
|
6108
|
+
if (usageCount >= 5)
|
|
6109
|
+
badges.push("hot");
|
|
6110
|
+
}
|
|
6111
|
+
results.push({ ...c, score, matchReasons, badges });
|
|
6112
|
+
}
|
|
6113
|
+
const matchedNames = new Set(results.map((r) => r.name));
|
|
6114
|
+
if (results.length < limit) {
|
|
6115
|
+
const { expanded } = expandQuery(tokens);
|
|
6116
|
+
if (expanded.length > 0) {
|
|
6117
|
+
for (const c of CONNECTORS) {
|
|
6118
|
+
if (matchedNames.has(c.name))
|
|
6119
|
+
continue;
|
|
6120
|
+
const nameLow2 = c.name.toLowerCase();
|
|
6121
|
+
const tagsLow2 = c.tags.map((t) => t.toLowerCase());
|
|
6122
|
+
const descLow2 = c.description.toLowerCase();
|
|
6123
|
+
let synScore = 0;
|
|
6124
|
+
const synReasons = [];
|
|
6125
|
+
for (const syn of expanded) {
|
|
6126
|
+
if (nameLow2.includes(syn)) {
|
|
6127
|
+
synScore += 2;
|
|
6128
|
+
synReasons.push(`syn:name~${syn}`);
|
|
6129
|
+
} else if (tagsLow2.some((t) => t.includes(syn))) {
|
|
6130
|
+
synScore += 1;
|
|
6131
|
+
synReasons.push(`syn:tag~${syn}`);
|
|
6132
|
+
} else if (descLow2.includes(syn)) {
|
|
6133
|
+
synScore += 1;
|
|
6134
|
+
synReasons.push(`syn:desc~${syn}`);
|
|
6135
|
+
}
|
|
6136
|
+
}
|
|
6137
|
+
if (synScore > 0) {
|
|
6138
|
+
const badges = [];
|
|
6139
|
+
if (installed.has(c.name)) {
|
|
6140
|
+
synScore += 50;
|
|
6141
|
+
badges.push("installed");
|
|
6142
|
+
}
|
|
6143
|
+
if (promoted.has(c.name)) {
|
|
6144
|
+
synScore += 30;
|
|
6145
|
+
badges.push("promoted");
|
|
6146
|
+
}
|
|
6147
|
+
const usageCount = usage.get(c.name) ?? 0;
|
|
6148
|
+
if (usageCount > 0) {
|
|
6149
|
+
synScore += Math.min(usageCount * 2, 40);
|
|
6150
|
+
if (usageCount >= 5)
|
|
6151
|
+
badges.push("hot");
|
|
6152
|
+
}
|
|
6153
|
+
results.push({ ...c, score: synScore, matchReasons: synReasons, badges });
|
|
6154
|
+
}
|
|
6155
|
+
}
|
|
6156
|
+
}
|
|
6157
|
+
}
|
|
6158
|
+
results.sort((a, b) => b.score - a.score);
|
|
6159
|
+
return results.slice(0, limit);
|
|
5929
6160
|
}
|
|
5930
6161
|
function getConnector(name) {
|
|
5931
6162
|
return CONNECTORS.find((c) => c.name === name);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Levenshtein distance for fuzzy connector search.
|
|
3
|
+
* No dependencies — pure implementation.
|
|
4
|
+
*/
|
|
5
|
+
/** Compute edit distance between two strings */
|
|
6
|
+
export declare function levenshtein(a: string, b: string): number;
|
|
7
|
+
/**
|
|
8
|
+
* Check if token fuzzy-matches target within maxDistance.
|
|
9
|
+
* Only applies to tokens >= 3 chars to avoid false positives.
|
|
10
|
+
*/
|
|
11
|
+
export declare function fuzzyMatch(token: string, target: string, maxDistance?: number): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Find best fuzzy match score for a token against a list of candidates.
|
|
14
|
+
* Returns 0 if no match within threshold.
|
|
15
|
+
*/
|
|
16
|
+
export declare function bestFuzzyScore(token: string, candidates: string[], maxDistance?: number): number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/lib/registry.d.ts
CHANGED
|
@@ -13,6 +13,27 @@ export declare const CATEGORIES: readonly ["AI & ML", "Developer Tools", "Design
|
|
|
13
13
|
export type Category = (typeof CATEGORIES)[number];
|
|
14
14
|
export declare const CONNECTORS: ConnectorMeta[];
|
|
15
15
|
export declare function getConnectorsByCategory(category: Category): ConnectorMeta[];
|
|
16
|
-
export
|
|
16
|
+
export interface SearchContext {
|
|
17
|
+
installed?: string[];
|
|
18
|
+
promoted?: string[];
|
|
19
|
+
usage?: Map<string, number>;
|
|
20
|
+
}
|
|
21
|
+
export interface ScoredResult extends ConnectorMeta {
|
|
22
|
+
score: number;
|
|
23
|
+
matchReasons: string[];
|
|
24
|
+
badges: string[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Ranked connector search with multi-token AND, scoring, and context signals.
|
|
28
|
+
*
|
|
29
|
+
* Score formula:
|
|
30
|
+
* relevance (name/tag/displayName/description matches)
|
|
31
|
+
* + installed boost (+50)
|
|
32
|
+
* + promoted boost (+30)
|
|
33
|
+
* + usage boost (min(count * 2, 40))
|
|
34
|
+
*/
|
|
35
|
+
export declare function searchConnectors(query: string, context?: SearchContext & {
|
|
36
|
+
limit?: number;
|
|
37
|
+
}): ScoredResult[];
|
|
17
38
|
export declare function getConnector(name: string): ConnectorMeta | undefined;
|
|
18
39
|
export declare function loadConnectorVersions(): void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synonym expansion for connector search.
|
|
3
|
+
* Domain-specific terms only — not a general thesaurus.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Expand search tokens with domain synonyms.
|
|
7
|
+
* Returns original tokens + synonym tokens (deduplicated).
|
|
8
|
+
*/
|
|
9
|
+
export declare function expandQuery(tokens: string[]): {
|
|
10
|
+
original: string[];
|
|
11
|
+
expanded: string[];
|
|
12
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|