@hasna/experts 0.0.7 → 0.0.8
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/dist/cli/index.js +353 -81
- package/dist/contacts.d.ts +15 -1
- package/dist/contacts.d.ts.map +1 -1
- package/dist/crawl.d.ts.map +1 -1
- package/dist/crypto.d.ts +6 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/db.d.ts +16 -4
- package/dist/db.d.ts.map +1 -1
- package/dist/embed.d.ts +20 -2
- package/dist/embed.d.ts.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +302 -67
- package/dist/score.d.ts +28 -0
- package/dist/score.d.ts.map +1 -1
- package/dist/sdk.js +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +218 -68
- package/dist/sources/mentorcruise.d.ts +26 -23
- package/dist/sources/mentorcruise.d.ts.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,SAAS,EAAmC,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,SAAS,EAAmC,MAAM,OAAO,CAAC;AA4BnE,wBAAgB,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,OAAO,GAAG,QAAQ,CA6I5D;AAED,wBAAsB,WAAW,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAqDhF;AAED,wBAAgB,WAAW,CAAC,IAAI,SAAO,EAAE,IAAI,SAAS,wBAUrD"}
|
package/dist/server/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
+
var __require = import.meta.require;
|
|
3
4
|
|
|
4
5
|
// src/db.ts
|
|
5
6
|
import { Database } from "bun:sqlite";
|
|
@@ -49,6 +50,36 @@ function authorityScore(e, inputs = {}, weights = DEFAULT_WEIGHTS) {
|
|
|
49
50
|
const raw = weights.rating * rating + weights.reviews * reviews + weights.followers * followers + weights.featured * featured + weights.verified * verified + weights.recency * recency;
|
|
50
51
|
return Math.round(raw * 1000) / 10;
|
|
51
52
|
}
|
|
53
|
+
function pricePerHour(price, priceUnit) {
|
|
54
|
+
if (!price || price <= 0)
|
|
55
|
+
return /free/i.test(priceUnit) ? 0 : null;
|
|
56
|
+
const u = (priceUnit || "").toLowerCase();
|
|
57
|
+
const minMatch = u.match(/(\d+)\s*min/);
|
|
58
|
+
if (minMatch)
|
|
59
|
+
return Math.round(price * 60 / Number(minMatch[1]));
|
|
60
|
+
if (/per\s*min|\/\s*min|minute/.test(u))
|
|
61
|
+
return price * 60;
|
|
62
|
+
if (/hour|\/\s*hr|per\s*hr/.test(u))
|
|
63
|
+
return price;
|
|
64
|
+
if (/free/.test(u))
|
|
65
|
+
return 0;
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
var DEFAULT_BLEND = { semantic: 0.8, authority: 0.2 };
|
|
69
|
+
function blendScore(semantic, authority, w = DEFAULT_BLEND) {
|
|
70
|
+
const a = Math.max(0, Math.min(1, (authority || 0) / 100));
|
|
71
|
+
const s = Math.max(0, Math.min(1, semantic));
|
|
72
|
+
return w.semantic * s + w.authority * a;
|
|
73
|
+
}
|
|
74
|
+
function explainMatch(query, e) {
|
|
75
|
+
const q = ` ${(query || "").toLowerCase()} `;
|
|
76
|
+
const hit = (label) => {
|
|
77
|
+
const l = label.toLowerCase();
|
|
78
|
+
return q.includes(` ${l} `) || q.includes(`${l},`) || q.includes(`${l}.`) || new RegExp(`\\b${l.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(q);
|
|
79
|
+
};
|
|
80
|
+
const matched = [...e.topics, ...e.tags].filter(hit);
|
|
81
|
+
return [...new Set(matched)].slice(0, 6);
|
|
82
|
+
}
|
|
52
83
|
|
|
53
84
|
// src/embed.ts
|
|
54
85
|
var STOPWORDS = new Set([
|
|
@@ -139,11 +170,40 @@ class OpenAIEmbedder {
|
|
|
139
170
|
return data.data.map((d) => d.embedding);
|
|
140
171
|
}
|
|
141
172
|
}
|
|
142
|
-
|
|
143
|
-
|
|
173
|
+
|
|
174
|
+
class TransformersEmbedder {
|
|
175
|
+
id = "minilm-l6-v2";
|
|
176
|
+
dim = 384;
|
|
177
|
+
model = process.env.EXPERTS_EMBED_MODEL || "Xenova/all-MiniLM-L6-v2";
|
|
178
|
+
extractor = null;
|
|
179
|
+
async ensure() {
|
|
180
|
+
if (this.extractor)
|
|
181
|
+
return;
|
|
182
|
+
const { pipeline } = await import("@huggingface/transformers");
|
|
183
|
+
this.extractor = await pipeline("feature-extraction", this.model);
|
|
184
|
+
}
|
|
185
|
+
async embed(texts) {
|
|
186
|
+
await this.ensure();
|
|
187
|
+
const out = [];
|
|
188
|
+
for (const t of texts) {
|
|
189
|
+
const r = await this.extractor(t || " ", { pooling: "mean", normalize: true });
|
|
190
|
+
out.push(Array.from(r.data));
|
|
191
|
+
}
|
|
192
|
+
return out;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async function getEmbedder() {
|
|
196
|
+
const choice = process.env.EXPERTS_EMBEDDER;
|
|
197
|
+
if (choice === "openai" && process.env.OPENAI_API_KEY)
|
|
144
198
|
return new OpenAIEmbedder;
|
|
199
|
+
if (choice === "hash")
|
|
200
|
+
return new HashingEmbedder;
|
|
201
|
+
try {
|
|
202
|
+
await import("@huggingface/transformers");
|
|
203
|
+
return new TransformersEmbedder;
|
|
204
|
+
} catch {
|
|
205
|
+
return new HashingEmbedder;
|
|
145
206
|
}
|
|
146
|
-
return new HashingEmbedder;
|
|
147
207
|
}
|
|
148
208
|
function cosine(a, b) {
|
|
149
209
|
let dot = 0;
|
|
@@ -228,6 +288,48 @@ function clusterPersons(experts) {
|
|
|
228
288
|
return out;
|
|
229
289
|
}
|
|
230
290
|
|
|
291
|
+
// src/crypto.ts
|
|
292
|
+
import { createCipheriv, createDecipheriv, createHmac, scryptSync } from "crypto";
|
|
293
|
+
var PREFIX = "enc1:";
|
|
294
|
+
var cachedKey = null;
|
|
295
|
+
var cachedFrom = null;
|
|
296
|
+
function key2() {
|
|
297
|
+
const secret = process.env.OPEN_EXPERTS_KEY;
|
|
298
|
+
if (!secret)
|
|
299
|
+
return null;
|
|
300
|
+
if (cachedKey && cachedFrom === secret)
|
|
301
|
+
return cachedKey;
|
|
302
|
+
cachedKey = scryptSync(secret, "open-experts/contacts/v1", 32);
|
|
303
|
+
cachedFrom = secret;
|
|
304
|
+
return cachedKey;
|
|
305
|
+
}
|
|
306
|
+
function maybeEncrypt(plaintext) {
|
|
307
|
+
const k = key2();
|
|
308
|
+
if (!k || plaintext == null)
|
|
309
|
+
return plaintext;
|
|
310
|
+
if (plaintext.startsWith(PREFIX))
|
|
311
|
+
return plaintext;
|
|
312
|
+
const iv = createHmac("sha256", k).update(plaintext).digest().subarray(0, 12);
|
|
313
|
+
const cipher = createCipheriv("aes-256-gcm", k, iv);
|
|
314
|
+
const enc = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
315
|
+
const tag = cipher.getAuthTag();
|
|
316
|
+
return PREFIX + Buffer.concat([iv, tag, enc]).toString("base64");
|
|
317
|
+
}
|
|
318
|
+
function maybeDecrypt(stored) {
|
|
319
|
+
if (stored == null || !stored.startsWith(PREFIX))
|
|
320
|
+
return stored;
|
|
321
|
+
const k = key2();
|
|
322
|
+
if (!k)
|
|
323
|
+
return stored;
|
|
324
|
+
const raw = Buffer.from(stored.slice(PREFIX.length), "base64");
|
|
325
|
+
const iv = raw.subarray(0, 12);
|
|
326
|
+
const tag = raw.subarray(12, 28);
|
|
327
|
+
const enc = raw.subarray(28);
|
|
328
|
+
const decipher = createDecipheriv("aes-256-gcm", k, iv);
|
|
329
|
+
decipher.setAuthTag(tag);
|
|
330
|
+
return Buffer.concat([decipher.update(enc), decipher.final()]).toString("utf8");
|
|
331
|
+
}
|
|
332
|
+
|
|
231
333
|
// src/db.ts
|
|
232
334
|
function defaultDbPath() {
|
|
233
335
|
return process.env.OPEN_EXPERTS_DB || join(homedir(), ".hasna", "experts", "experts.db");
|
|
@@ -339,10 +441,11 @@ class ExpertsDB {
|
|
|
339
441
|
CREATE INDEX IF NOT EXISTS idx_contacts_expert ON contacts(source, source_id);
|
|
340
442
|
CREATE INDEX IF NOT EXISTS idx_contacts_status ON contacts(status);
|
|
341
443
|
|
|
342
|
-
-- Semantic search: one embedding vector per expert
|
|
444
|
+
-- Semantic search: one embedding vector per expert (text_hash enables
|
|
445
|
+
-- incremental re-embedding \u2014 skip unchanged experts).
|
|
343
446
|
CREATE TABLE IF NOT EXISTS vectors (
|
|
344
447
|
source TEXT NOT NULL, source_id TEXT NOT NULL,
|
|
345
|
-
embedder TEXT NOT NULL, dim INTEGER, vec BLOB,
|
|
448
|
+
embedder TEXT NOT NULL, dim INTEGER, vec BLOB, text_hash TEXT,
|
|
346
449
|
PRIMARY KEY (source, source_id)
|
|
347
450
|
);
|
|
348
451
|
|
|
@@ -364,6 +467,7 @@ class ExpertsDB {
|
|
|
364
467
|
`);
|
|
365
468
|
this.addColumnIfMissing("experts", "avatar_local", "TEXT");
|
|
366
469
|
this.addColumnIfMissing("experts", "authority", "REAL DEFAULT 0");
|
|
470
|
+
this.addColumnIfMissing("vectors", "text_hash", "TEXT");
|
|
367
471
|
}
|
|
368
472
|
addColumnIfMissing(table, column, type) {
|
|
369
473
|
const cols = this.db.query(`PRAGMA table_info(${table})`).all();
|
|
@@ -450,6 +554,7 @@ class ExpertsDB {
|
|
|
450
554
|
extra: JSON.parse(r.extra || "{}"),
|
|
451
555
|
avatarLocal: r.avatar_local || undefined,
|
|
452
556
|
authority: r.authority ?? 0,
|
|
557
|
+
pricePerHour: pricePerHour(r.price ?? 0, r.price_unit ?? ""),
|
|
453
558
|
crawledAt: r.crawled_at
|
|
454
559
|
};
|
|
455
560
|
}
|
|
@@ -573,11 +678,11 @@ class ExpertsDB {
|
|
|
573
678
|
sql += " ORDER BY name";
|
|
574
679
|
return this.db.query(sql).all(...params);
|
|
575
680
|
}
|
|
576
|
-
setMeta(
|
|
577
|
-
this.db.query("INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value").run(
|
|
681
|
+
setMeta(key3, value) {
|
|
682
|
+
this.db.query("INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value").run(key3, value);
|
|
578
683
|
}
|
|
579
|
-
getMeta(
|
|
580
|
-
const row = this.db.query("SELECT value FROM meta WHERE key = ?").get(
|
|
684
|
+
getMeta(key3) {
|
|
685
|
+
const row = this.db.query("SELECT value FROM meta WHERE key = ?").get(key3);
|
|
581
686
|
return row ? row.value : null;
|
|
582
687
|
}
|
|
583
688
|
stats(source) {
|
|
@@ -601,12 +706,12 @@ class ExpertsDB {
|
|
|
601
706
|
const nodeIds = new Map;
|
|
602
707
|
const insertNode = this.db.query("INSERT INTO kg_nodes (type, key, label) VALUES (?, ?, ?) ON CONFLICT(type, key) DO UPDATE SET label=excluded.label RETURNING id");
|
|
603
708
|
const insertEdge = this.db.query("INSERT OR REPLACE INTO kg_edges (src, dst, rel, weight) VALUES (?, ?, ?, ?)");
|
|
604
|
-
const node = (type,
|
|
605
|
-
const ck = `${type}\x00${
|
|
709
|
+
const node = (type, key3, label) => {
|
|
710
|
+
const ck = `${type}\x00${key3.toLowerCase()}`;
|
|
606
711
|
const cached = nodeIds.get(ck);
|
|
607
712
|
if (cached != null)
|
|
608
713
|
return cached;
|
|
609
|
-
const id = insertNode.get(type,
|
|
714
|
+
const id = insertNode.get(type, key3.toLowerCase(), label).id;
|
|
610
715
|
nodeIds.set(ck, id);
|
|
611
716
|
return id;
|
|
612
717
|
};
|
|
@@ -616,7 +721,7 @@ class ExpertsDB {
|
|
|
616
721
|
for (const topic of e.topics) {
|
|
617
722
|
insertEdge.run(eId, node("topic", topic, topic), "IN_TOPIC", 1);
|
|
618
723
|
}
|
|
619
|
-
const tweetText = this.recentTweets(e.source, e.sourceId, 30).map((t) => t.text).join(". ");
|
|
724
|
+
const tweetText = this.recentTweets(e.source, e.sourceId, 30).filter((t) => !t.isRetweet).map((t) => t.text).join(". ");
|
|
620
725
|
const tags = inferTags(expertText(e) + ". " + tweetText, vocabulary);
|
|
621
726
|
for (const tag of tags) {
|
|
622
727
|
insertEdge.run(eId, node("tag", tag, tag), "HAS_TAG", 1);
|
|
@@ -670,11 +775,11 @@ class ExpertsDB {
|
|
|
670
775
|
lastSeen: r.last_seen || ""
|
|
671
776
|
}));
|
|
672
777
|
}
|
|
673
|
-
expertFromNodeKey(
|
|
674
|
-
const idx =
|
|
778
|
+
expertFromNodeKey(key3) {
|
|
779
|
+
const idx = key3.indexOf(":");
|
|
675
780
|
if (idx < 0)
|
|
676
781
|
return null;
|
|
677
|
-
return this.get(
|
|
782
|
+
return this.get(key3.slice(idx + 1), key3.slice(0, idx));
|
|
678
783
|
}
|
|
679
784
|
findByNeeds(needs, opts = {}) {
|
|
680
785
|
const cleaned = needs.map((n) => n.trim().toLowerCase()).filter(Boolean);
|
|
@@ -801,6 +906,15 @@ class ExpertsDB {
|
|
|
801
906
|
};
|
|
802
907
|
}
|
|
803
908
|
replaceTweets(source, sourceId, tweets) {
|
|
909
|
+
const norm = (t) => (t || "").toLowerCase().replace(/^rt @\w+:\s*/, "").replace(/https?:\/\/\S+/g, "").replace(/[^a-z0-9 ]/g, "").replace(/\s+/g, " ").trim();
|
|
910
|
+
const seen = new Set;
|
|
911
|
+
const deduped = tweets.filter((t) => {
|
|
912
|
+
const k = norm(t.text);
|
|
913
|
+
if (!k || seen.has(k))
|
|
914
|
+
return false;
|
|
915
|
+
seen.add(k);
|
|
916
|
+
return true;
|
|
917
|
+
});
|
|
804
918
|
const tx = this.db.transaction((rows) => {
|
|
805
919
|
this.db.query("DELETE FROM tweets WHERE source = ? AND source_id = ?").run(source, sourceId);
|
|
806
920
|
const stmt = this.db.query(`
|
|
@@ -813,7 +927,7 @@ class ExpertsDB {
|
|
|
813
927
|
stmt.run(t.source, t.sourceId, t.tweetId, t.text, t.createdAt, t.retweetCount, t.replyCount, t.likeCount, t.quoteCount, t.impressionCount, t.isRetweet ? 1 : 0, t.isReply ? 1 : 0);
|
|
814
928
|
}
|
|
815
929
|
});
|
|
816
|
-
tx(
|
|
930
|
+
tx(deduped);
|
|
817
931
|
}
|
|
818
932
|
recentTweets(source, sourceId, limit = 10) {
|
|
819
933
|
const rows = this.db.query("SELECT * FROM tweets WHERE source = ? AND source_id = ? ORDER BY created_at DESC LIMIT ?").all(source, sourceId, limit);
|
|
@@ -931,17 +1045,24 @@ class ExpertsDB {
|
|
|
931
1045
|
const log = opts.onLog ?? (() => {});
|
|
932
1046
|
const experts = this.list({ source: opts.source });
|
|
933
1047
|
const batch = opts.batch ?? 64;
|
|
934
|
-
const stmt = this.db.query("INSERT OR REPLACE INTO vectors (source, source_id, embedder, dim, vec) VALUES (?, ?, ?, ?, ?)");
|
|
1048
|
+
const stmt = this.db.query("INSERT OR REPLACE INTO vectors (source, source_id, embedder, dim, vec, text_hash) VALUES (?, ?, ?, ?, ?, ?)");
|
|
1049
|
+
const existing = new Map(this.db.query("SELECT source, source_id, embedder, text_hash FROM vectors").all().map((r) => [`${r.source}:${r.source_id}`, { embedder: r.embedder, hash: r.text_hash || "" }]));
|
|
1050
|
+
const work = experts.map((e) => ({ e, text: expertEmbedText(e), hash: String(Bun.hash(expertEmbedText(e))) })).filter(({ e, hash }) => {
|
|
1051
|
+
if (opts.force)
|
|
1052
|
+
return true;
|
|
1053
|
+
const prev = existing.get(`${e.source}:${e.sourceId}`);
|
|
1054
|
+
return !prev || prev.embedder !== embedder.id || prev.hash !== hash;
|
|
1055
|
+
});
|
|
935
1056
|
let done = 0;
|
|
936
|
-
for (let i = 0;i <
|
|
937
|
-
const slice =
|
|
938
|
-
const vecs = await embedder.embed(slice.map((
|
|
1057
|
+
for (let i = 0;i < work.length; i += batch) {
|
|
1058
|
+
const slice = work.slice(i, i + batch);
|
|
1059
|
+
const vecs = await embedder.embed(slice.map((w) => w.text));
|
|
939
1060
|
const tx = this.db.transaction(() => {
|
|
940
|
-
slice.forEach((
|
|
1061
|
+
slice.forEach((w, j) => stmt.run(w.e.source, w.e.sourceId, embedder.id, embedder.dim, packVector(vecs[j]), w.hash));
|
|
941
1062
|
});
|
|
942
1063
|
tx();
|
|
943
1064
|
done += slice.length;
|
|
944
|
-
log(` embedded ${done}/${experts.length}`);
|
|
1065
|
+
log(` embedded ${done}/${work.length} (${experts.length - work.length} unchanged)`);
|
|
945
1066
|
}
|
|
946
1067
|
this.setMeta("embedder", embedder.id);
|
|
947
1068
|
this.setMeta("embedded_at", new Date().toISOString());
|
|
@@ -954,7 +1075,13 @@ class ExpertsDB {
|
|
|
954
1075
|
const where = opts.source ? "WHERE v.source = ?" : "";
|
|
955
1076
|
const params = opts.source ? [opts.source] : [];
|
|
956
1077
|
const rows = this.db.query(`SELECT e.*, v.vec AS _vec FROM vectors v JOIN experts e ON e.source=v.source AND e.source_id=v.source_id ${where}`).all(...params);
|
|
957
|
-
const
|
|
1078
|
+
const blend = opts.blend !== false;
|
|
1079
|
+
const scored = rows.map((r) => {
|
|
1080
|
+
const expert = this.rowToExpert(r);
|
|
1081
|
+
const semantic = cosine(queryVec, unpackVector(r._vec));
|
|
1082
|
+
const score = blend ? blendScore(semantic, expert.authority ?? 0) : semantic;
|
|
1083
|
+
return { expert, score, semantic };
|
|
1084
|
+
});
|
|
958
1085
|
scored.sort((a, b) => b.score - a.score);
|
|
959
1086
|
return scored.slice(0, opts.limit ?? 25);
|
|
960
1087
|
}
|
|
@@ -1044,7 +1171,7 @@ class ExpertsDB {
|
|
|
1044
1171
|
$source: c.source,
|
|
1045
1172
|
$source_id: c.sourceId,
|
|
1046
1173
|
$type: c.type,
|
|
1047
|
-
$value: c.value,
|
|
1174
|
+
$value: maybeEncrypt(c.value),
|
|
1048
1175
|
$label: c.label,
|
|
1049
1176
|
$provider: c.provider,
|
|
1050
1177
|
$confidence: c.confidence,
|
|
@@ -1054,7 +1181,7 @@ class ExpertsDB {
|
|
|
1054
1181
|
});
|
|
1055
1182
|
}
|
|
1056
1183
|
setContactStatus(source, sourceId, type, value, status) {
|
|
1057
|
-
this.db.query("UPDATE contacts SET status = ?, verified_at = ? WHERE source = ? AND source_id = ? AND type = ? AND value = ?").run(status, new Date().toISOString(), source, sourceId, type, value);
|
|
1184
|
+
this.db.query("UPDATE contacts SET status = ?, verified_at = ? WHERE source = ? AND source_id = ? AND type = ? AND value = ?").run(status, new Date().toISOString(), source, sourceId, type, maybeEncrypt(value));
|
|
1058
1185
|
}
|
|
1059
1186
|
contacts(source, sourceId) {
|
|
1060
1187
|
const rows = this.db.query("SELECT * FROM contacts WHERE source = ? AND source_id = ? ORDER BY type, confidence DESC").all(source, sourceId);
|
|
@@ -1062,7 +1189,7 @@ class ExpertsDB {
|
|
|
1062
1189
|
source: r.source,
|
|
1063
1190
|
sourceId: r.source_id,
|
|
1064
1191
|
type: r.type,
|
|
1065
|
-
value: r.value,
|
|
1192
|
+
value: maybeDecrypt(r.value),
|
|
1066
1193
|
label: r.label || "",
|
|
1067
1194
|
provider: r.provider || "",
|
|
1068
1195
|
confidence: r.confidence ?? 0,
|
|
@@ -1087,7 +1214,7 @@ class ExpertsDB {
|
|
|
1087
1214
|
source: r.source,
|
|
1088
1215
|
sourceId: r.source_id,
|
|
1089
1216
|
type: r.type,
|
|
1090
|
-
value: r.value,
|
|
1217
|
+
value: maybeDecrypt(r.value),
|
|
1091
1218
|
label: r.label || "",
|
|
1092
1219
|
provider: r.provider || "",
|
|
1093
1220
|
confidence: r.confidence ?? 0,
|
|
@@ -1397,34 +1524,37 @@ async function fetchJson(url, fetchFn, init = {}) {
|
|
|
1397
1524
|
}
|
|
1398
1525
|
|
|
1399
1526
|
// src/sources/mentorcruise.ts
|
|
1527
|
+
function stripHtml(s) {
|
|
1528
|
+
return (s || "").replace(/<[^>]+>/g, " ").replace(/&[a-z#0-9]+;/gi, " ").replace(/\s+/g, " ").trim();
|
|
1529
|
+
}
|
|
1400
1530
|
function normalizeMentor(m, crawledAt) {
|
|
1401
|
-
const
|
|
1531
|
+
const path = m.get_absolute_url || "";
|
|
1532
|
+
const slug = path.match(/\/mentor\/([^/]+)/)?.[1] || slugify(m.get_full_name || String(m.objectID ?? ""));
|
|
1402
1533
|
const socials = {};
|
|
1403
1534
|
if (m.twitter)
|
|
1404
1535
|
socials.twitter = m.twitter.startsWith("http") ? m.twitter : `https://x.com/${m.twitter}`;
|
|
1405
1536
|
if (m.linkedin)
|
|
1406
1537
|
socials.linkedin = m.linkedin;
|
|
1538
|
+
const price = m.all_prices?.length ? Math.min(...m.all_prices) : Math.round(m.avg_price_per_call ?? 0);
|
|
1407
1539
|
return makeExpert({
|
|
1408
1540
|
source: "mentorcruise",
|
|
1409
|
-
sourceId: String(m.
|
|
1541
|
+
sourceId: String(m.objectID ?? slug),
|
|
1410
1542
|
slug,
|
|
1411
|
-
url: `https://mentorcruise.com/mentor/${slug}/`,
|
|
1412
|
-
fullName: m.
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
rating: m.rating ?? 0,
|
|
1422
|
-
ratingCount: m.reviews_count ?? 0,
|
|
1423
|
-
verified: Boolean(m.verified),
|
|
1543
|
+
url: path ? `https://mentorcruise.com${path}` : `https://mentorcruise.com/mentor/${slug}/`,
|
|
1544
|
+
fullName: m.get_full_name ?? "",
|
|
1545
|
+
title: (m.cleaned_job_title ?? []).join(", "),
|
|
1546
|
+
bio: stripHtml(m.bio_formatted ?? ""),
|
|
1547
|
+
avatar: m.get_profile_picture ?? "",
|
|
1548
|
+
price,
|
|
1549
|
+
priceCurrency: "USD",
|
|
1550
|
+
priceUnit: price ? "per month" : "",
|
|
1551
|
+
rating: m.avg_rating_float_one_decimal ?? 0,
|
|
1552
|
+
ratingCount: m.number_of_reviews ?? 0,
|
|
1424
1553
|
featured: Boolean(m.is_top_mentor),
|
|
1425
|
-
topics: m.
|
|
1426
|
-
tags: m.
|
|
1554
|
+
topics: m.get_industries ?? [],
|
|
1555
|
+
tags: m.get_skills ?? [],
|
|
1427
1556
|
socials,
|
|
1557
|
+
extra: { company: m.company ?? "", location: m.get_location_display ?? "", avgPricePerCall: m.avg_price_per_call ?? 0 },
|
|
1428
1558
|
crawledAt
|
|
1429
1559
|
});
|
|
1430
1560
|
}
|
|
@@ -1434,44 +1564,61 @@ class MentorCruiseSource {
|
|
|
1434
1564
|
description = "MentorCruise \u2014 long-term mentorship from vetted mentors";
|
|
1435
1565
|
website = "https://mentorcruise.com";
|
|
1436
1566
|
fetchFn;
|
|
1437
|
-
|
|
1567
|
+
appId;
|
|
1568
|
+
apiKey;
|
|
1569
|
+
index;
|
|
1438
1570
|
pageSize;
|
|
1439
1571
|
constructor(opts = {}) {
|
|
1440
1572
|
this.fetchFn = opts.fetchFn ?? fetch;
|
|
1441
|
-
this.
|
|
1442
|
-
this.
|
|
1573
|
+
this.appId = opts.appId ?? process.env.MENTORCRUISE_ALGOLIA_APP_ID ?? "YD3XA4V91L";
|
|
1574
|
+
this.apiKey = opts.apiKey ?? process.env.MENTORCRUISE_ALGOLIA_API_KEY ?? "454b55a2e50bc884225318d99b0dad1a";
|
|
1575
|
+
this.index = opts.index ?? process.env.MENTORCRUISE_ALGOLIA_INDEX ?? "MentorProfile_prod";
|
|
1576
|
+
this.pageSize = opts.pageSize ?? 200;
|
|
1443
1577
|
}
|
|
1444
1578
|
async crawl(opts = {}) {
|
|
1445
1579
|
const log = opts.onLog ?? (() => {});
|
|
1446
1580
|
const crawledAt = new Date().toISOString();
|
|
1581
|
+
const url = `https://${this.appId}-dsn.algolia.net/1/indexes/${this.index}/query`;
|
|
1447
1582
|
const experts = [];
|
|
1448
1583
|
const tags = new Set;
|
|
1449
|
-
let
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1584
|
+
let page = 0;
|
|
1585
|
+
let pages = 1;
|
|
1586
|
+
while (page < pages) {
|
|
1587
|
+
let data;
|
|
1588
|
+
try {
|
|
1589
|
+
const res = await this.fetchFn(url, {
|
|
1590
|
+
method: "POST",
|
|
1591
|
+
headers: {
|
|
1592
|
+
"X-Algolia-Application-Id": this.appId,
|
|
1593
|
+
"X-Algolia-API-Key": this.apiKey,
|
|
1594
|
+
"Content-Type": "application/json"
|
|
1595
|
+
},
|
|
1596
|
+
body: JSON.stringify({ params: `hitsPerPage=${this.pageSize}&page=${page}` })
|
|
1597
|
+
});
|
|
1598
|
+
if (!res.ok)
|
|
1599
|
+
break;
|
|
1600
|
+
data = await res.json();
|
|
1601
|
+
} catch {
|
|
1454
1602
|
break;
|
|
1455
|
-
|
|
1456
|
-
|
|
1603
|
+
}
|
|
1604
|
+
pages = data.nbPages ?? 1;
|
|
1605
|
+
for (const hit of data.hits ?? []) {
|
|
1606
|
+
const e = normalizeMentor(hit, crawledAt);
|
|
1457
1607
|
experts.push(e);
|
|
1458
1608
|
for (const t of e.tags)
|
|
1459
1609
|
tags.add(t);
|
|
1460
1610
|
}
|
|
1461
|
-
|
|
1462
|
-
|
|
1611
|
+
log(` mentorcruise: ${experts.length}/${data.nbHits ?? "?"}`);
|
|
1612
|
+
page++;
|
|
1463
1613
|
if (opts.max && experts.length >= opts.max)
|
|
1464
1614
|
break;
|
|
1465
|
-
if (items.length < this.pageSize)
|
|
1466
|
-
break;
|
|
1467
1615
|
}
|
|
1468
1616
|
if (experts.length === 0) {
|
|
1469
|
-
log("mentorcruise:
|
|
1617
|
+
log("mentorcruise: Algolia returned nothing (set MENTORCRUISE_ALGOLIA_* or inject fetchFn).");
|
|
1470
1618
|
}
|
|
1471
|
-
const topics = [];
|
|
1472
1619
|
return {
|
|
1473
1620
|
experts: opts.max ? experts.slice(0, opts.max) : experts,
|
|
1474
|
-
topics,
|
|
1621
|
+
topics: [],
|
|
1475
1622
|
tags: [...tags].map((name) => ({ name, topic: "" })),
|
|
1476
1623
|
total: experts.length
|
|
1477
1624
|
};
|
|
@@ -1692,6 +1839,9 @@ async function crawlSource(db, sourceName, opts = {}) {
|
|
|
1692
1839
|
throw new Error(`Unknown source "${sourceName}". Run \`experts sources\` to list options.`);
|
|
1693
1840
|
}
|
|
1694
1841
|
const data = await source.crawl(opts);
|
|
1842
|
+
if (data.experts.length === 0 && db.count(source.name) > 0) {
|
|
1843
|
+
opts.onLog?.(`\u26A0 ${source.name} returned 0 experts but ${db.count(source.name)} are stored \u2014 possible API drift; not overwriting.`);
|
|
1844
|
+
}
|
|
1695
1845
|
const changes = db.recordChanges(source.name, data.experts);
|
|
1696
1846
|
db.upsertExperts(data.experts);
|
|
1697
1847
|
if (data.topics.length)
|
|
@@ -1861,11 +2011,12 @@ async function handleAsync(db, req) {
|
|
|
1861
2011
|
return json({ error: "missing q" }, 400);
|
|
1862
2012
|
if (db.vectorCount() === 0)
|
|
1863
2013
|
return json({ error: "no semantic index; run `experts embed`" }, 409);
|
|
1864
|
-
const [qv] = await getEmbedder().embed([q]);
|
|
1865
|
-
|
|
2014
|
+
const [qv] = await (await getEmbedder()).embed([q]);
|
|
2015
|
+
const results = db.semanticSearch(qv, {
|
|
1866
2016
|
source: url.searchParams.get("source") || undefined,
|
|
1867
2017
|
limit: num(url.searchParams.get("limit"))
|
|
1868
|
-
}));
|
|
2018
|
+
}).map((r) => ({ ...r, why: explainMatch(q, r.expert) }));
|
|
2019
|
+
return json(results);
|
|
1869
2020
|
}
|
|
1870
2021
|
if (url.pathname.replace(/\/+$/, "") === "/brief") {
|
|
1871
2022
|
const q = url.searchParams.get("q") || "";
|
|
@@ -1874,17 +2025,16 @@ async function handleAsync(db, req) {
|
|
|
1874
2025
|
if (db.vectorCount() === 0)
|
|
1875
2026
|
return json({ error: "no semantic index; run `experts embed`" }, 409);
|
|
1876
2027
|
const limit = num(url.searchParams.get("limit")) ?? 10;
|
|
1877
|
-
const [qv] = await getEmbedder().embed([q]);
|
|
2028
|
+
const [qv] = await (await getEmbedder()).embed([q]);
|
|
1878
2029
|
const raw = db.semanticSearch(qv, { source: url.searchParams.get("source") || undefined, limit: (limit + 5) * 4 });
|
|
1879
2030
|
const seen = new Set;
|
|
1880
|
-
const briefLc = q.toLowerCase();
|
|
1881
2031
|
const out = [];
|
|
1882
2032
|
for (const r of raw) {
|
|
1883
2033
|
const pid = db.personIdOf(r.expert.source, r.expert.sourceId);
|
|
1884
2034
|
if (seen.has(pid))
|
|
1885
2035
|
continue;
|
|
1886
2036
|
seen.add(pid);
|
|
1887
|
-
out.push({ ...r, why: r.expert
|
|
2037
|
+
out.push({ ...r, why: explainMatch(q, r.expert) });
|
|
1888
2038
|
if (out.length >= limit)
|
|
1889
2039
|
break;
|
|
1890
2040
|
}
|
|
@@ -1,45 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MentorCruise source adapter (mentorship marketplace).
|
|
3
3
|
*
|
|
4
|
-
* MentorCruise
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* stable, tested part.
|
|
4
|
+
* MentorCruise's directory is backed by an Algolia index (`MentorProfile_prod`).
|
|
5
|
+
* The public app id + search key are client-side (as Algolia search keys are),
|
|
6
|
+
* so we query the index directly and paginate. All of it is injectable for
|
|
7
|
+
* tests; normalization to the common Expert model is the stable, tested part.
|
|
8
8
|
*/
|
|
9
9
|
import type { CrawlData, Expert, Source, SourceCrawlOptions } from "../types";
|
|
10
|
+
/** A MentorCruise mentor as stored in Algolia. */
|
|
10
11
|
export interface RawMentor {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
12
|
+
objectID?: string | number;
|
|
13
|
+
get_full_name?: string;
|
|
14
|
+
cleaned_job_title?: string[];
|
|
15
|
+
company?: string;
|
|
16
|
+
bio_formatted?: string;
|
|
17
|
+
avg_price_per_call?: number;
|
|
18
|
+
all_prices?: number[];
|
|
19
|
+
avg_rating_float_one_decimal?: number;
|
|
20
|
+
number_of_reviews?: number;
|
|
21
|
+
get_industries?: string[];
|
|
22
|
+
get_skills?: string[];
|
|
23
|
+
get_location_display?: string;
|
|
24
|
+
get_absolute_url?: string;
|
|
25
|
+
get_profile_picture?: string;
|
|
24
26
|
is_top_mentor?: boolean;
|
|
25
|
-
verified?: boolean;
|
|
26
|
-
skills?: string[];
|
|
27
|
-
categories?: string[];
|
|
28
27
|
twitter?: string;
|
|
29
28
|
linkedin?: string;
|
|
30
29
|
}
|
|
31
|
-
/** Map a MentorCruise
|
|
30
|
+
/** Map a MentorCruise Algolia record to the common Expert shape. */
|
|
32
31
|
export declare function normalizeMentor(m: RawMentor, crawledAt?: string): Expert;
|
|
33
32
|
export declare class MentorCruiseSource implements Source {
|
|
34
33
|
readonly name = "mentorcruise";
|
|
35
34
|
readonly description = "MentorCruise \u2014 long-term mentorship from vetted mentors";
|
|
36
35
|
readonly website = "https://mentorcruise.com";
|
|
37
36
|
private fetchFn;
|
|
38
|
-
private
|
|
37
|
+
private appId;
|
|
38
|
+
private apiKey;
|
|
39
|
+
private index;
|
|
39
40
|
private pageSize;
|
|
40
41
|
constructor(opts?: {
|
|
41
42
|
fetchFn?: typeof fetch;
|
|
42
|
-
|
|
43
|
+
appId?: string;
|
|
44
|
+
apiKey?: string;
|
|
45
|
+
index?: string;
|
|
43
46
|
pageSize?: number;
|
|
44
47
|
});
|
|
45
48
|
crawl(opts?: SourceCrawlOptions): Promise<CrawlData>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mentorcruise.d.ts","sourceRoot":"","sources":["../../src/sources/mentorcruise.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,
|
|
1
|
+
{"version":3,"file":"mentorcruise.d.ts","sourceRoot":"","sources":["../../src/sources/mentorcruise.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAG9E,kDAAkD;AAClD,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,4BAA4B,CAAC,EAAE,MAAM,CAAC;IACtC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAMD,oEAAoE;AACpE,wBAAgB,eAAe,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CA4BxE;AAED,qBAAa,kBAAmB,YAAW,MAAM;IAC/C,QAAQ,CAAC,IAAI,kBAAkB;IAC/B,QAAQ,CAAC,WAAW,kEAA6D;IACjF,QAAQ,CAAC,OAAO,8BAA8B;IAC9C,OAAO,CAAC,OAAO,CAAe;IAC9B,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,QAAQ,CAAS;gBAEb,IAAI,GAAE;QAAE,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO;IAQ/G,KAAK,CAAC,IAAI,GAAE,kBAAuB,GAAG,OAAO,CAAC,SAAS,CAAC;CA6C/D"}
|
package/dist/types.d.ts
CHANGED
|
@@ -48,6 +48,8 @@ export interface Expert {
|
|
|
48
48
|
avatarLocal?: string;
|
|
49
49
|
/** Composite authority/relevance score (0..100), set by rescore(). */
|
|
50
50
|
authority?: number;
|
|
51
|
+
/** Comparable USD/hour rate derived from price + priceUnit (null if not hourly-comparable). */
|
|
52
|
+
pricePerHour?: number | null;
|
|
51
53
|
crawledAt: string;
|
|
52
54
|
}
|
|
53
55
|
/** X/Twitter profile snapshot captured during enrichment. */
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,iFAAiF;AACjF,MAAM,WAAW,MAAM;IACrB,kEAAkE;IAClE,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,0BAA0B;IAC1B,GAAG,EAAE,MAAM,CAAC;IAEZ,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IAEjB,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,2BAA2B;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IAEf,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;IAElB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IAEpB,QAAQ,EAAE,OAAO,CAAC;IAClB,iEAAiE;IACjE,QAAQ,EAAE,OAAO,CAAC;IAElB,iDAAiD;IACjD,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,gCAAgC;IAChC,IAAI,EAAE,MAAM,EAAE,CAAC;IAEf,gFAAgF;IAChF,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEhC,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAE/B,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,6DAA6D;AAC7D,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,EAAE,MAAM,CAAC;IACxB,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,iDAAiD;AACjD,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,yBAAyB;IACzB,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;IACxB,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,yBAAyB;IACzB,MAAM,EAAE,YAAY,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;IACvD,kDAAkD;IAClD,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,8BAA8B;AAC9B,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,sBAAsB;AACtB,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,0CAA0C;AAC1C,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,kDAAkD;AAClD,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,8DAA8D;IAC9D,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACxC,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,sDAAsD;IACtD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uBAAuB;IACvB,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B;AAED,yDAAyD;AACzD,MAAM,WAAW,MAAM;IACrB,iEAAiE;IACjE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yDAAyD;IACzD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,oBAAoB;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,oCAAoC;IACpC,KAAK,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;CACrD"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/experts",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "Crawl expert marketplaces (intro.co and more) into a local store, then query them via CLI or a remote HTTP API.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"README.md"
|
|
27
27
|
],
|
|
28
28
|
"scripts": {
|
|
29
|
-
"build": "rm -rf dist && bun build src/cli/index.ts --outdir dist/cli --target bun --external commander --external chalk && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/index.ts src/sdk.ts --outdir dist --target bun && tsc --emitDeclarationOnly --outDir dist",
|
|
29
|
+
"build": "rm -rf dist && bun build src/cli/index.ts --outdir dist/cli --target bun --external commander --external chalk --external @huggingface/transformers && bun build src/server/index.ts --outdir dist/server --target bun --external @huggingface/transformers && bun build src/index.ts src/sdk.ts --outdir dist --target bun --external @huggingface/transformers && tsc --emitDeclarationOnly --outDir dist",
|
|
30
30
|
"typecheck": "tsc --noEmit",
|
|
31
31
|
"test": "bun test",
|
|
32
32
|
"dev": "bun run src/cli/index.ts",
|
|
@@ -64,6 +64,9 @@
|
|
|
64
64
|
"chalk": "5.4.1",
|
|
65
65
|
"commander": "13.1.0"
|
|
66
66
|
},
|
|
67
|
+
"optionalDependencies": {
|
|
68
|
+
"@huggingface/transformers": "4.2.0"
|
|
69
|
+
},
|
|
67
70
|
"devDependencies": {
|
|
68
71
|
"@types/bun": "1.2.4",
|
|
69
72
|
"typescript": "5.7.3"
|