terminalhire 0.3.5 → 0.4.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/dist/bin/jpi-bounties.js +963 -202
- package/dist/bin/jpi-dispatch.js +995 -209
- package/dist/bin/jpi-jobs.js +970 -204
- package/dist/bin/jpi-learn.js +98 -11
- package/dist/bin/jpi-login.js +986 -205
- package/dist/bin/jpi-profile.js +98 -11
- package/dist/bin/jpi-refresh.js +965 -204
- package/dist/bin/jpi-save.js +98 -11
- package/dist/bin/jpi-spinner.js +1 -1
- package/dist/bin/jpi-sync.js +98 -11
- package/dist/bin/spinner.js +2 -2
- package/dist/src/profile.js +40 -3
- package/dist/src/signal.js +40 -3
- package/package.json +1 -1
package/dist/bin/jpi-bounties.js
CHANGED
|
@@ -147,11 +147,11 @@ var init_graph_data = __esm({
|
|
|
147
147
|
{ id: "spark", parents: ["data-engineering"], synonyms: ["apache-spark"] },
|
|
148
148
|
{ id: "airflow", parents: ["data-engineering"], synonyms: ["apache-airflow"] },
|
|
149
149
|
{ id: "dbt", parents: ["data-engineering"] },
|
|
150
|
-
{ id: "ml", synonyms: ["machine-learning"], related: [{ to: "pytorch", w: 0.5 }, { to: "tensorflow", w: 0.5 }, { to: "scikit-learn", w: 0.5 }] },
|
|
151
|
-
{ id: "llm", parents: ["ml"], synonyms: ["llms", "genai", "generative-ai"], related: [{ to: "langchain", w: 0.5 }, { to: "rag", w: 0.55 }, { to: "openai", w: 0.45 }, { to: "anthropic", w: 0.45 }] },
|
|
150
|
+
{ id: "ml", synonyms: ["machine-learning"], related: [{ to: "pytorch", w: 0.5 }, { to: "tensorflow", w: 0.5 }, { to: "scikit-learn", w: 0.5 }, { to: "data-engineering", w: 0.4 }] },
|
|
151
|
+
{ id: "llm", parents: ["ml"], synonyms: ["llms", "genai", "generative-ai", "gpt"], related: [{ to: "langchain", w: 0.5 }, { to: "rag", w: 0.55 }, { to: "openai", w: 0.45 }, { to: "anthropic", w: 0.45 }] },
|
|
152
152
|
{ id: "pytorch", parents: ["ml"], synonyms: ["torch"], related: [{ to: "tensorflow", w: 0.5 }] },
|
|
153
153
|
{ id: "tensorflow", parents: ["ml"], synonyms: ["keras", "tf-keras"] },
|
|
154
|
-
{ id: "pandas", parents: ["python"], related: [{ to: "numpy", w: 0.6 }] },
|
|
154
|
+
{ id: "pandas", parents: ["python"], related: [{ to: "numpy", w: 0.6 }, { to: "data-engineering", w: 0.45 }, { to: "spark", w: 0.4 }] },
|
|
155
155
|
{ id: "numpy", parents: ["python"] },
|
|
156
156
|
{ id: "scikit-learn", parents: ["ml"], synonyms: ["sklearn"] },
|
|
157
157
|
{ id: "jupyter", parents: ["python"] },
|
|
@@ -161,6 +161,14 @@ var init_graph_data = __esm({
|
|
|
161
161
|
{ id: "anthropic", parents: ["llm"], synonyms: ["claude"] },
|
|
162
162
|
{ id: "rag", parents: ["llm"], synonyms: ["retrieval-augmented-generation"] },
|
|
163
163
|
{ id: "mlops", parents: ["ml"], related: [{ to: "devops", w: 0.4 }] },
|
|
164
|
+
{ id: "agents", parents: ["llm"], synonyms: ["agentic", "ai-agents", "multi-agent"], related: [{ to: "rag", w: 0.4 }] },
|
|
165
|
+
{ id: "mcp", parents: ["agents"], synonyms: ["model-context-protocol"], related: [{ to: "llm", w: 0.45 }] },
|
|
166
|
+
{ id: "inference", parents: ["ml"], synonyms: ["model-inference", "llm-inference", "model-serving"], related: [{ to: "mlops", w: 0.5 }, { to: "llm", w: 0.4 }] },
|
|
167
|
+
{ id: "embeddings", parents: ["ml"], synonyms: ["embedding", "vector-embeddings"], related: [{ to: "rag", w: 0.55 }, { to: "llm", w: 0.45 }] },
|
|
168
|
+
{ id: "prompt-engineering", parents: ["llm"], synonyms: ["prompting", "prompt"] },
|
|
169
|
+
{ id: "fine-tuning", parents: ["ml"], synonyms: ["finetuning", "fine-tune", "rlhf"], related: [{ to: "llm", w: 0.5 }] },
|
|
170
|
+
{ id: "computer-vision", parents: ["ml"], synonyms: ["image-recognition", "object-detection"] },
|
|
171
|
+
{ id: "recsys", parents: ["ml"], synonyms: ["recommender-systems", "recommendation-systems", "recommendation"] },
|
|
164
172
|
// ── Mobile ──────────────────────────────────────────────────────────────────
|
|
165
173
|
{ id: "mobile", related: [{ to: "ios", w: 0.5 }, { to: "android", w: 0.5 }] },
|
|
166
174
|
{ id: "ios", parents: ["mobile", "swift"], related: [{ to: "android", w: 0.4 }] },
|
|
@@ -326,6 +334,207 @@ var init_types2 = __esm({
|
|
|
326
334
|
}
|
|
327
335
|
});
|
|
328
336
|
|
|
337
|
+
// ../../packages/core/src/vocab/extract.ts
|
|
338
|
+
function tokenize(text) {
|
|
339
|
+
return text.toLowerCase().replace(/[^a-z0-9.\-+#]/g, " ").split(/\s+/).filter(Boolean);
|
|
340
|
+
}
|
|
341
|
+
function looksLikeEngRole(title) {
|
|
342
|
+
return !NON_ENG_TITLE.test(title) && ENG_INTENT.test(title);
|
|
343
|
+
}
|
|
344
|
+
function resolveToken(token) {
|
|
345
|
+
const tryOne = (t) => {
|
|
346
|
+
if (GRAPH.ids.has(t)) return { id: t, viaSynonym: false };
|
|
347
|
+
const mapped = GRAPH.synonyms.get(t);
|
|
348
|
+
return mapped ? { id: mapped, viaSynonym: true } : null;
|
|
349
|
+
};
|
|
350
|
+
return tryOne(token) ?? tryOne(token.replace(/^[.\-+#]+|[.\-+#]+$/g, ""));
|
|
351
|
+
}
|
|
352
|
+
function extractSkillTags(title, body = "") {
|
|
353
|
+
if (!looksLikeEngRole(title)) return [];
|
|
354
|
+
const text = `${title}
|
|
355
|
+
${body}`;
|
|
356
|
+
const tokens = tokenize(text);
|
|
357
|
+
const ids = /* @__PURE__ */ new Set();
|
|
358
|
+
const ambiguousPending = /* @__PURE__ */ new Set();
|
|
359
|
+
for (const tok of tokens) {
|
|
360
|
+
const r = resolveToken(tok);
|
|
361
|
+
if (!r) continue;
|
|
362
|
+
if (NON_EXTRACTABLE.has(r.id)) continue;
|
|
363
|
+
if (SYNONYM_ONLY.has(r.id) && !r.viaSynonym) continue;
|
|
364
|
+
const cue = AMBIGUOUS[r.id];
|
|
365
|
+
if (cue) {
|
|
366
|
+
if (cue.test(text)) ids.add(r.id);
|
|
367
|
+
else ambiguousPending.add(r.id);
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
ids.add(r.id);
|
|
371
|
+
}
|
|
372
|
+
const hardCount = [...ids].filter((id) => !SOFT_DOMAIN.has(id)).length;
|
|
373
|
+
if (hardCount >= 2) for (const id of ambiguousPending) ids.add(id);
|
|
374
|
+
return [...ids];
|
|
375
|
+
}
|
|
376
|
+
function coreTagsFromTitle(title) {
|
|
377
|
+
return extractSkillTags(title, "").filter((t) => !SOFT_DOMAIN.has(t));
|
|
378
|
+
}
|
|
379
|
+
var SOFT_DOMAIN, SYNONYM_ONLY, NON_EXTRACTABLE, AMBIGUOUS, ENG_INTENT, NON_ENG_TITLE;
|
|
380
|
+
var init_extract = __esm({
|
|
381
|
+
"../../packages/core/src/vocab/extract.ts"() {
|
|
382
|
+
"use strict";
|
|
383
|
+
init_vocab();
|
|
384
|
+
SOFT_DOMAIN = /* @__PURE__ */ new Set([
|
|
385
|
+
"frontend",
|
|
386
|
+
"backend",
|
|
387
|
+
"devops",
|
|
388
|
+
"security",
|
|
389
|
+
"payments",
|
|
390
|
+
"billing",
|
|
391
|
+
"microservices",
|
|
392
|
+
"caching",
|
|
393
|
+
"search",
|
|
394
|
+
"observability",
|
|
395
|
+
"monitoring",
|
|
396
|
+
"testing",
|
|
397
|
+
"accessibility",
|
|
398
|
+
"seo",
|
|
399
|
+
"performance",
|
|
400
|
+
"realtime",
|
|
401
|
+
"authentication",
|
|
402
|
+
"api-design"
|
|
403
|
+
]);
|
|
404
|
+
SYNONYM_ONLY = /* @__PURE__ */ new Set(["performance", "security", "seo"]);
|
|
405
|
+
NON_EXTRACTABLE = /* @__PURE__ */ new Set(["payments", "billing"]);
|
|
406
|
+
for (const id of SYNONYM_ONLY) {
|
|
407
|
+
if (!SOFT_DOMAIN.has(id)) throw new Error(`extract: SYNONYM_ONLY "${id}" not in SOFT_DOMAIN`);
|
|
408
|
+
}
|
|
409
|
+
AMBIGUOUS = {
|
|
410
|
+
// Accept "go" with an ecosystem cue OR an explicit-skill phrasing ("Go developer",
|
|
411
|
+
// "in Go", "experience with Go"). Rejects prose: "ready to go", "go above", "go live".
|
|
412
|
+
go: /\b(golang|goroutines?|go\.mod|gin framework|gorm)\b|\bgo\b\s+(developer|engineer|programmer|microservices?|backend|services?|lang)|\b(in|with|using|written in|built in|experience (?:in|with)|proficient in|fluent in)\s+go\b/i,
|
|
413
|
+
r: /\b(rstudio|tidyverse|ggplot|shiny|dplyr|cran|r-lang|rlang)\b/i,
|
|
414
|
+
ml: /\b(machine[\s-]?learning|pytorch|tensorflow|scikit|sklearn|keras|neural|model training|deep[\s-]?learning|numpy|pandas|ml\s+(?:engineer|platform|researcher|infrastructure)|(?:ml|ai)\s+research)\b/i
|
|
415
|
+
};
|
|
416
|
+
ENG_INTENT = /\b(engineer|engineering|developer|dev\b|swe|sde|programmer|architect|full[\s-]?stack|front[\s-]?end|back[\s-]?end|devops|sre|software|coding|codebase|technical staff|tech(?:nical)? lead)\b/i;
|
|
417
|
+
NON_ENG_TITLE = /\b(account executive|account manager|sales (?:rep|representative|development|manager|lead)|sdr|bdr|recruiter|recruiting|talent|marketing|administrative|business partner|billing coordinator|operations (?:administrator|coordinator)|customer success|project finance|controller|bookkeeper|graphic|brand)\b/i;
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// ../../packages/core/src/vocab/idf-background.ts
|
|
422
|
+
var IDF_BACKGROUND;
|
|
423
|
+
var init_idf_background = __esm({
|
|
424
|
+
"../../packages/core/src/vocab/idf-background.ts"() {
|
|
425
|
+
"use strict";
|
|
426
|
+
IDF_BACKGROUND = {
|
|
427
|
+
N: 244,
|
|
428
|
+
df: {
|
|
429
|
+
"backend": 71,
|
|
430
|
+
"python": 57,
|
|
431
|
+
"monitoring": 44,
|
|
432
|
+
"nextjs": 40,
|
|
433
|
+
"testing": 40,
|
|
434
|
+
"observability": 38,
|
|
435
|
+
"llm": 38,
|
|
436
|
+
"go": 36,
|
|
437
|
+
"aws": 36,
|
|
438
|
+
"react": 33,
|
|
439
|
+
"frontend": 30,
|
|
440
|
+
"ml": 28,
|
|
441
|
+
"mobile": 24,
|
|
442
|
+
"realtime": 24,
|
|
443
|
+
"typescript": 23,
|
|
444
|
+
"devops": 22,
|
|
445
|
+
"kubernetes": 22,
|
|
446
|
+
"javascript": 21,
|
|
447
|
+
"java": 20,
|
|
448
|
+
"rag": 20,
|
|
449
|
+
"api-design": 20,
|
|
450
|
+
"linux": 19,
|
|
451
|
+
"postgresql": 19,
|
|
452
|
+
"search": 17,
|
|
453
|
+
"azure": 16,
|
|
454
|
+
"snowflake": 15,
|
|
455
|
+
"spark": 15,
|
|
456
|
+
"kotlin": 14,
|
|
457
|
+
"gcp": 14,
|
|
458
|
+
"accessibility": 14,
|
|
459
|
+
"nodejs": 14,
|
|
460
|
+
"graphql": 14,
|
|
461
|
+
"airflow": 14,
|
|
462
|
+
"docker": 14,
|
|
463
|
+
"ci-cd": 13,
|
|
464
|
+
"android": 12,
|
|
465
|
+
"cpp": 12,
|
|
466
|
+
"gitlab-ci": 11,
|
|
467
|
+
"anthropic": 11,
|
|
468
|
+
"terraform": 11,
|
|
469
|
+
"mysql": 11,
|
|
470
|
+
"r": 10,
|
|
471
|
+
"dbt": 9,
|
|
472
|
+
"langchain": 9,
|
|
473
|
+
"pytorch": 9,
|
|
474
|
+
"ruby": 9,
|
|
475
|
+
"rails": 9,
|
|
476
|
+
"cloudflare": 7,
|
|
477
|
+
"datadog": 7,
|
|
478
|
+
"css": 7,
|
|
479
|
+
"ansible": 7,
|
|
480
|
+
"openai": 6,
|
|
481
|
+
"kafka": 6,
|
|
482
|
+
"rust": 5,
|
|
483
|
+
"grpc": 5,
|
|
484
|
+
"microservices": 5,
|
|
485
|
+
"serverless": 5,
|
|
486
|
+
"scala": 5,
|
|
487
|
+
"prometheus": 5,
|
|
488
|
+
"grafana": 5,
|
|
489
|
+
"php": 5,
|
|
490
|
+
"redis": 5,
|
|
491
|
+
"huggingface": 4,
|
|
492
|
+
"pandas": 4,
|
|
493
|
+
"scikit-learn": 4,
|
|
494
|
+
"html": 4,
|
|
495
|
+
"ios": 4,
|
|
496
|
+
"authentication": 4,
|
|
497
|
+
"vue": 4,
|
|
498
|
+
"mlops": 3,
|
|
499
|
+
"spring": 3,
|
|
500
|
+
"mongodb": 3,
|
|
501
|
+
"csharp": 3,
|
|
502
|
+
"swift": 2,
|
|
503
|
+
"caching": 2,
|
|
504
|
+
"haskell": 2,
|
|
505
|
+
"pulumi": 2,
|
|
506
|
+
"argocd": 2,
|
|
507
|
+
"tensorflow": 2,
|
|
508
|
+
"express": 2,
|
|
509
|
+
"elasticsearch": 2,
|
|
510
|
+
"clickhouse": 2,
|
|
511
|
+
"nestjs": 2,
|
|
512
|
+
"vite": 2,
|
|
513
|
+
"svelte": 2,
|
|
514
|
+
"phoenix": 2,
|
|
515
|
+
"angular": 2,
|
|
516
|
+
"django": 2,
|
|
517
|
+
"dotnet": 2,
|
|
518
|
+
"elixir": 2,
|
|
519
|
+
"bun": 1,
|
|
520
|
+
"oauth": 1,
|
|
521
|
+
"dynamodb": 1,
|
|
522
|
+
"helm": 1,
|
|
523
|
+
"playwright": 1,
|
|
524
|
+
"cypress": 1,
|
|
525
|
+
"jest": 1,
|
|
526
|
+
"mocha": 1,
|
|
527
|
+
"typeorm": 1,
|
|
528
|
+
"tailwind": 1,
|
|
529
|
+
"prisma": 1,
|
|
530
|
+
"expo": 1,
|
|
531
|
+
"rabbitmq": 1,
|
|
532
|
+
"redux": 1
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
|
|
329
538
|
// ../../packages/core/src/vocab/index.ts
|
|
330
539
|
function normalize(tokens) {
|
|
331
540
|
const result = /* @__PURE__ */ new Set();
|
|
@@ -362,6 +571,8 @@ var init_vocab = __esm({
|
|
|
362
571
|
init_types2();
|
|
363
572
|
init_closure();
|
|
364
573
|
init_graph_data();
|
|
574
|
+
init_extract();
|
|
575
|
+
init_idf_background();
|
|
365
576
|
GRAPH = buildGraph(VOCAB_NODES);
|
|
366
577
|
VOCABULARY = [...GRAPH.ids];
|
|
367
578
|
SYNONYMS = Object.fromEntries(GRAPH.synonyms);
|
|
@@ -376,23 +587,330 @@ var init_vocabulary = __esm({
|
|
|
376
587
|
}
|
|
377
588
|
});
|
|
378
589
|
|
|
379
|
-
// ../../packages/core/src/
|
|
380
|
-
function
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
590
|
+
// ../../packages/core/src/github.ts
|
|
591
|
+
function ghHeaders(token) {
|
|
592
|
+
const headers = {
|
|
593
|
+
Accept: "application/vnd.github+json",
|
|
594
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
595
|
+
// GitHub's REST API REQUIRES a User-Agent; serverless runtimes don't always
|
|
596
|
+
// send a default (omitting it yields a 403 "administrative rules" error).
|
|
597
|
+
"User-Agent": "terminalhire"
|
|
598
|
+
};
|
|
599
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
600
|
+
return headers;
|
|
601
|
+
}
|
|
602
|
+
async function ghFetch(path, token) {
|
|
603
|
+
const url = `https://api.github.com${path}`;
|
|
604
|
+
const res = await fetch(url, { headers: ghHeaders(token) });
|
|
605
|
+
if (!res.ok) {
|
|
606
|
+
throw new Error(`GitHub API ${path}: HTTP ${res.status} ${res.statusText}`);
|
|
607
|
+
}
|
|
608
|
+
return res.json();
|
|
609
|
+
}
|
|
610
|
+
async function fetchGitHubProfile(login, token) {
|
|
611
|
+
const user = await ghFetch(`/users/${login}`, token);
|
|
612
|
+
let repos = [];
|
|
613
|
+
try {
|
|
614
|
+
repos = await ghFetch(
|
|
615
|
+
`/users/${login}/repos?sort=pushed&per_page=100`,
|
|
616
|
+
token
|
|
617
|
+
);
|
|
618
|
+
} catch (err) {
|
|
619
|
+
console.warn(`[github] ${login}: repos fetch failed, continuing \u2014`, err);
|
|
620
|
+
}
|
|
621
|
+
const langCount = {};
|
|
622
|
+
for (const repo of repos) {
|
|
623
|
+
if (repo.fork) continue;
|
|
624
|
+
if (repo.language) {
|
|
625
|
+
langCount[repo.language.toLowerCase()] = (langCount[repo.language.toLowerCase()] ?? 0) + 1;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
const topLanguages = Object.entries(langCount).sort(([, a], [, b]) => b - a).slice(0, 10).map(([lang]) => lang);
|
|
629
|
+
const topicSet = /* @__PURE__ */ new Set();
|
|
630
|
+
for (const repo of repos) {
|
|
631
|
+
if (repo.fork) continue;
|
|
632
|
+
for (const t of repo.topics ?? []) topicSet.add(t.toLowerCase());
|
|
633
|
+
}
|
|
634
|
+
const topics = Array.from(topicSet).slice(0, 30);
|
|
635
|
+
let recentPRorgs;
|
|
636
|
+
try {
|
|
637
|
+
const q = encodeURIComponent(
|
|
638
|
+
`type:pr is:merged author:${login} sort:updated`
|
|
639
|
+
);
|
|
640
|
+
const result = await ghFetch(
|
|
641
|
+
`/search/issues?q=${q}&per_page=30`,
|
|
642
|
+
token
|
|
643
|
+
);
|
|
644
|
+
const orgs = /* @__PURE__ */ new Set();
|
|
645
|
+
for (const item of result.items ?? []) {
|
|
646
|
+
const orgLogin = item.repository?.owner?.login;
|
|
647
|
+
if (orgLogin && orgLogin !== login) orgs.add(orgLogin);
|
|
648
|
+
}
|
|
649
|
+
if (orgs.size > 0) recentPRorgs = Array.from(orgs);
|
|
650
|
+
} catch {
|
|
651
|
+
}
|
|
652
|
+
return {
|
|
653
|
+
login: user.login,
|
|
654
|
+
name: user.name ?? void 0,
|
|
655
|
+
publicEmail: user.email ?? void 0,
|
|
656
|
+
avatarUrl: user.avatar_url,
|
|
657
|
+
accountCreatedAt: user.created_at,
|
|
658
|
+
publicRepos: user.public_repos,
|
|
659
|
+
followers: user.followers,
|
|
660
|
+
topLanguages,
|
|
661
|
+
topics,
|
|
662
|
+
recentPRorgs
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
function inferSeniority(p) {
|
|
666
|
+
const ageMs = Date.now() - new Date(p.accountCreatedAt).getTime();
|
|
667
|
+
const ageYears = ageMs / (1e3 * 60 * 60 * 24 * 365.25);
|
|
668
|
+
if (ageYears >= 9 && (p.publicRepos >= 40 || p.followers >= 500)) return "staff";
|
|
669
|
+
if (ageYears >= 5 && (p.publicRepos >= 20 || p.followers >= 100)) return "senior";
|
|
670
|
+
if (ageYears >= 2 && p.publicRepos >= 5) return "mid";
|
|
671
|
+
return "junior";
|
|
672
|
+
}
|
|
673
|
+
function githubToFingerprint(p) {
|
|
674
|
+
const rawTokens = [
|
|
675
|
+
...p.topLanguages,
|
|
676
|
+
...p.topics
|
|
677
|
+
// recentPRorgs intentionally excluded — org names are not skill tags
|
|
678
|
+
];
|
|
679
|
+
const skillTags = normalize(rawTokens);
|
|
680
|
+
const seniorityBand = inferSeniority(p);
|
|
681
|
+
return { skillTags, seniorityBand };
|
|
682
|
+
}
|
|
683
|
+
async function ghFetchRaw(path, token) {
|
|
684
|
+
return fetch(`https://api.github.com${path}`, { headers: ghHeaders(token) });
|
|
685
|
+
}
|
|
686
|
+
function parseRepoUrl(repoUrl) {
|
|
687
|
+
const m = repoUrl.match(/\/repos\/([^/]+)\/([^/]+)\/?$/);
|
|
688
|
+
return m ? { owner: m[1], name: m[2] } : null;
|
|
689
|
+
}
|
|
690
|
+
function isTrivialPRTitle(title) {
|
|
691
|
+
return TRIVIAL_PR_TITLE.test(title);
|
|
692
|
+
}
|
|
693
|
+
async function fetchOwnedOrgs(token) {
|
|
694
|
+
try {
|
|
695
|
+
const memberships = await ghFetch(`/user/memberships/orgs?per_page=100`, token);
|
|
696
|
+
return new Set(
|
|
697
|
+
memberships.filter((m) => m.role === "admin").map((m) => m.organization.login.toLowerCase())
|
|
698
|
+
);
|
|
699
|
+
} catch {
|
|
700
|
+
return /* @__PURE__ */ new Set();
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
async function repoContributorCount(owner, name, token) {
|
|
704
|
+
try {
|
|
705
|
+
const res = await ghFetchRaw(
|
|
706
|
+
`/repos/${owner}/${name}/contributors?per_page=1&anon=false`,
|
|
707
|
+
token
|
|
708
|
+
);
|
|
709
|
+
if (!res.ok) return void 0;
|
|
710
|
+
const link = res.headers.get("link");
|
|
711
|
+
const m = link?.match(/[?&]page=(\d+)>;\s*rel="last"/);
|
|
712
|
+
if (m) return Number(m[1]);
|
|
713
|
+
const body = await res.json();
|
|
714
|
+
return Array.isArray(body) ? body.length : 0;
|
|
715
|
+
} catch {
|
|
716
|
+
return void 0;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
async function fetchRepoMeta(owner, name, token, cache) {
|
|
720
|
+
const key = `${owner}/${name}`.toLowerCase();
|
|
721
|
+
const cached = cache.get(key);
|
|
722
|
+
if (cached !== void 0) return cached;
|
|
723
|
+
let meta = null;
|
|
724
|
+
try {
|
|
725
|
+
const r = await ghFetch(`/repos/${owner}/${name}`, token);
|
|
726
|
+
const contributors = await repoContributorCount(owner, name, token);
|
|
727
|
+
meta = {
|
|
728
|
+
stars: r.stargazers_count ?? 0,
|
|
729
|
+
archived: !!r.archived,
|
|
730
|
+
fork: !!r.fork,
|
|
731
|
+
language: r.language ?? null,
|
|
732
|
+
topics: r.topics ?? [],
|
|
733
|
+
contributors
|
|
734
|
+
};
|
|
735
|
+
} catch {
|
|
736
|
+
meta = null;
|
|
737
|
+
}
|
|
738
|
+
cache.set(key, meta);
|
|
739
|
+
return meta;
|
|
740
|
+
}
|
|
741
|
+
function emptyCredential(status) {
|
|
742
|
+
return { status, byDomain: {}, qualifyingTotal: 0, computedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
743
|
+
}
|
|
744
|
+
async function fetchPublicOrgs(login, token) {
|
|
745
|
+
try {
|
|
746
|
+
const orgs = await ghFetch(
|
|
747
|
+
`/users/${login}/orgs?per_page=100`,
|
|
748
|
+
token
|
|
749
|
+
);
|
|
750
|
+
return new Set(orgs.map((o) => o.login.toLowerCase()));
|
|
751
|
+
} catch {
|
|
752
|
+
return /* @__PURE__ */ new Set();
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
async function computeAcceptanceFromSearch(login, token, ownedOrgs, cache, gates = {
|
|
756
|
+
minStars: MIN_STARS,
|
|
757
|
+
minContributors: MIN_CONTRIBUTORS
|
|
758
|
+
}) {
|
|
759
|
+
const computedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
760
|
+
const loginLc = login.toLowerCase();
|
|
761
|
+
let items;
|
|
762
|
+
try {
|
|
763
|
+
const q = encodeURIComponent(`type:pr is:merged author:${login} -user:${login} sort:updated`);
|
|
764
|
+
const res = await ghFetch(
|
|
765
|
+
`/search/issues?q=${q}&per_page=${CANDIDATE_PR_PAGE}`,
|
|
766
|
+
token
|
|
767
|
+
);
|
|
768
|
+
items = res.items ?? [];
|
|
769
|
+
} catch (err) {
|
|
770
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
771
|
+
console.warn("[acceptance] search failed:", msg);
|
|
772
|
+
return emptyCredential(/HTTP 403|HTTP 429|rate limit/i.test(msg) ? "rate-limited" : "failed");
|
|
773
|
+
}
|
|
774
|
+
const byDomain = {};
|
|
775
|
+
let qualifyingTotal = 0;
|
|
776
|
+
for (const item of items) {
|
|
777
|
+
const repo = parseRepoUrl(item.repository_url);
|
|
778
|
+
if (!repo) continue;
|
|
779
|
+
const ownerLc = repo.owner.toLowerCase();
|
|
780
|
+
if (ownerLc === loginLc) continue;
|
|
781
|
+
if (ownedOrgs.has(ownerLc)) continue;
|
|
782
|
+
if (isTrivialPRTitle(item.title)) continue;
|
|
783
|
+
const meta = await fetchRepoMeta(repo.owner, repo.name, token, cache);
|
|
784
|
+
if (!meta) continue;
|
|
785
|
+
if (meta.archived || meta.fork) continue;
|
|
786
|
+
if (meta.stars < gates.minStars) continue;
|
|
787
|
+
if (meta.contributors !== void 0 && meta.contributors < gates.minContributors) continue;
|
|
788
|
+
qualifyingTotal += 1;
|
|
789
|
+
const mergedAt = item.pull_request?.merged_at ?? item.closed_at ?? item.created_at;
|
|
790
|
+
const rawDomains = [meta.language ?? "", ...meta.topics].filter(Boolean);
|
|
791
|
+
for (const d of new Set(normalize(rawDomains))) {
|
|
792
|
+
const b = byDomain[d] ?? (byDomain[d] = { mergedPRs: 0, distinctOrgs: 0, lastMergedAt: mergedAt, orgs: /* @__PURE__ */ new Set() });
|
|
793
|
+
b.mergedPRs += 1;
|
|
794
|
+
b.orgs.add(ownerLc);
|
|
795
|
+
if (mergedAt > b.lastMergedAt) b.lastMergedAt = mergedAt;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
const finalDomains = {};
|
|
799
|
+
for (const [d, b] of Object.entries(byDomain)) {
|
|
800
|
+
finalDomains[d] = {
|
|
801
|
+
mergedPRs: b.mergedPRs,
|
|
802
|
+
distinctOrgs: b.orgs.size,
|
|
803
|
+
lastMergedAt: b.lastMergedAt
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
return { status: "ok", byDomain: finalDomains, qualifyingTotal, computedAt };
|
|
807
|
+
}
|
|
808
|
+
async function computeAcceptanceCredential(login, token, cache = /* @__PURE__ */ new Map()) {
|
|
809
|
+
if (!token) return emptyCredential("no-token");
|
|
810
|
+
const ownedOrgs = await fetchOwnedOrgs(token);
|
|
811
|
+
return computeAcceptanceFromSearch(login, token, ownedOrgs, cache);
|
|
812
|
+
}
|
|
813
|
+
async function computeAcceptanceCredentialPublic(login, token, cache = /* @__PURE__ */ new Map(), opts) {
|
|
814
|
+
if (!token) return emptyCredential("no-token");
|
|
815
|
+
const ownedOrgs = await fetchPublicOrgs(login, token);
|
|
816
|
+
for (const org of opts?.includeOrgs ?? []) ownedOrgs.delete(org.toLowerCase());
|
|
817
|
+
const gates = opts?.relaxGates ? { minStars: 0, minContributors: 0 } : void 0;
|
|
818
|
+
return computeAcceptanceFromSearch(login, token, ownedOrgs, cache, gates);
|
|
819
|
+
}
|
|
820
|
+
function acceptanceCountForDomains(cred, domains) {
|
|
821
|
+
if (cred.status !== "ok") return 0;
|
|
822
|
+
let max = 0;
|
|
823
|
+
for (const d of domains) {
|
|
824
|
+
const c = cred.byDomain[d]?.mergedPRs ?? 0;
|
|
825
|
+
if (c > max) max = c;
|
|
826
|
+
}
|
|
827
|
+
return max;
|
|
828
|
+
}
|
|
829
|
+
function bestAcceptanceDomain(cred, domains) {
|
|
830
|
+
if (cred.status !== "ok") return null;
|
|
831
|
+
let best = null;
|
|
832
|
+
for (const d of domains) {
|
|
833
|
+
const count = cred.byDomain[d]?.mergedPRs ?? 0;
|
|
834
|
+
if (count > 0 && (best === null || count > best.count)) best = { domain: d, count };
|
|
835
|
+
}
|
|
836
|
+
return best;
|
|
837
|
+
}
|
|
838
|
+
function resumeRecencyDecay(lastSeenIso, now) {
|
|
839
|
+
const ageMs = now - new Date(lastSeenIso).getTime();
|
|
840
|
+
if (!Number.isFinite(ageMs)) return 0;
|
|
841
|
+
return Math.pow(0.5, ageMs / RESUME_DECAY_HALF_LIFE_MS);
|
|
842
|
+
}
|
|
843
|
+
async function fetchRepoRecency(login, token) {
|
|
844
|
+
try {
|
|
845
|
+
const repos = await ghFetch(`/users/${login}/repos?sort=pushed&per_page=100`, token);
|
|
846
|
+
return repos.filter((r) => !r.fork && !!r.pushed_at).map((r) => ({ pushedAt: r.pushed_at, language: r.language ?? null, topics: r.topics ?? [] }));
|
|
847
|
+
} catch {
|
|
848
|
+
return [];
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
function deriveResumeTrend(cred, repoRecency, now = Date.now()) {
|
|
852
|
+
const agg = /* @__PURE__ */ new Map();
|
|
853
|
+
const bump = (domain, when, count, mergedPRs) => {
|
|
854
|
+
const e = agg.get(domain);
|
|
855
|
+
if (!e) {
|
|
856
|
+
agg.set(domain, { count, last: when, earliest: when, mergedPRs });
|
|
857
|
+
} else {
|
|
858
|
+
e.count += count;
|
|
859
|
+
e.mergedPRs += mergedPRs;
|
|
860
|
+
if (when > e.last) e.last = when;
|
|
861
|
+
if (when < e.earliest) e.earliest = when;
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
if (cred.status === "ok") {
|
|
865
|
+
for (const [domain, d] of Object.entries(cred.byDomain)) {
|
|
866
|
+
bump(domain, d.lastMergedAt, d.mergedPRs, d.mergedPRs);
|
|
387
867
|
}
|
|
388
868
|
}
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
869
|
+
for (const r of repoRecency) {
|
|
870
|
+
for (const domain of new Set(normalize([r.language ?? "", ...r.topics].filter(Boolean)))) {
|
|
871
|
+
bump(domain, r.pushedAt, 1, 0);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
const oneHalfLifeAgoIso = new Date(now - RESUME_DECAY_HALF_LIFE_MS).toISOString();
|
|
875
|
+
const scored = [];
|
|
876
|
+
for (const [domain, e] of agg.entries()) {
|
|
877
|
+
const recencyScore2 = resumeRecencyDecay(e.last, now);
|
|
878
|
+
const weight = e.count * recencyScore2;
|
|
879
|
+
if (weight < RESUME_MIN_SCORE) continue;
|
|
880
|
+
let direction;
|
|
881
|
+
if (e.earliest > oneHalfLifeAgoIso) direction = "new";
|
|
882
|
+
else if (recencyScore2 >= 0.5) direction = "up";
|
|
883
|
+
else direction = "down";
|
|
884
|
+
scored.push({
|
|
885
|
+
t: { domain, direction, recencyScore: Math.round(recencyScore2 * 1e3) / 1e3, mergedPRs: e.mergedPRs },
|
|
886
|
+
weight
|
|
887
|
+
});
|
|
392
888
|
}
|
|
393
|
-
return
|
|
889
|
+
return scored.sort((a, b) => b.weight - a.weight).slice(0, 12).map((s) => s.t);
|
|
394
890
|
}
|
|
395
|
-
|
|
891
|
+
var MIN_STARS, MIN_CONTRIBUTORS, CANDIDATE_PR_PAGE, TRIVIAL_PR_TITLE, RESUME_DECAY_HALF_LIFE_MS, RESUME_MIN_SCORE;
|
|
892
|
+
var init_github = __esm({
|
|
893
|
+
"../../packages/core/src/github.ts"() {
|
|
894
|
+
"use strict";
|
|
895
|
+
init_vocabulary();
|
|
896
|
+
MIN_STARS = 50;
|
|
897
|
+
MIN_CONTRIBUTORS = 10;
|
|
898
|
+
CANDIDATE_PR_PAGE = 50;
|
|
899
|
+
TRIVIAL_PR_TITLE = /^\s*(fix\s+typo|typo\b|update\s+readme|readme\b|docs?:|docs?\(|chore:|chore\(|style:|ci:|build:|bump\b|update\s+dependenc)/i;
|
|
900
|
+
RESUME_DECAY_HALF_LIFE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
901
|
+
RESUME_MIN_SCORE = 0.05;
|
|
902
|
+
}
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
// ../../packages/core/src/matcher.ts
|
|
906
|
+
function acceptanceDomainsOf(job) {
|
|
907
|
+
return job.coreTags && job.coreTags.length > 0 ? job.coreTags : job.tags;
|
|
908
|
+
}
|
|
909
|
+
function backgroundIdf(tag) {
|
|
910
|
+
const df = IDF_BACKGROUND.df[tag] ?? 0;
|
|
911
|
+
return Math.log((IDF_BACKGROUND.N + 1) / (df + 1)) + 1;
|
|
912
|
+
}
|
|
913
|
+
function inferSeniority2(title) {
|
|
396
914
|
if (!ENG_TITLE.test(title)) return void 0;
|
|
397
915
|
for (const [re, level] of SENIORITY_PATTERNS) {
|
|
398
916
|
if (re.test(title)) return level;
|
|
@@ -401,7 +919,7 @@ function inferSeniority(title) {
|
|
|
401
919
|
}
|
|
402
920
|
function seniorityScore(fp, job) {
|
|
403
921
|
if (!fp.seniorityBand) return 1;
|
|
404
|
-
const jobLevel =
|
|
922
|
+
const jobLevel = inferSeniority2(job.title);
|
|
405
923
|
if (!jobLevel) return 0.85;
|
|
406
924
|
const wanted = SENIORITY_RANK[fp.seniorityBand] ?? 1;
|
|
407
925
|
const got = SENIORITY_RANK[jobLevel] ?? 1;
|
|
@@ -411,8 +929,10 @@ function seniorityScore(fp, job) {
|
|
|
411
929
|
return 0.4;
|
|
412
930
|
}
|
|
413
931
|
function recencyScore(postedAt, now) {
|
|
414
|
-
if (!postedAt) return
|
|
415
|
-
const
|
|
932
|
+
if (!postedAt) return UNKNOWN_RECENCY;
|
|
933
|
+
const ms = new Date(postedAt).getTime();
|
|
934
|
+
if (Number.isNaN(ms)) return UNKNOWN_RECENCY;
|
|
935
|
+
const ageDays2 = (now - ms) / 864e5;
|
|
416
936
|
if (ageDays2 < 7) return 1;
|
|
417
937
|
if (ageDays2 < 30) return 0.9;
|
|
418
938
|
if (ageDays2 < 90) return 0.75;
|
|
@@ -443,9 +963,8 @@ function harmonicMean(a, b) {
|
|
|
443
963
|
if (a <= 0 || b <= 0) return 0;
|
|
444
964
|
return 2 * a * b / (a + b);
|
|
445
965
|
}
|
|
446
|
-
function match(fp, jobs, limit = 5, now = Date.now()) {
|
|
447
|
-
const
|
|
448
|
-
const idfOf = (t) => idf.get(t) ?? 0;
|
|
966
|
+
function match(fp, jobs, limit = 5, now = Date.now(), opts = {}) {
|
|
967
|
+
const idfOf = backgroundIdf;
|
|
449
968
|
const expanded = expandWeighted(fp.skillTags);
|
|
450
969
|
const maxDevScore = fp.skillTags.reduce((acc, t) => acc + idfOf(t), 0);
|
|
451
970
|
const candidates = jobs.filter((j) => passesFilters(fp, j));
|
|
@@ -471,32 +990,45 @@ function match(fp, jobs, limit = 5, now = Date.now()) {
|
|
|
471
990
|
const jobCov = jobMaxScore > 0 ? Math.min(1, jobMatchScore / jobMaxScore) : 0;
|
|
472
991
|
const tagComponent = harmonicMean(devCov, jobCov);
|
|
473
992
|
if (tagComponent === 0) return null;
|
|
993
|
+
const coreTags = job.coreTags ?? coreTagsFromTitle(job.title);
|
|
994
|
+
let coreComponent = tagComponent;
|
|
995
|
+
if (coreTags.length > 0) {
|
|
996
|
+
const coreCov = Math.max(0, ...coreTags.map((ct) => expanded.get(ct)?.weight ?? 0));
|
|
997
|
+
if (coreCov === 0) coreComponent = tagComponent * CORE_MISS_PENALTY;
|
|
998
|
+
}
|
|
474
999
|
details.sort((a, b) => idfOf(b.tag) * b.weight - idfOf(a.tag) * a.weight);
|
|
475
1000
|
const sScore = seniorityScore(fp, job);
|
|
476
1001
|
const rScore = recencyScore(job.postedAt, now);
|
|
477
|
-
const score =
|
|
1002
|
+
const score = coreComponent * 0.6 + sScore * 0.25 + rScore * 0.15;
|
|
478
1003
|
const matchedTags = [...new Set(details.map((d) => d.via ?? d.tag))];
|
|
1004
|
+
const badge = opts.acceptance ? bestAcceptanceDomain(opts.acceptance, acceptanceDomainsOf(job)) : null;
|
|
479
1005
|
return {
|
|
480
1006
|
job,
|
|
481
1007
|
score: Math.round(score * 1e3) / 1e3,
|
|
482
1008
|
matchedTags,
|
|
483
1009
|
matchDetails: details,
|
|
1010
|
+
...badge ? { acceptance: { status: "ok", domain: badge.domain, count: badge.count } } : {},
|
|
484
1011
|
reason: buildReason(details)
|
|
485
1012
|
};
|
|
486
1013
|
});
|
|
487
|
-
return scored.filter((r) => r !== null && r.score >= MIN_SCORE).sort((a, b) =>
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
1014
|
+
return scored.filter((r) => r !== null && r.score >= MIN_SCORE).sort((a, b) => {
|
|
1015
|
+
const byScore = b.score - a.score;
|
|
1016
|
+
if (Math.abs(byScore) > TIEBREAK_EPS) return byScore;
|
|
1017
|
+
const byAcceptance = (b.acceptance?.count ?? 0) - (a.acceptance?.count ?? 0);
|
|
1018
|
+
if (byAcceptance !== 0) return byAcceptance;
|
|
1019
|
+
return byScore;
|
|
1020
|
+
}).slice(0, limit);
|
|
1021
|
+
}
|
|
1022
|
+
var MIN_SCORE, TIEBREAK_EPS, SHARPEN, CORE_MISS_PENALTY, SENIORITY_RANK, SENIORITY_PATTERNS, ENG_TITLE, UNKNOWN_RECENCY;
|
|
494
1023
|
var init_matcher = __esm({
|
|
495
1024
|
"../../packages/core/src/matcher.ts"() {
|
|
496
1025
|
"use strict";
|
|
497
1026
|
init_vocabulary();
|
|
1027
|
+
init_github();
|
|
498
1028
|
MIN_SCORE = 0.15;
|
|
1029
|
+
TIEBREAK_EPS = 5e-3;
|
|
499
1030
|
SHARPEN = 1.6;
|
|
1031
|
+
CORE_MISS_PENALTY = 0.4;
|
|
500
1032
|
SENIORITY_RANK = {
|
|
501
1033
|
junior: 0,
|
|
502
1034
|
mid: 1,
|
|
@@ -510,24 +1042,31 @@ var init_matcher = __esm({
|
|
|
510
1042
|
[/\bmid[\s-]?level\b|\bmid\b/i, "mid"]
|
|
511
1043
|
];
|
|
512
1044
|
ENG_TITLE = /\b(engineer|engineering|developer|dev|swe|sde|programmer|architect)\b/i;
|
|
1045
|
+
UNKNOWN_RECENCY = 0.75;
|
|
513
1046
|
}
|
|
514
1047
|
});
|
|
515
1048
|
|
|
516
|
-
// ../../packages/core/src/feeds/
|
|
517
|
-
function
|
|
518
|
-
return
|
|
1049
|
+
// ../../packages/core/src/feeds/http.ts
|
|
1050
|
+
function fetchWithTimeout(input, init, timeoutMs = FEED_FETCH_TIMEOUT_MS) {
|
|
1051
|
+
return fetch(input, { ...init, signal: AbortSignal.timeout(timeoutMs) });
|
|
519
1052
|
}
|
|
1053
|
+
var FEED_FETCH_TIMEOUT_MS;
|
|
1054
|
+
var init_http = __esm({
|
|
1055
|
+
"../../packages/core/src/feeds/http.ts"() {
|
|
1056
|
+
"use strict";
|
|
1057
|
+
FEED_FETCH_TIMEOUT_MS = 1e4;
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
// ../../packages/core/src/feeds/greenhouse.ts
|
|
520
1062
|
function extractTags(job) {
|
|
521
|
-
const
|
|
522
|
-
job.title,
|
|
1063
|
+
const body = [
|
|
523
1064
|
...(job.departments ?? []).map((d) => d.name),
|
|
524
1065
|
job.location?.name ?? "",
|
|
525
1066
|
...(job.offices ?? []).map((o) => o.name),
|
|
526
|
-
// mine the full HTML description for additional signal when present
|
|
527
1067
|
...job.content ? [job.content.replace(/<[^>]*>/g, " ")] : []
|
|
528
|
-
].filter(Boolean);
|
|
529
|
-
|
|
530
|
-
return normalize(tokens);
|
|
1068
|
+
].filter(Boolean).join(" ");
|
|
1069
|
+
return extractSkillTags(job.title, body);
|
|
531
1070
|
}
|
|
532
1071
|
function inferRemote(location) {
|
|
533
1072
|
const l = location.toLowerCase();
|
|
@@ -537,7 +1076,7 @@ async function fetchSlug(slug) {
|
|
|
537
1076
|
const url = `https://boards-api.greenhouse.io/v1/boards/${slug}/jobs?content=true`;
|
|
538
1077
|
let res;
|
|
539
1078
|
try {
|
|
540
|
-
res = await
|
|
1079
|
+
res = await fetchWithTimeout(url, { headers: { Accept: "application/json" } });
|
|
541
1080
|
} catch (err) {
|
|
542
1081
|
console.warn(`[greenhouse] ${slug}: network error \u2014`, err);
|
|
543
1082
|
return [];
|
|
@@ -579,6 +1118,7 @@ var init_greenhouse = __esm({
|
|
|
579
1118
|
"../../packages/core/src/feeds/greenhouse.ts"() {
|
|
580
1119
|
"use strict";
|
|
581
1120
|
init_vocabulary();
|
|
1121
|
+
init_http();
|
|
582
1122
|
FALLBACK_SLUGS = [
|
|
583
1123
|
"stripe",
|
|
584
1124
|
"linear",
|
|
@@ -625,17 +1165,15 @@ var init_greenhouse = __esm({
|
|
|
625
1165
|
});
|
|
626
1166
|
|
|
627
1167
|
// ../../packages/core/src/feeds/ashby.ts
|
|
628
|
-
function tokenize2(text) {
|
|
629
|
-
return text.toLowerCase().replace(/[^a-z0-9.\-+#]/g, " ").split(/\s+/).filter(Boolean);
|
|
630
|
-
}
|
|
631
1168
|
function extractTags2(job) {
|
|
632
|
-
const
|
|
633
|
-
job.
|
|
634
|
-
job.
|
|
635
|
-
job.
|
|
636
|
-
...(job.secondaryLocations ?? []).map((l) => l.
|
|
637
|
-
|
|
638
|
-
|
|
1169
|
+
const body = [
|
|
1170
|
+
job.team ?? "",
|
|
1171
|
+
job.department ?? "",
|
|
1172
|
+
job.location ?? "",
|
|
1173
|
+
...(job.secondaryLocations ?? []).map((l) => l.location ?? ""),
|
|
1174
|
+
job.descriptionPlain ?? ""
|
|
1175
|
+
].join(" ");
|
|
1176
|
+
return extractSkillTags(job.title, body);
|
|
639
1177
|
}
|
|
640
1178
|
function mapEmploymentType(raw) {
|
|
641
1179
|
if (!raw) return "full_time";
|
|
@@ -646,12 +1184,12 @@ function mapEmploymentType(raw) {
|
|
|
646
1184
|
}
|
|
647
1185
|
function inferRemote2(job) {
|
|
648
1186
|
if (job.isRemote === true) return true;
|
|
649
|
-
const loc = (job.
|
|
1187
|
+
const loc = (job.location ?? "").toLowerCase();
|
|
650
1188
|
return loc.includes("remote") || loc.includes("anywhere");
|
|
651
1189
|
}
|
|
652
1190
|
async function fetchSlug2(slug) {
|
|
653
1191
|
const url = `https://api.ashbyhq.com/posting-api/job-board/${slug}`;
|
|
654
|
-
const res = await
|
|
1192
|
+
const res = await fetchWithTimeout(url, {
|
|
655
1193
|
headers: { Accept: "application/json" }
|
|
656
1194
|
});
|
|
657
1195
|
if (!res.ok) {
|
|
@@ -665,14 +1203,14 @@ async function fetchSlug2(slug) {
|
|
|
665
1203
|
source: "ashby",
|
|
666
1204
|
title: j.title,
|
|
667
1205
|
company: slug,
|
|
668
|
-
url: j.applyUrl ?? `https://jobs.ashbyhq.com/${slug}/${j.id}`,
|
|
1206
|
+
url: j.jobUrl ?? j.applyUrl ?? `https://jobs.ashbyhq.com/${slug}/${j.id}`,
|
|
669
1207
|
remote: inferRemote2(j),
|
|
670
|
-
location: j.
|
|
1208
|
+
location: j.location,
|
|
671
1209
|
compMin: comp?.minValue,
|
|
672
1210
|
compMax: comp?.maxValue,
|
|
673
1211
|
tags: extractTags2(j),
|
|
674
1212
|
roleType: mapEmploymentType(j.employmentType),
|
|
675
|
-
postedAt: j.
|
|
1213
|
+
postedAt: j.publishedAt,
|
|
676
1214
|
applyMode: "direct",
|
|
677
1215
|
raw: j
|
|
678
1216
|
};
|
|
@@ -683,6 +1221,7 @@ var init_ashby = __esm({
|
|
|
683
1221
|
"../../packages/core/src/feeds/ashby.ts"() {
|
|
684
1222
|
"use strict";
|
|
685
1223
|
init_vocabulary();
|
|
1224
|
+
init_http();
|
|
686
1225
|
ashby = {
|
|
687
1226
|
source: "ashby",
|
|
688
1227
|
async fetch(opts) {
|
|
@@ -699,20 +1238,16 @@ var init_ashby = __esm({
|
|
|
699
1238
|
});
|
|
700
1239
|
|
|
701
1240
|
// ../../packages/core/src/feeds/lever.ts
|
|
702
|
-
function tokenize3(text) {
|
|
703
|
-
return text.toLowerCase().replace(/[^a-z0-9.\-+#]/g, " ").split(/\s+/).filter(Boolean);
|
|
704
|
-
}
|
|
705
1241
|
function extractTags3(p) {
|
|
706
1242
|
const cat = p.categories ?? {};
|
|
707
|
-
const
|
|
708
|
-
p.text,
|
|
1243
|
+
const body = [
|
|
709
1244
|
cat.team ?? "",
|
|
710
1245
|
cat.department ?? "",
|
|
711
1246
|
cat.location ?? "",
|
|
712
1247
|
...cat.allLocations ?? [],
|
|
713
1248
|
p.descriptionPlain ?? ""
|
|
714
|
-
];
|
|
715
|
-
return
|
|
1249
|
+
].join(" ");
|
|
1250
|
+
return extractSkillTags(p.text, body);
|
|
716
1251
|
}
|
|
717
1252
|
function mapCommitment(raw) {
|
|
718
1253
|
if (!raw) return "full_time";
|
|
@@ -737,7 +1272,7 @@ function toIso(ms) {
|
|
|
737
1272
|
}
|
|
738
1273
|
async function fetchSlug3(slug) {
|
|
739
1274
|
const url = `https://api.lever.co/v0/postings/${slug}?mode=json`;
|
|
740
|
-
const res = await
|
|
1275
|
+
const res = await fetchWithTimeout(url, { headers: { Accept: "application/json" } });
|
|
741
1276
|
if (!res.ok) {
|
|
742
1277
|
throw new Error(`Lever ${slug}: HTTP ${res.status}`);
|
|
743
1278
|
}
|
|
@@ -768,6 +1303,7 @@ var init_lever = __esm({
|
|
|
768
1303
|
"../../packages/core/src/feeds/lever.ts"() {
|
|
769
1304
|
"use strict";
|
|
770
1305
|
init_vocabulary();
|
|
1306
|
+
init_http();
|
|
771
1307
|
lever = {
|
|
772
1308
|
source: "lever",
|
|
773
1309
|
async fetch(opts) {
|
|
@@ -785,15 +1321,8 @@ var init_lever = __esm({
|
|
|
785
1321
|
});
|
|
786
1322
|
|
|
787
1323
|
// ../../packages/core/src/feeds/himalayas.ts
|
|
788
|
-
function tokenize4(text) {
|
|
789
|
-
return text.toLowerCase().replace(/[^a-z0-9.\-+#]/g, " ").split(/\s+/).filter(Boolean);
|
|
790
|
-
}
|
|
791
1324
|
function extractTags4(job) {
|
|
792
|
-
|
|
793
|
-
job.title,
|
|
794
|
-
...job.tags ?? []
|
|
795
|
-
];
|
|
796
|
-
return normalize(texts.flatMap(tokenize4));
|
|
1325
|
+
return extractSkillTags(job.title, (job.tags ?? []).join(" "));
|
|
797
1326
|
}
|
|
798
1327
|
function mapJobType(raw) {
|
|
799
1328
|
if (!raw) return "full_time";
|
|
@@ -816,12 +1345,13 @@ var init_himalayas = __esm({
|
|
|
816
1345
|
"../../packages/core/src/feeds/himalayas.ts"() {
|
|
817
1346
|
"use strict";
|
|
818
1347
|
init_vocabulary();
|
|
1348
|
+
init_http();
|
|
819
1349
|
himalayas = {
|
|
820
1350
|
source: "himalayas",
|
|
821
1351
|
async fetch(opts) {
|
|
822
1352
|
const limit = opts?.limit ?? 100;
|
|
823
1353
|
const url = `https://himalayas.app/jobs/api?limit=${limit}`;
|
|
824
|
-
const res = await
|
|
1354
|
+
const res = await fetchWithTimeout(url, {
|
|
825
1355
|
headers: { Accept: "application/json" }
|
|
826
1356
|
});
|
|
827
1357
|
if (!res.ok) {
|
|
@@ -878,9 +1408,6 @@ var init_entities = __esm({
|
|
|
878
1408
|
});
|
|
879
1409
|
|
|
880
1410
|
// ../../packages/core/src/feeds/wwr.ts
|
|
881
|
-
function tokenize5(text) {
|
|
882
|
-
return text.toLowerCase().replace(/[^a-z0-9.\-+#]/g, " ").split(/\s+/).filter(Boolean);
|
|
883
|
-
}
|
|
884
1411
|
function stripHtml(html) {
|
|
885
1412
|
return html.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
|
|
886
1413
|
}
|
|
@@ -924,8 +1451,8 @@ function parseRss(xml) {
|
|
|
924
1451
|
return items;
|
|
925
1452
|
}
|
|
926
1453
|
function extractTags5(item) {
|
|
927
|
-
const
|
|
928
|
-
return
|
|
1454
|
+
const body = [item.category, stripHtml(item.description)].join(" ");
|
|
1455
|
+
return extractSkillTags(item.title, body);
|
|
929
1456
|
}
|
|
930
1457
|
var WWR_RSS_URL, wwr;
|
|
931
1458
|
var init_wwr = __esm({
|
|
@@ -933,12 +1460,13 @@ var init_wwr = __esm({
|
|
|
933
1460
|
"use strict";
|
|
934
1461
|
init_vocabulary();
|
|
935
1462
|
init_entities();
|
|
1463
|
+
init_http();
|
|
936
1464
|
WWR_RSS_URL = "https://weworkremotely.com/remote-jobs.rss";
|
|
937
1465
|
wwr = {
|
|
938
1466
|
source: "wwr",
|
|
939
1467
|
async fetch(opts) {
|
|
940
1468
|
const limit = opts?.limit ?? 200;
|
|
941
|
-
const res = await
|
|
1469
|
+
const res = await fetchWithTimeout(WWR_RSS_URL, {
|
|
942
1470
|
headers: { Accept: "application/rss+xml, application/xml, text/xml" }
|
|
943
1471
|
});
|
|
944
1472
|
if (!res.ok) {
|
|
@@ -946,6 +1474,11 @@ var init_wwr = __esm({
|
|
|
946
1474
|
}
|
|
947
1475
|
const xml = await res.text();
|
|
948
1476
|
const items = parseRss(xml).slice(0, limit);
|
|
1477
|
+
function safeIso(s) {
|
|
1478
|
+
if (!s) return void 0;
|
|
1479
|
+
const d = new Date(s);
|
|
1480
|
+
return Number.isNaN(d.getTime()) ? void 0 : d.toISOString();
|
|
1481
|
+
}
|
|
949
1482
|
return items.map((item) => ({
|
|
950
1483
|
id: extractId(item.link),
|
|
951
1484
|
source: "wwr",
|
|
@@ -957,7 +1490,7 @@ var init_wwr = __esm({
|
|
|
957
1490
|
location: "Remote",
|
|
958
1491
|
tags: extractTags5(item),
|
|
959
1492
|
roleType: inferRoleType(item.category),
|
|
960
|
-
postedAt:
|
|
1493
|
+
postedAt: safeIso(item.pubDate),
|
|
961
1494
|
applyMode: "direct",
|
|
962
1495
|
raw: item
|
|
963
1496
|
}));
|
|
@@ -967,9 +1500,6 @@ var init_wwr = __esm({
|
|
|
967
1500
|
});
|
|
968
1501
|
|
|
969
1502
|
// ../../packages/core/src/feeds/hn.ts
|
|
970
|
-
function tokenize6(text) {
|
|
971
|
-
return text.toLowerCase().replace(/[^a-z0-9.\-+#]/g, " ").split(/\s+/).filter(Boolean);
|
|
972
|
-
}
|
|
973
1503
|
function stripHtml2(html) {
|
|
974
1504
|
return decodeEntities(html.replace(/<p>/gi, " ").replace(/<[^>]*>/g, "")).replace(/\s+/g, " ").trim();
|
|
975
1505
|
}
|
|
@@ -1000,7 +1530,7 @@ function parseComment(item) {
|
|
|
1000
1530
|
return null;
|
|
1001
1531
|
}
|
|
1002
1532
|
const url = extractUrl(raw) || `https://news.ycombinator.com/item?id=${item.id}`;
|
|
1003
|
-
const tags = extractTags6(raw);
|
|
1533
|
+
const tags = extractTags6(title, raw);
|
|
1004
1534
|
if (tags.length === 0) return null;
|
|
1005
1535
|
return {
|
|
1006
1536
|
id: `hn:${item.id}`,
|
|
@@ -1017,8 +1547,8 @@ function parseComment(item) {
|
|
|
1017
1547
|
raw: item
|
|
1018
1548
|
};
|
|
1019
1549
|
}
|
|
1020
|
-
function extractTags6(text) {
|
|
1021
|
-
return
|
|
1550
|
+
function extractTags6(title, text) {
|
|
1551
|
+
return extractSkillTags(title, text);
|
|
1022
1552
|
}
|
|
1023
1553
|
var ALGOLIA_SEARCH, ALGOLIA_ITEMS, hn;
|
|
1024
1554
|
var init_hn = __esm({
|
|
@@ -1026,13 +1556,14 @@ var init_hn = __esm({
|
|
|
1026
1556
|
"use strict";
|
|
1027
1557
|
init_vocabulary();
|
|
1028
1558
|
init_entities();
|
|
1559
|
+
init_http();
|
|
1029
1560
|
ALGOLIA_SEARCH = "https://hn.algolia.com/api/v1/search?query=Ask+HN%3A+Who+is+Hiring%3F&tags=story,ask_hn&hitsPerPage=1";
|
|
1030
1561
|
ALGOLIA_ITEMS = "https://hn.algolia.com/api/v1/items/";
|
|
1031
1562
|
hn = {
|
|
1032
1563
|
source: "hn",
|
|
1033
1564
|
async fetch(opts) {
|
|
1034
1565
|
const limit = opts?.limit ?? 150;
|
|
1035
|
-
const searchRes = await
|
|
1566
|
+
const searchRes = await fetchWithTimeout(ALGOLIA_SEARCH, {
|
|
1036
1567
|
headers: { Accept: "application/json" }
|
|
1037
1568
|
});
|
|
1038
1569
|
if (!searchRes.ok) {
|
|
@@ -1043,7 +1574,7 @@ var init_hn = __esm({
|
|
|
1043
1574
|
if (!story) {
|
|
1044
1575
|
throw new Error('HN: No "Who is Hiring" story found');
|
|
1045
1576
|
}
|
|
1046
|
-
const itemRes = await
|
|
1577
|
+
const itemRes = await fetchWithTimeout(`${ALGOLIA_ITEMS}${story.objectID}`, {
|
|
1047
1578
|
headers: { Accept: "application/json" }
|
|
1048
1579
|
});
|
|
1049
1580
|
if (!itemRes.ok) {
|
|
@@ -1108,7 +1639,7 @@ function authHeaders() {
|
|
|
1108
1639
|
if (token) h["Authorization"] = `Bearer ${token}`;
|
|
1109
1640
|
return h;
|
|
1110
1641
|
}
|
|
1111
|
-
function
|
|
1642
|
+
function tokenize2(text) {
|
|
1112
1643
|
return text.toLowerCase().replace(/[^a-z0-9.\-+#]/g, " ").split(/\s+/).filter(Boolean);
|
|
1113
1644
|
}
|
|
1114
1645
|
function parseAmountUSD(text) {
|
|
@@ -1137,7 +1668,7 @@ function isBountyIssue(issue) {
|
|
|
1137
1668
|
async function ghJson(path) {
|
|
1138
1669
|
let res;
|
|
1139
1670
|
try {
|
|
1140
|
-
res = await
|
|
1671
|
+
res = await fetchWithTimeout(`${GITHUB_API}${path}`, { headers: authHeaders() });
|
|
1141
1672
|
} catch (err) {
|
|
1142
1673
|
console.warn(`[github-bounties] network error ${path} \u2014`, err);
|
|
1143
1674
|
return null;
|
|
@@ -1193,7 +1724,7 @@ async function fetchRepoBounties(repoFullName) {
|
|
|
1193
1724
|
const body = issue.body ? decodeEntities(issue.body) : "";
|
|
1194
1725
|
const amountUSD = parseAmountUSD(title) ?? parseAmountUSD(body) ?? await fetchCommentAmount(repoFullName, issue.number);
|
|
1195
1726
|
const labels = labelNames(issue);
|
|
1196
|
-
const tags = normalize(
|
|
1727
|
+
const tags = normalize(tokenize2([title, labels.join(" "), body.slice(0, 2e3)].join(" ")));
|
|
1197
1728
|
return {
|
|
1198
1729
|
id: `bounty:${repoFullName}#${issue.number}`,
|
|
1199
1730
|
source: "bounty",
|
|
@@ -1219,31 +1750,328 @@ async function fetchRepoBounties(repoFullName) {
|
|
|
1219
1750
|
};
|
|
1220
1751
|
}));
|
|
1221
1752
|
}
|
|
1222
|
-
|
|
1753
|
+
function repoFullNameFromApiUrl(url) {
|
|
1754
|
+
const m = url.match(/\/repos\/([^/]+)\/([^/]+)\/?$/);
|
|
1755
|
+
return m ? `${m[1]}/${m[2]}` : null;
|
|
1756
|
+
}
|
|
1757
|
+
async function searchBountyIssues() {
|
|
1758
|
+
const byUrl = /* @__PURE__ */ new Map();
|
|
1759
|
+
for (const q of SEARCH_QUERIES) {
|
|
1760
|
+
const res = await ghJson(
|
|
1761
|
+
`/search/issues?q=${encodeURIComponent(q)}&sort=created&order=desc&per_page=${SEARCH_PER_PAGE}`
|
|
1762
|
+
);
|
|
1763
|
+
for (const it of res?.items ?? []) {
|
|
1764
|
+
if (it.pull_request) continue;
|
|
1765
|
+
if (!byUrl.has(it.html_url)) byUrl.set(it.html_url, it);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
return [...byUrl.values()].sort(
|
|
1769
|
+
(a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
|
|
1770
|
+
);
|
|
1771
|
+
}
|
|
1772
|
+
async function repoMetaCached(fullName) {
|
|
1773
|
+
const hit = repoMetaCache.get(fullName);
|
|
1774
|
+
if (hit !== void 0) return hit;
|
|
1775
|
+
const r = await ghJson(`/repos/${fullName}`) ?? null;
|
|
1776
|
+
repoMetaCache.set(fullName, r);
|
|
1777
|
+
return r;
|
|
1778
|
+
}
|
|
1779
|
+
async function fetchSearchBounties() {
|
|
1780
|
+
const issues = (await searchBountyIssues()).slice(0, MAX_SEARCH_ISSUES_SCANNED);
|
|
1781
|
+
const distinctRepos = [
|
|
1782
|
+
...new Set(
|
|
1783
|
+
issues.map((i) => repoFullNameFromApiUrl(i.repository_url)).filter((x) => !!x)
|
|
1784
|
+
)
|
|
1785
|
+
];
|
|
1786
|
+
for (let i = 0; i < distinctRepos.length; i += REPO_META_CONCURRENCY) {
|
|
1787
|
+
await Promise.all(distinctRepos.slice(i, i + REPO_META_CONCURRENCY).map(repoMetaCached));
|
|
1788
|
+
}
|
|
1789
|
+
const jobs = [];
|
|
1790
|
+
const perRepo = /* @__PURE__ */ new Map();
|
|
1791
|
+
for (const issue of issues) {
|
|
1792
|
+
if (jobs.length >= MAX_SEARCH_BOUNTIES) break;
|
|
1793
|
+
const fullName = repoFullNameFromApiUrl(issue.repository_url);
|
|
1794
|
+
if (!fullName) continue;
|
|
1795
|
+
if ((perRepo.get(fullName) ?? 0) >= MAX_BOUNTIES_PER_REPO) continue;
|
|
1796
|
+
const repo = await repoMetaCached(fullName);
|
|
1797
|
+
if (!repo) continue;
|
|
1798
|
+
const passes = passesMaturityGate({
|
|
1799
|
+
fullName: repo.full_name,
|
|
1800
|
+
stargazers: repo.stargazers_count,
|
|
1801
|
+
createdAt: repo.created_at,
|
|
1802
|
+
archived: repo.archived,
|
|
1803
|
+
disabled: repo.disabled
|
|
1804
|
+
});
|
|
1805
|
+
if (!passes) continue;
|
|
1806
|
+
const title = decodeEntities(issue.title).trim();
|
|
1807
|
+
const body = issue.body ? decodeEntities(issue.body) : "";
|
|
1808
|
+
const labels = labelNames(issue);
|
|
1809
|
+
let amountUSD = parseAmountUSD(title) ?? parseAmountUSD(labels.join(" ")) ?? parseAmountUSD(body);
|
|
1810
|
+
if (amountUSD == null && labels.some((n) => /💎|💰/.test(n))) {
|
|
1811
|
+
amountUSD = await fetchCommentAmount(fullName, issue.number);
|
|
1812
|
+
}
|
|
1813
|
+
if (amountUSD == null) continue;
|
|
1814
|
+
if (amountUSD > SEARCH_HIGH_VALUE_USD && repo.stargazers_count < SEARCH_HIGH_VALUE_MIN_STARS) continue;
|
|
1815
|
+
const tags = normalize(
|
|
1816
|
+
tokenize2([title, labels.join(" "), body.slice(0, 2e3)].join(" "))
|
|
1817
|
+
);
|
|
1818
|
+
perRepo.set(fullName, (perRepo.get(fullName) ?? 0) + 1);
|
|
1819
|
+
jobs.push({
|
|
1820
|
+
id: `bounty:${fullName}#${issue.number}`,
|
|
1821
|
+
source: "bounty",
|
|
1822
|
+
title,
|
|
1823
|
+
company: repo.owner.login,
|
|
1824
|
+
url: issue.html_url,
|
|
1825
|
+
remote: true,
|
|
1826
|
+
location: "Remote",
|
|
1827
|
+
tags,
|
|
1828
|
+
roleType: "freelance",
|
|
1829
|
+
postedAt: issue.created_at,
|
|
1830
|
+
applyMode: "direct",
|
|
1831
|
+
bounty: {
|
|
1832
|
+
amountUSD,
|
|
1833
|
+
estimatedEffort: effortFromAmount(amountUSD),
|
|
1834
|
+
bountySource: "github",
|
|
1835
|
+
claimUrl: issue.html_url,
|
|
1836
|
+
repoFullName: fullName,
|
|
1837
|
+
repoStars: repo.stargazers_count,
|
|
1838
|
+
issueBody: body.slice(0, 1e3) || void 0
|
|
1839
|
+
},
|
|
1840
|
+
raw: issue
|
|
1841
|
+
});
|
|
1842
|
+
}
|
|
1843
|
+
return jobs;
|
|
1844
|
+
}
|
|
1845
|
+
var GITHUB_API, BOUNTY_LABEL_RE, SEARCH_QUERIES, SEARCH_PER_PAGE, MAX_SEARCH_BOUNTIES, MAX_SEARCH_ISSUES_SCANNED, REPO_META_CONCURRENCY, SEARCH_HIGH_VALUE_USD, SEARCH_HIGH_VALUE_MIN_STARS, repoMetaCache, githubBounties;
|
|
1223
1846
|
var init_github_bounties = __esm({
|
|
1224
1847
|
"../../packages/core/src/feeds/github-bounties.ts"() {
|
|
1225
1848
|
"use strict";
|
|
1226
1849
|
init_vocabulary();
|
|
1227
1850
|
init_entities();
|
|
1228
1851
|
init_bounty_gate();
|
|
1852
|
+
init_http();
|
|
1229
1853
|
GITHUB_API = "https://api.github.com";
|
|
1230
1854
|
BOUNTY_LABEL_RE = /bounty|reward|funded|💎|💰/i;
|
|
1855
|
+
SEARCH_QUERIES = [
|
|
1856
|
+
'label:"\u{1F48E} Bounty" type:issue state:open',
|
|
1857
|
+
// Algora-applied — highest signal
|
|
1858
|
+
"label:bounty type:issue state:open",
|
|
1859
|
+
'label:"\u{1F4B0} Bounty" type:issue state:open'
|
|
1860
|
+
];
|
|
1861
|
+
SEARCH_PER_PAGE = 100;
|
|
1862
|
+
MAX_SEARCH_BOUNTIES = 150;
|
|
1863
|
+
MAX_SEARCH_ISSUES_SCANNED = 300;
|
|
1864
|
+
REPO_META_CONCURRENCY = 15;
|
|
1865
|
+
SEARCH_HIGH_VALUE_USD = 500;
|
|
1866
|
+
SEARCH_HIGH_VALUE_MIN_STARS = 50;
|
|
1867
|
+
repoMetaCache = /* @__PURE__ */ new Map();
|
|
1231
1868
|
githubBounties = {
|
|
1232
1869
|
source: "bounty",
|
|
1233
1870
|
async fetch(opts) {
|
|
1234
|
-
const
|
|
1235
|
-
|
|
1236
|
-
|
|
1871
|
+
const allowlist = opts?.slugs && opts.slugs.length > 0 ? opts.slugs : DEFAULT_BOUNTY_REPOS;
|
|
1872
|
+
const [searched, listed] = await Promise.all([
|
|
1873
|
+
fetchSearchBounties().catch((e) => {
|
|
1874
|
+
console.warn("[github-bounties] search discovery failed:", e);
|
|
1875
|
+
return [];
|
|
1876
|
+
}),
|
|
1877
|
+
Promise.allSettled(allowlist.map(fetchRepoBounties)).then(
|
|
1878
|
+
(settled) => settled.flatMap((r) => r.status === "fulfilled" ? r.value : [])
|
|
1879
|
+
)
|
|
1880
|
+
]);
|
|
1881
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1882
|
+
const out = [];
|
|
1883
|
+
for (const j of [...searched, ...listed]) {
|
|
1884
|
+
if (!seen.has(j.id)) {
|
|
1885
|
+
seen.add(j.id);
|
|
1886
|
+
out.push(j);
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
console.info(
|
|
1890
|
+
`[github-bounties] total: ${out.length} bounties (${searched.length} search + ${listed.length} allowlist, deduped)`
|
|
1891
|
+
);
|
|
1892
|
+
return out;
|
|
1893
|
+
}
|
|
1894
|
+
};
|
|
1895
|
+
}
|
|
1896
|
+
});
|
|
1897
|
+
|
|
1898
|
+
// ../../packages/core/src/feeds/opire.ts
|
|
1899
|
+
function tokenize3(text) {
|
|
1900
|
+
return text.toLowerCase().replace(/[^a-z0-9.\-+#]/g, " ").split(/\s+/).filter((w) => w.length > 1);
|
|
1901
|
+
}
|
|
1902
|
+
function effortFromAmount2(usd) {
|
|
1903
|
+
if (usd == null) return void 0;
|
|
1904
|
+
if (usd < 150) return "small";
|
|
1905
|
+
if (usd < 750) return "medium";
|
|
1906
|
+
return "large";
|
|
1907
|
+
}
|
|
1908
|
+
function priceToUSD(p) {
|
|
1909
|
+
if (!p || typeof p.value !== "number") return void 0;
|
|
1910
|
+
if (p.unit === "USD_CENT") return Math.round(p.value) / 100;
|
|
1911
|
+
if (p.unit === "USD") return p.value;
|
|
1912
|
+
return void 0;
|
|
1913
|
+
}
|
|
1914
|
+
function repoFullNameFromUrl(url) {
|
|
1915
|
+
const m = url?.match(/github\.com\/([^/]+)\/([^/]+)/i);
|
|
1916
|
+
return m ? `${m[1]}/${m[2].replace(/\.git$/, "")}` : void 0;
|
|
1917
|
+
}
|
|
1918
|
+
var OPIRE_REWARDS_URL, MIN_USD, MAX_USD, MAX_OPIRE_BOUNTIES, opire;
|
|
1919
|
+
var init_opire = __esm({
|
|
1920
|
+
"../../packages/core/src/feeds/opire.ts"() {
|
|
1921
|
+
"use strict";
|
|
1922
|
+
init_vocabulary();
|
|
1923
|
+
init_http();
|
|
1924
|
+
OPIRE_REWARDS_URL = "https://api.opire.dev/rewards";
|
|
1925
|
+
MIN_USD = 25;
|
|
1926
|
+
MAX_USD = 25e3;
|
|
1927
|
+
MAX_OPIRE_BOUNTIES = 100;
|
|
1928
|
+
opire = {
|
|
1929
|
+
source: "bounty",
|
|
1930
|
+
async fetch() {
|
|
1931
|
+
let rewards;
|
|
1932
|
+
try {
|
|
1933
|
+
const res = await fetchWithTimeout(OPIRE_REWARDS_URL, {
|
|
1934
|
+
headers: { Accept: "application/json", "User-Agent": "terminalhire" }
|
|
1935
|
+
});
|
|
1936
|
+
if (!res.ok) {
|
|
1937
|
+
console.warn(`[opire] HTTP ${res.status}`);
|
|
1938
|
+
return [];
|
|
1939
|
+
}
|
|
1940
|
+
const json = await res.json();
|
|
1941
|
+
rewards = Array.isArray(json) ? json : json?.data ?? json?.items ?? [];
|
|
1942
|
+
} catch (err) {
|
|
1943
|
+
console.warn("[opire] fetch failed \u2014", err);
|
|
1944
|
+
return [];
|
|
1945
|
+
}
|
|
1946
|
+
const jobs = [];
|
|
1947
|
+
for (const r of rewards) {
|
|
1948
|
+
if (r.platform !== "GitHub") continue;
|
|
1949
|
+
if (r.project && r.project.isPublic === false) continue;
|
|
1950
|
+
const repoFullName = repoFullNameFromUrl(r.project?.url ?? r.url);
|
|
1951
|
+
if (!repoFullName) continue;
|
|
1952
|
+
const amountUSD = priceToUSD(r.pendingPrice);
|
|
1953
|
+
if (amountUSD == null || amountUSD < MIN_USD || amountUSD > MAX_USD) continue;
|
|
1954
|
+
const title = (r.title ?? "").trim();
|
|
1955
|
+
if (title.length < 4) continue;
|
|
1956
|
+
const tags = normalize([...r.programmingLanguages ?? [], ...tokenize3(title)]);
|
|
1957
|
+
jobs.push({
|
|
1958
|
+
id: `bounty:opire:${r.id}`,
|
|
1959
|
+
source: "bounty",
|
|
1960
|
+
title,
|
|
1961
|
+
company: r.organization?.name ?? repoFullName.split("/")[0],
|
|
1962
|
+
url: r.url,
|
|
1963
|
+
remote: true,
|
|
1964
|
+
location: "Remote",
|
|
1965
|
+
tags,
|
|
1966
|
+
roleType: "freelance",
|
|
1967
|
+
postedAt: Number.isFinite(r.createdAt) ? new Date(r.createdAt).toISOString() : void 0,
|
|
1968
|
+
applyMode: "direct",
|
|
1969
|
+
bounty: {
|
|
1970
|
+
amountUSD,
|
|
1971
|
+
estimatedEffort: effortFromAmount2(amountUSD),
|
|
1972
|
+
bountySource: "opire",
|
|
1973
|
+
claimUrl: r.url,
|
|
1974
|
+
repoFullName
|
|
1975
|
+
},
|
|
1976
|
+
raw: r
|
|
1977
|
+
});
|
|
1978
|
+
if (jobs.length >= MAX_OPIRE_BOUNTIES) break;
|
|
1979
|
+
}
|
|
1980
|
+
console.info(`[opire] ${jobs.length} bounties (from ${rewards.length} rewards)`);
|
|
1981
|
+
return jobs;
|
|
1982
|
+
}
|
|
1983
|
+
};
|
|
1984
|
+
}
|
|
1985
|
+
});
|
|
1986
|
+
|
|
1987
|
+
// ../../packages/core/src/feeds/workable.ts
|
|
1988
|
+
function locationStr(loc) {
|
|
1989
|
+
if (!loc) return "";
|
|
1990
|
+
return [loc.city, loc.country].filter(Boolean).join(", ");
|
|
1991
|
+
}
|
|
1992
|
+
function isRemote(j) {
|
|
1993
|
+
return j.remote === true || (j.workplace ?? "").toLowerCase() === "remote";
|
|
1994
|
+
}
|
|
1995
|
+
function extractTags7(j) {
|
|
1996
|
+
const body = [...j.department ?? [], locationStr(j.location)].filter(Boolean).join(" ");
|
|
1997
|
+
return extractSkillTags(j.title, body);
|
|
1998
|
+
}
|
|
1999
|
+
async function fetchAccount(account) {
|
|
2000
|
+
const url = `https://apply.workable.com/api/v3/accounts/${account}/jobs`;
|
|
2001
|
+
const out = [];
|
|
2002
|
+
let token;
|
|
2003
|
+
for (let page = 0; page < MAX_PAGES; page++) {
|
|
2004
|
+
let res;
|
|
2005
|
+
try {
|
|
2006
|
+
res = await fetchWithTimeout(url, {
|
|
2007
|
+
method: "POST",
|
|
2008
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
2009
|
+
body: JSON.stringify(token ? { token } : {})
|
|
2010
|
+
});
|
|
2011
|
+
} catch (err) {
|
|
2012
|
+
console.warn(`[workable] ${account}: network error \u2014`, err);
|
|
2013
|
+
break;
|
|
2014
|
+
}
|
|
2015
|
+
if (!res.ok) {
|
|
2016
|
+
console.warn(`[workable] ${account}: HTTP ${res.status}`);
|
|
2017
|
+
break;
|
|
2018
|
+
}
|
|
2019
|
+
let data;
|
|
2020
|
+
try {
|
|
2021
|
+
data = await res.json();
|
|
2022
|
+
} catch (err) {
|
|
2023
|
+
console.warn(`[workable] ${account}: JSON parse error \u2014`, err);
|
|
2024
|
+
break;
|
|
2025
|
+
}
|
|
2026
|
+
const results = data.results ?? [];
|
|
2027
|
+
for (const j of results) {
|
|
2028
|
+
if (j.state && j.state !== "published") continue;
|
|
2029
|
+
out.push({
|
|
2030
|
+
id: `workable:${j.id}`,
|
|
2031
|
+
source: "workable",
|
|
2032
|
+
title: j.title,
|
|
2033
|
+
company: account,
|
|
2034
|
+
url: `https://apply.workable.com/${account}/j/${j.shortcode}/`,
|
|
2035
|
+
remote: isRemote(j),
|
|
2036
|
+
location: locationStr(j.location) || void 0,
|
|
2037
|
+
tags: extractTags7(j),
|
|
2038
|
+
roleType: "full_time",
|
|
2039
|
+
postedAt: j.published,
|
|
2040
|
+
applyMode: "direct",
|
|
2041
|
+
raw: j
|
|
2042
|
+
});
|
|
2043
|
+
}
|
|
2044
|
+
token = data.token;
|
|
2045
|
+
if (!token || results.length === 0) break;
|
|
2046
|
+
}
|
|
2047
|
+
if (out.length > 0) console.info(`[workable] ${account}: ${out.length} jobs`);
|
|
2048
|
+
return out;
|
|
2049
|
+
}
|
|
2050
|
+
var FALLBACK_ACCOUNTS, MAX_PAGES, workable;
|
|
2051
|
+
var init_workable = __esm({
|
|
2052
|
+
"../../packages/core/src/feeds/workable.ts"() {
|
|
2053
|
+
"use strict";
|
|
2054
|
+
init_vocabulary();
|
|
2055
|
+
init_http();
|
|
2056
|
+
FALLBACK_ACCOUNTS = ["zego", "workmotion"];
|
|
2057
|
+
MAX_PAGES = 5;
|
|
2058
|
+
workable = {
|
|
2059
|
+
source: "workable",
|
|
2060
|
+
async fetch(opts) {
|
|
2061
|
+
const accounts = opts?.slugs && opts.slugs.length > 0 ? opts.slugs : FALLBACK_ACCOUNTS;
|
|
2062
|
+
console.info(`[workable] fetching ${accounts.length} accounts: ${accounts.join(", ")}`);
|
|
2063
|
+
const results = await Promise.allSettled(accounts.map(fetchAccount));
|
|
1237
2064
|
const jobs = [];
|
|
1238
2065
|
let failures = 0;
|
|
1239
|
-
for (const r of
|
|
1240
|
-
if (r.status === "fulfilled")
|
|
1241
|
-
|
|
2066
|
+
for (const r of results) {
|
|
2067
|
+
if (r.status === "fulfilled") {
|
|
2068
|
+
jobs.push(...r.value);
|
|
2069
|
+
} else {
|
|
1242
2070
|
failures++;
|
|
1243
|
-
console.warn("[
|
|
2071
|
+
console.warn("[workable] account fetch rejected:", r.reason);
|
|
1244
2072
|
}
|
|
1245
2073
|
}
|
|
1246
|
-
console.info(`[
|
|
2074
|
+
console.info(`[workable] total: ${jobs.length} jobs, ${failures} account failures`);
|
|
1247
2075
|
return jobs;
|
|
1248
2076
|
}
|
|
1249
2077
|
};
|
|
@@ -1252,7 +2080,19 @@ var init_github_bounties = __esm({
|
|
|
1252
2080
|
|
|
1253
2081
|
// ../../packages/core/src/feeds/index.ts
|
|
1254
2082
|
async function aggregateBounties(opts) {
|
|
1255
|
-
|
|
2083
|
+
const [gh, op] = await Promise.all([
|
|
2084
|
+
githubBounties.fetch({ slugs: opts?.repos }),
|
|
2085
|
+
opire.fetch()
|
|
2086
|
+
]);
|
|
2087
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2088
|
+
const out = [];
|
|
2089
|
+
for (const j of [...gh, ...op]) {
|
|
2090
|
+
const key = j.bounty?.claimUrl ?? j.url;
|
|
2091
|
+
if (seen.has(key)) continue;
|
|
2092
|
+
seen.add(key);
|
|
2093
|
+
out.push(j);
|
|
2094
|
+
}
|
|
2095
|
+
return out;
|
|
1256
2096
|
}
|
|
1257
2097
|
function flattenTiers(t) {
|
|
1258
2098
|
return [.../* @__PURE__ */ new Set([...t.bigco, ...t.scaleup, ...t.startup])];
|
|
@@ -1261,18 +2101,20 @@ async function aggregate(opts) {
|
|
|
1261
2101
|
const ghSlugs = opts?.slugs?.["greenhouse"] ?? DEFAULT_GREENHOUSE_SLUGS;
|
|
1262
2102
|
const ashbySlugs = opts?.slugs?.["ashby"] ?? DEFAULT_ASHBY_SLUGS;
|
|
1263
2103
|
const leverSlugs = opts?.slugs?.["lever"] ?? DEFAULT_LEVER_SLUGS;
|
|
2104
|
+
const workableSlugs = opts?.slugs?.["workable"] ?? DEFAULT_WORKABLE_SLUGS;
|
|
1264
2105
|
const limit = opts?.limit ?? 150;
|
|
1265
2106
|
const settled = await Promise.allSettled([
|
|
1266
2107
|
greenhouse.fetch({ slugs: ghSlugs, limit }),
|
|
1267
2108
|
ashby.fetch({ slugs: ashbySlugs, limit }),
|
|
1268
2109
|
lever.fetch({ slugs: leverSlugs, limit }),
|
|
2110
|
+
workable.fetch({ slugs: workableSlugs, limit }),
|
|
1269
2111
|
himalayas.fetch({ limit }),
|
|
1270
2112
|
wwr.fetch({ limit }),
|
|
1271
2113
|
hn.fetch({ limit })
|
|
1272
2114
|
]);
|
|
1273
2115
|
const seen = /* @__PURE__ */ new Set();
|
|
1274
2116
|
const jobs = [];
|
|
1275
|
-
const sourceNames = ["greenhouse", "ashby", "lever", "himalayas", "wwr", "hn"];
|
|
2117
|
+
const sourceNames = ["greenhouse", "ashby", "lever", "workable", "himalayas", "wwr", "hn"];
|
|
1276
2118
|
for (let i = 0; i < settled.length; i++) {
|
|
1277
2119
|
const result = settled[i];
|
|
1278
2120
|
if (result.status === "rejected") {
|
|
@@ -1288,7 +2130,7 @@ async function aggregate(opts) {
|
|
|
1288
2130
|
}
|
|
1289
2131
|
if (opts?.includeBounties !== false) {
|
|
1290
2132
|
try {
|
|
1291
|
-
const bounties = await
|
|
2133
|
+
const bounties = await aggregateBounties({ repos: opts?.slugs?.["bounty"] });
|
|
1292
2134
|
for (const b of bounties) {
|
|
1293
2135
|
if (!seen.has(b.id)) {
|
|
1294
2136
|
seen.add(b.id);
|
|
@@ -1301,7 +2143,7 @@ async function aggregate(opts) {
|
|
|
1301
2143
|
}
|
|
1302
2144
|
return jobs;
|
|
1303
2145
|
}
|
|
1304
|
-
var FEEDS, GREENHOUSE_SLUGS_BY_TIER, ASHBY_SLUGS_BY_TIER, LEVER_SLUGS_BY_TIER, DEFAULT_GREENHOUSE_SLUGS, DEFAULT_ASHBY_SLUGS, DEFAULT_LEVER_SLUGS;
|
|
2146
|
+
var FEEDS, GREENHOUSE_SLUGS_BY_TIER, ASHBY_SLUGS_BY_TIER, LEVER_SLUGS_BY_TIER, DEFAULT_GREENHOUSE_SLUGS, DEFAULT_ASHBY_SLUGS, DEFAULT_LEVER_SLUGS, DEFAULT_WORKABLE_SLUGS;
|
|
1305
2147
|
var init_feeds = __esm({
|
|
1306
2148
|
"../../packages/core/src/feeds/index.ts"() {
|
|
1307
2149
|
"use strict";
|
|
@@ -1312,8 +2154,10 @@ var init_feeds = __esm({
|
|
|
1312
2154
|
init_wwr();
|
|
1313
2155
|
init_hn();
|
|
1314
2156
|
init_github_bounties();
|
|
2157
|
+
init_opire();
|
|
2158
|
+
init_workable();
|
|
1315
2159
|
init_bounty_gate();
|
|
1316
|
-
FEEDS = [greenhouse, ashby, lever, himalayas, wwr, hn];
|
|
2160
|
+
FEEDS = [greenhouse, ashby, lever, workable, himalayas, wwr, hn];
|
|
1317
2161
|
GREENHOUSE_SLUGS_BY_TIER = {
|
|
1318
2162
|
bigco: [
|
|
1319
2163
|
"stripe",
|
|
@@ -1419,6 +2263,7 @@ var init_feeds = __esm({
|
|
|
1419
2263
|
DEFAULT_GREENHOUSE_SLUGS = flattenTiers(GREENHOUSE_SLUGS_BY_TIER);
|
|
1420
2264
|
DEFAULT_ASHBY_SLUGS = flattenTiers(ASHBY_SLUGS_BY_TIER);
|
|
1421
2265
|
DEFAULT_LEVER_SLUGS = flattenTiers(LEVER_SLUGS_BY_TIER);
|
|
2266
|
+
DEFAULT_WORKABLE_SLUGS = ["zego", "workmotion"];
|
|
1422
2267
|
}
|
|
1423
2268
|
});
|
|
1424
2269
|
|
|
@@ -1510,103 +2355,6 @@ var init_indexer = __esm({
|
|
|
1510
2355
|
}
|
|
1511
2356
|
});
|
|
1512
2357
|
|
|
1513
|
-
// ../../packages/core/src/github.ts
|
|
1514
|
-
function ghHeaders(token) {
|
|
1515
|
-
const headers = {
|
|
1516
|
-
Accept: "application/vnd.github+json",
|
|
1517
|
-
"X-GitHub-Api-Version": "2022-11-28"
|
|
1518
|
-
};
|
|
1519
|
-
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
1520
|
-
return headers;
|
|
1521
|
-
}
|
|
1522
|
-
async function ghFetch(path, token) {
|
|
1523
|
-
const url = `https://api.github.com${path}`;
|
|
1524
|
-
const res = await fetch(url, { headers: ghHeaders(token) });
|
|
1525
|
-
if (!res.ok) {
|
|
1526
|
-
throw new Error(`GitHub API ${path}: HTTP ${res.status} ${res.statusText}`);
|
|
1527
|
-
}
|
|
1528
|
-
return res.json();
|
|
1529
|
-
}
|
|
1530
|
-
async function fetchGitHubProfile(login, token) {
|
|
1531
|
-
const user = await ghFetch(`/users/${login}`, token);
|
|
1532
|
-
let repos = [];
|
|
1533
|
-
try {
|
|
1534
|
-
repos = await ghFetch(
|
|
1535
|
-
`/users/${login}/repos?sort=pushed&per_page=100`,
|
|
1536
|
-
token
|
|
1537
|
-
);
|
|
1538
|
-
} catch (err) {
|
|
1539
|
-
console.warn(`[github] ${login}: repos fetch failed, continuing \u2014`, err);
|
|
1540
|
-
}
|
|
1541
|
-
const langCount = {};
|
|
1542
|
-
for (const repo of repos) {
|
|
1543
|
-
if (repo.fork) continue;
|
|
1544
|
-
if (repo.language) {
|
|
1545
|
-
langCount[repo.language.toLowerCase()] = (langCount[repo.language.toLowerCase()] ?? 0) + 1;
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
const topLanguages = Object.entries(langCount).sort(([, a], [, b]) => b - a).slice(0, 10).map(([lang]) => lang);
|
|
1549
|
-
const topicSet = /* @__PURE__ */ new Set();
|
|
1550
|
-
for (const repo of repos) {
|
|
1551
|
-
if (repo.fork) continue;
|
|
1552
|
-
for (const t of repo.topics ?? []) topicSet.add(t.toLowerCase());
|
|
1553
|
-
}
|
|
1554
|
-
const topics = Array.from(topicSet).slice(0, 30);
|
|
1555
|
-
let recentPRorgs;
|
|
1556
|
-
try {
|
|
1557
|
-
const q = encodeURIComponent(
|
|
1558
|
-
`type:pr is:merged author:${login} sort:updated`
|
|
1559
|
-
);
|
|
1560
|
-
const result = await ghFetch(
|
|
1561
|
-
`/search/issues?q=${q}&per_page=30`,
|
|
1562
|
-
token
|
|
1563
|
-
);
|
|
1564
|
-
const orgs = /* @__PURE__ */ new Set();
|
|
1565
|
-
for (const item of result.items ?? []) {
|
|
1566
|
-
const orgLogin = item.repository?.owner?.login;
|
|
1567
|
-
if (orgLogin && orgLogin !== login) orgs.add(orgLogin);
|
|
1568
|
-
}
|
|
1569
|
-
if (orgs.size > 0) recentPRorgs = Array.from(orgs);
|
|
1570
|
-
} catch {
|
|
1571
|
-
}
|
|
1572
|
-
return {
|
|
1573
|
-
login: user.login,
|
|
1574
|
-
name: user.name ?? void 0,
|
|
1575
|
-
publicEmail: user.email ?? void 0,
|
|
1576
|
-
avatarUrl: user.avatar_url,
|
|
1577
|
-
accountCreatedAt: user.created_at,
|
|
1578
|
-
publicRepos: user.public_repos,
|
|
1579
|
-
followers: user.followers,
|
|
1580
|
-
topLanguages,
|
|
1581
|
-
topics,
|
|
1582
|
-
recentPRorgs
|
|
1583
|
-
};
|
|
1584
|
-
}
|
|
1585
|
-
function inferSeniority2(p) {
|
|
1586
|
-
const ageMs = Date.now() - new Date(p.accountCreatedAt).getTime();
|
|
1587
|
-
const ageYears = ageMs / (1e3 * 60 * 60 * 24 * 365.25);
|
|
1588
|
-
if (ageYears >= 9 && (p.publicRepos >= 40 || p.followers >= 500)) return "staff";
|
|
1589
|
-
if (ageYears >= 5 && (p.publicRepos >= 20 || p.followers >= 100)) return "senior";
|
|
1590
|
-
if (ageYears >= 2 && p.publicRepos >= 5) return "mid";
|
|
1591
|
-
return "junior";
|
|
1592
|
-
}
|
|
1593
|
-
function githubToFingerprint(p) {
|
|
1594
|
-
const rawTokens = [
|
|
1595
|
-
...p.topLanguages,
|
|
1596
|
-
...p.topics
|
|
1597
|
-
// recentPRorgs intentionally excluded — org names are not skill tags
|
|
1598
|
-
];
|
|
1599
|
-
const skillTags = normalize(rawTokens);
|
|
1600
|
-
const seniorityBand = inferSeniority2(p);
|
|
1601
|
-
return { skillTags, seniorityBand };
|
|
1602
|
-
}
|
|
1603
|
-
var init_github = __esm({
|
|
1604
|
-
"../../packages/core/src/github.ts"() {
|
|
1605
|
-
"use strict";
|
|
1606
|
-
init_vocabulary();
|
|
1607
|
-
}
|
|
1608
|
-
});
|
|
1609
|
-
|
|
1610
2358
|
// ../../packages/core/src/index.ts
|
|
1611
2359
|
var src_exports = {};
|
|
1612
2360
|
__export(src_exports, {
|
|
@@ -1616,22 +2364,32 @@ __export(src_exports, {
|
|
|
1616
2364
|
DEFAULT_BOUNTY_REPOS: () => DEFAULT_BOUNTY_REPOS,
|
|
1617
2365
|
DEFAULT_GREENHOUSE_SLUGS: () => DEFAULT_GREENHOUSE_SLUGS,
|
|
1618
2366
|
DEFAULT_LEVER_SLUGS: () => DEFAULT_LEVER_SLUGS,
|
|
2367
|
+
DEFAULT_WORKABLE_SLUGS: () => DEFAULT_WORKABLE_SLUGS,
|
|
1619
2368
|
EXAMPLE_BUYER: () => EXAMPLE_BUYER,
|
|
1620
2369
|
FEEDS: () => FEEDS,
|
|
1621
2370
|
GRAPH: () => GRAPH,
|
|
1622
2371
|
GREENHOUSE_SLUGS_BY_TIER: () => GREENHOUSE_SLUGS_BY_TIER,
|
|
2372
|
+
IDF_BACKGROUND: () => IDF_BACKGROUND,
|
|
1623
2373
|
LEVER_SLUGS_BY_TIER: () => LEVER_SLUGS_BY_TIER,
|
|
1624
2374
|
SYNONYMS: () => SYNONYMS,
|
|
1625
2375
|
VOCABULARY: () => VOCABULARY,
|
|
1626
2376
|
VOCAB_NODES: () => VOCAB_NODES,
|
|
2377
|
+
acceptanceCountForDomains: () => acceptanceCountForDomains,
|
|
1627
2378
|
aggregate: () => aggregate,
|
|
1628
2379
|
aggregateBounties: () => aggregateBounties,
|
|
1629
2380
|
ashby: () => ashby,
|
|
2381
|
+
bestAcceptanceDomain: () => bestAcceptanceDomain,
|
|
1630
2382
|
buildGraph: () => buildGraph,
|
|
1631
2383
|
buildIndex: () => buildIndex,
|
|
1632
2384
|
buildReason: () => buildReason,
|
|
2385
|
+
computeAcceptanceCredential: () => computeAcceptanceCredential,
|
|
2386
|
+
computeAcceptanceCredentialPublic: () => computeAcceptanceCredentialPublic,
|
|
2387
|
+
coreTagsFromTitle: () => coreTagsFromTitle,
|
|
2388
|
+
deriveResumeTrend: () => deriveResumeTrend,
|
|
1633
2389
|
expandWeighted: () => expandWeighted,
|
|
2390
|
+
extractSkillTags: () => extractSkillTags,
|
|
1634
2391
|
fetchGitHubProfile: () => fetchGitHubProfile,
|
|
2392
|
+
fetchRepoRecency: () => fetchRepoRecency,
|
|
1635
2393
|
flattenTiers: () => flattenTiers,
|
|
1636
2394
|
getBuyer: () => getBuyer,
|
|
1637
2395
|
githubBounties: () => githubBounties,
|
|
@@ -1642,11 +2400,14 @@ __export(src_exports, {
|
|
|
1642
2400
|
isBounty: () => isBounty,
|
|
1643
2401
|
lever: () => lever,
|
|
1644
2402
|
loadPartnerRoles: () => loadPartnerRoles,
|
|
2403
|
+
looksLikeEngRole: () => looksLikeEngRole,
|
|
1645
2404
|
match: () => match,
|
|
1646
|
-
matchOne: () => matchOne,
|
|
1647
2405
|
normalize: () => normalize,
|
|
2406
|
+
opire: () => opire,
|
|
1648
2407
|
passesMaturityGate: () => passesMaturityGate,
|
|
2408
|
+
tokenize: () => tokenize,
|
|
1649
2409
|
validateGraph: () => validateGraph,
|
|
2410
|
+
workable: () => workable,
|
|
1650
2411
|
wwr: () => wwr
|
|
1651
2412
|
});
|
|
1652
2413
|
var init_src = __esm({
|