terminalhire 0.4.0 → 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 +470 -39
- package/dist/bin/jpi-dispatch.js +472 -41
- package/dist/bin/jpi-jobs.js +470 -39
- package/dist/bin/jpi-learn.js +45 -1
- package/dist/bin/jpi-login.js +470 -39
- package/dist/bin/jpi-profile.js +45 -1
- package/dist/bin/jpi-refresh.js +472 -41
- package/dist/bin/jpi-save.js +45 -1
- package/dist/bin/jpi-spinner.js +1 -1
- package/dist/bin/jpi-sync.js +45 -1
- package/dist/bin/spinner.js +2 -2
- package/dist/src/profile.js +12 -1
- package/dist/src/signal.js +12 -1
- package/package.json +1 -1
package/dist/bin/jpi-dispatch.js
CHANGED
|
@@ -362,7 +362,7 @@ var init_graph_data = __esm({
|
|
|
362
362
|
{ id: "airflow", parents: ["data-engineering"], synonyms: ["apache-airflow"] },
|
|
363
363
|
{ id: "dbt", parents: ["data-engineering"] },
|
|
364
364
|
{ 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 }] },
|
|
365
|
-
{ 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 }] },
|
|
365
|
+
{ 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 }] },
|
|
366
366
|
{ id: "pytorch", parents: ["ml"], synonyms: ["torch"], related: [{ to: "tensorflow", w: 0.5 }] },
|
|
367
367
|
{ id: "tensorflow", parents: ["ml"], synonyms: ["keras", "tf-keras"] },
|
|
368
368
|
{ id: "pandas", parents: ["python"], related: [{ to: "numpy", w: 0.6 }, { to: "data-engineering", w: 0.45 }, { to: "spark", w: 0.4 }] },
|
|
@@ -375,6 +375,14 @@ var init_graph_data = __esm({
|
|
|
375
375
|
{ id: "anthropic", parents: ["llm"], synonyms: ["claude"] },
|
|
376
376
|
{ id: "rag", parents: ["llm"], synonyms: ["retrieval-augmented-generation"] },
|
|
377
377
|
{ id: "mlops", parents: ["ml"], related: [{ to: "devops", w: 0.4 }] },
|
|
378
|
+
{ id: "agents", parents: ["llm"], synonyms: ["agentic", "ai-agents", "multi-agent"], related: [{ to: "rag", w: 0.4 }] },
|
|
379
|
+
{ id: "mcp", parents: ["agents"], synonyms: ["model-context-protocol"], related: [{ to: "llm", w: 0.45 }] },
|
|
380
|
+
{ id: "inference", parents: ["ml"], synonyms: ["model-inference", "llm-inference", "model-serving"], related: [{ to: "mlops", w: 0.5 }, { to: "llm", w: 0.4 }] },
|
|
381
|
+
{ id: "embeddings", parents: ["ml"], synonyms: ["embedding", "vector-embeddings"], related: [{ to: "rag", w: 0.55 }, { to: "llm", w: 0.45 }] },
|
|
382
|
+
{ id: "prompt-engineering", parents: ["llm"], synonyms: ["prompting", "prompt"] },
|
|
383
|
+
{ id: "fine-tuning", parents: ["ml"], synonyms: ["finetuning", "fine-tune", "rlhf"], related: [{ to: "llm", w: 0.5 }] },
|
|
384
|
+
{ id: "computer-vision", parents: ["ml"], synonyms: ["image-recognition", "object-detection"] },
|
|
385
|
+
{ id: "recsys", parents: ["ml"], synonyms: ["recommender-systems", "recommendation-systems", "recommendation"] },
|
|
378
386
|
// ── Mobile ──────────────────────────────────────────────────────────────────
|
|
379
387
|
{ id: "mobile", related: [{ to: "ios", w: 0.5 }, { to: "android", w: 0.5 }] },
|
|
380
388
|
{ id: "ios", parents: ["mobile", "swift"], related: [{ to: "android", w: 0.4 }] },
|
|
@@ -797,7 +805,10 @@ var init_vocabulary = __esm({
|
|
|
797
805
|
function ghHeaders(token) {
|
|
798
806
|
const headers = {
|
|
799
807
|
Accept: "application/vnd.github+json",
|
|
800
|
-
"X-GitHub-Api-Version": "2022-11-28"
|
|
808
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
809
|
+
// GitHub's REST API REQUIRES a User-Agent; serverless runtimes don't always
|
|
810
|
+
// send a default (omitting it yields a 403 "administrative rules" error).
|
|
811
|
+
"User-Agent": "terminalhire"
|
|
801
812
|
};
|
|
802
813
|
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
803
814
|
return headers;
|
|
@@ -941,16 +952,25 @@ async function fetchRepoMeta(owner, name, token, cache) {
|
|
|
941
952
|
cache.set(key, meta);
|
|
942
953
|
return meta;
|
|
943
954
|
}
|
|
944
|
-
|
|
955
|
+
function emptyCredential(status) {
|
|
956
|
+
return { status, byDomain: {}, qualifyingTotal: 0, computedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
957
|
+
}
|
|
958
|
+
async function fetchPublicOrgs(login, token) {
|
|
959
|
+
try {
|
|
960
|
+
const orgs = await ghFetch(
|
|
961
|
+
`/users/${login}/orgs?per_page=100`,
|
|
962
|
+
token
|
|
963
|
+
);
|
|
964
|
+
return new Set(orgs.map((o) => o.login.toLowerCase()));
|
|
965
|
+
} catch {
|
|
966
|
+
return /* @__PURE__ */ new Set();
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
async function computeAcceptanceFromSearch(login, token, ownedOrgs, cache, gates = {
|
|
970
|
+
minStars: MIN_STARS,
|
|
971
|
+
minContributors: MIN_CONTRIBUTORS
|
|
972
|
+
}) {
|
|
945
973
|
const computedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
946
|
-
const empty = (status) => ({
|
|
947
|
-
status,
|
|
948
|
-
byDomain: {},
|
|
949
|
-
qualifyingTotal: 0,
|
|
950
|
-
computedAt
|
|
951
|
-
});
|
|
952
|
-
if (!token) return empty("no-token");
|
|
953
|
-
const ownedOrgs = await fetchOwnedOrgs(token);
|
|
954
974
|
const loginLc = login.toLowerCase();
|
|
955
975
|
let items;
|
|
956
976
|
try {
|
|
@@ -961,8 +981,9 @@ async function computeAcceptanceCredential(login, token, cache = /* @__PURE__ */
|
|
|
961
981
|
);
|
|
962
982
|
items = res.items ?? [];
|
|
963
983
|
} catch (err) {
|
|
964
|
-
const msg = String(err);
|
|
965
|
-
|
|
984
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
985
|
+
console.warn("[acceptance] search failed:", msg);
|
|
986
|
+
return emptyCredential(/HTTP 403|HTTP 429|rate limit/i.test(msg) ? "rate-limited" : "failed");
|
|
966
987
|
}
|
|
967
988
|
const byDomain = {};
|
|
968
989
|
let qualifyingTotal = 0;
|
|
@@ -976,8 +997,8 @@ async function computeAcceptanceCredential(login, token, cache = /* @__PURE__ */
|
|
|
976
997
|
const meta = await fetchRepoMeta(repo.owner, repo.name, token, cache);
|
|
977
998
|
if (!meta) continue;
|
|
978
999
|
if (meta.archived || meta.fork) continue;
|
|
979
|
-
if (meta.stars <
|
|
980
|
-
if (meta.contributors !== void 0 && meta.contributors <
|
|
1000
|
+
if (meta.stars < gates.minStars) continue;
|
|
1001
|
+
if (meta.contributors !== void 0 && meta.contributors < gates.minContributors) continue;
|
|
981
1002
|
qualifyingTotal += 1;
|
|
982
1003
|
const mergedAt = item.pull_request?.merged_at ?? item.closed_at ?? item.created_at;
|
|
983
1004
|
const rawDomains = [meta.language ?? "", ...meta.topics].filter(Boolean);
|
|
@@ -998,6 +1019,18 @@ async function computeAcceptanceCredential(login, token, cache = /* @__PURE__ */
|
|
|
998
1019
|
}
|
|
999
1020
|
return { status: "ok", byDomain: finalDomains, qualifyingTotal, computedAt };
|
|
1000
1021
|
}
|
|
1022
|
+
async function computeAcceptanceCredential(login, token, cache = /* @__PURE__ */ new Map()) {
|
|
1023
|
+
if (!token) return emptyCredential("no-token");
|
|
1024
|
+
const ownedOrgs = await fetchOwnedOrgs(token);
|
|
1025
|
+
return computeAcceptanceFromSearch(login, token, ownedOrgs, cache);
|
|
1026
|
+
}
|
|
1027
|
+
async function computeAcceptanceCredentialPublic(login, token, cache = /* @__PURE__ */ new Map(), opts) {
|
|
1028
|
+
if (!token) return emptyCredential("no-token");
|
|
1029
|
+
const ownedOrgs = await fetchPublicOrgs(login, token);
|
|
1030
|
+
for (const org of opts?.includeOrgs ?? []) ownedOrgs.delete(org.toLowerCase());
|
|
1031
|
+
const gates = opts?.relaxGates ? { minStars: 0, minContributors: 0 } : void 0;
|
|
1032
|
+
return computeAcceptanceFromSearch(login, token, ownedOrgs, cache, gates);
|
|
1033
|
+
}
|
|
1001
1034
|
function acceptanceCountForDomains(cred, domains) {
|
|
1002
1035
|
if (cred.status !== "ok") return 0;
|
|
1003
1036
|
let max = 0;
|
|
@@ -1016,7 +1049,60 @@ function bestAcceptanceDomain(cred, domains) {
|
|
|
1016
1049
|
}
|
|
1017
1050
|
return best;
|
|
1018
1051
|
}
|
|
1019
|
-
|
|
1052
|
+
function resumeRecencyDecay(lastSeenIso, now) {
|
|
1053
|
+
const ageMs = now - new Date(lastSeenIso).getTime();
|
|
1054
|
+
if (!Number.isFinite(ageMs)) return 0;
|
|
1055
|
+
return Math.pow(0.5, ageMs / RESUME_DECAY_HALF_LIFE_MS);
|
|
1056
|
+
}
|
|
1057
|
+
async function fetchRepoRecency(login, token) {
|
|
1058
|
+
try {
|
|
1059
|
+
const repos = await ghFetch(`/users/${login}/repos?sort=pushed&per_page=100`, token);
|
|
1060
|
+
return repos.filter((r) => !r.fork && !!r.pushed_at).map((r) => ({ pushedAt: r.pushed_at, language: r.language ?? null, topics: r.topics ?? [] }));
|
|
1061
|
+
} catch {
|
|
1062
|
+
return [];
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
function deriveResumeTrend(cred, repoRecency, now = Date.now()) {
|
|
1066
|
+
const agg = /* @__PURE__ */ new Map();
|
|
1067
|
+
const bump = (domain, when, count, mergedPRs) => {
|
|
1068
|
+
const e = agg.get(domain);
|
|
1069
|
+
if (!e) {
|
|
1070
|
+
agg.set(domain, { count, last: when, earliest: when, mergedPRs });
|
|
1071
|
+
} else {
|
|
1072
|
+
e.count += count;
|
|
1073
|
+
e.mergedPRs += mergedPRs;
|
|
1074
|
+
if (when > e.last) e.last = when;
|
|
1075
|
+
if (when < e.earliest) e.earliest = when;
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
if (cred.status === "ok") {
|
|
1079
|
+
for (const [domain, d] of Object.entries(cred.byDomain)) {
|
|
1080
|
+
bump(domain, d.lastMergedAt, d.mergedPRs, d.mergedPRs);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
for (const r of repoRecency) {
|
|
1084
|
+
for (const domain of new Set(normalize([r.language ?? "", ...r.topics].filter(Boolean)))) {
|
|
1085
|
+
bump(domain, r.pushedAt, 1, 0);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
const oneHalfLifeAgoIso = new Date(now - RESUME_DECAY_HALF_LIFE_MS).toISOString();
|
|
1089
|
+
const scored = [];
|
|
1090
|
+
for (const [domain, e] of agg.entries()) {
|
|
1091
|
+
const recencyScore2 = resumeRecencyDecay(e.last, now);
|
|
1092
|
+
const weight = e.count * recencyScore2;
|
|
1093
|
+
if (weight < RESUME_MIN_SCORE) continue;
|
|
1094
|
+
let direction;
|
|
1095
|
+
if (e.earliest > oneHalfLifeAgoIso) direction = "new";
|
|
1096
|
+
else if (recencyScore2 >= 0.5) direction = "up";
|
|
1097
|
+
else direction = "down";
|
|
1098
|
+
scored.push({
|
|
1099
|
+
t: { domain, direction, recencyScore: Math.round(recencyScore2 * 1e3) / 1e3, mergedPRs: e.mergedPRs },
|
|
1100
|
+
weight
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
return scored.sort((a, b) => b.weight - a.weight).slice(0, 12).map((s) => s.t);
|
|
1104
|
+
}
|
|
1105
|
+
var MIN_STARS, MIN_CONTRIBUTORS, CANDIDATE_PR_PAGE, TRIVIAL_PR_TITLE, RESUME_DECAY_HALF_LIFE_MS, RESUME_MIN_SCORE;
|
|
1020
1106
|
var init_github = __esm({
|
|
1021
1107
|
"../../packages/core/src/github.ts"() {
|
|
1022
1108
|
"use strict";
|
|
@@ -1025,6 +1111,8 @@ var init_github = __esm({
|
|
|
1025
1111
|
MIN_CONTRIBUTORS = 10;
|
|
1026
1112
|
CANDIDATE_PR_PAGE = 50;
|
|
1027
1113
|
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;
|
|
1114
|
+
RESUME_DECAY_HALF_LIFE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
1115
|
+
RESUME_MIN_SCORE = 0.05;
|
|
1028
1116
|
}
|
|
1029
1117
|
});
|
|
1030
1118
|
|
|
@@ -1172,6 +1260,18 @@ var init_matcher = __esm({
|
|
|
1172
1260
|
}
|
|
1173
1261
|
});
|
|
1174
1262
|
|
|
1263
|
+
// ../../packages/core/src/feeds/http.ts
|
|
1264
|
+
function fetchWithTimeout(input, init, timeoutMs = FEED_FETCH_TIMEOUT_MS) {
|
|
1265
|
+
return fetch(input, { ...init, signal: AbortSignal.timeout(timeoutMs) });
|
|
1266
|
+
}
|
|
1267
|
+
var FEED_FETCH_TIMEOUT_MS;
|
|
1268
|
+
var init_http = __esm({
|
|
1269
|
+
"../../packages/core/src/feeds/http.ts"() {
|
|
1270
|
+
"use strict";
|
|
1271
|
+
FEED_FETCH_TIMEOUT_MS = 1e4;
|
|
1272
|
+
}
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1175
1275
|
// ../../packages/core/src/feeds/greenhouse.ts
|
|
1176
1276
|
function extractTags(job) {
|
|
1177
1277
|
const body = [
|
|
@@ -1190,7 +1290,7 @@ async function fetchSlug(slug) {
|
|
|
1190
1290
|
const url = `https://boards-api.greenhouse.io/v1/boards/${slug}/jobs?content=true`;
|
|
1191
1291
|
let res;
|
|
1192
1292
|
try {
|
|
1193
|
-
res = await
|
|
1293
|
+
res = await fetchWithTimeout(url, { headers: { Accept: "application/json" } });
|
|
1194
1294
|
} catch (err) {
|
|
1195
1295
|
console.warn(`[greenhouse] ${slug}: network error \u2014`, err);
|
|
1196
1296
|
return [];
|
|
@@ -1232,6 +1332,7 @@ var init_greenhouse = __esm({
|
|
|
1232
1332
|
"../../packages/core/src/feeds/greenhouse.ts"() {
|
|
1233
1333
|
"use strict";
|
|
1234
1334
|
init_vocabulary();
|
|
1335
|
+
init_http();
|
|
1235
1336
|
FALLBACK_SLUGS = [
|
|
1236
1337
|
"stripe",
|
|
1237
1338
|
"linear",
|
|
@@ -1302,7 +1403,7 @@ function inferRemote2(job) {
|
|
|
1302
1403
|
}
|
|
1303
1404
|
async function fetchSlug2(slug) {
|
|
1304
1405
|
const url = `https://api.ashbyhq.com/posting-api/job-board/${slug}`;
|
|
1305
|
-
const res = await
|
|
1406
|
+
const res = await fetchWithTimeout(url, {
|
|
1306
1407
|
headers: { Accept: "application/json" }
|
|
1307
1408
|
});
|
|
1308
1409
|
if (!res.ok) {
|
|
@@ -1334,6 +1435,7 @@ var init_ashby = __esm({
|
|
|
1334
1435
|
"../../packages/core/src/feeds/ashby.ts"() {
|
|
1335
1436
|
"use strict";
|
|
1336
1437
|
init_vocabulary();
|
|
1438
|
+
init_http();
|
|
1337
1439
|
ashby = {
|
|
1338
1440
|
source: "ashby",
|
|
1339
1441
|
async fetch(opts) {
|
|
@@ -1384,7 +1486,7 @@ function toIso(ms) {
|
|
|
1384
1486
|
}
|
|
1385
1487
|
async function fetchSlug3(slug) {
|
|
1386
1488
|
const url = `https://api.lever.co/v0/postings/${slug}?mode=json`;
|
|
1387
|
-
const res = await
|
|
1489
|
+
const res = await fetchWithTimeout(url, { headers: { Accept: "application/json" } });
|
|
1388
1490
|
if (!res.ok) {
|
|
1389
1491
|
throw new Error(`Lever ${slug}: HTTP ${res.status}`);
|
|
1390
1492
|
}
|
|
@@ -1415,6 +1517,7 @@ var init_lever = __esm({
|
|
|
1415
1517
|
"../../packages/core/src/feeds/lever.ts"() {
|
|
1416
1518
|
"use strict";
|
|
1417
1519
|
init_vocabulary();
|
|
1520
|
+
init_http();
|
|
1418
1521
|
lever = {
|
|
1419
1522
|
source: "lever",
|
|
1420
1523
|
async fetch(opts) {
|
|
@@ -1456,12 +1559,13 @@ var init_himalayas = __esm({
|
|
|
1456
1559
|
"../../packages/core/src/feeds/himalayas.ts"() {
|
|
1457
1560
|
"use strict";
|
|
1458
1561
|
init_vocabulary();
|
|
1562
|
+
init_http();
|
|
1459
1563
|
himalayas = {
|
|
1460
1564
|
source: "himalayas",
|
|
1461
1565
|
async fetch(opts) {
|
|
1462
1566
|
const limit = opts?.limit ?? 100;
|
|
1463
1567
|
const url = `https://himalayas.app/jobs/api?limit=${limit}`;
|
|
1464
|
-
const res = await
|
|
1568
|
+
const res = await fetchWithTimeout(url, {
|
|
1465
1569
|
headers: { Accept: "application/json" }
|
|
1466
1570
|
});
|
|
1467
1571
|
if (!res.ok) {
|
|
@@ -1570,12 +1674,13 @@ var init_wwr = __esm({
|
|
|
1570
1674
|
"use strict";
|
|
1571
1675
|
init_vocabulary();
|
|
1572
1676
|
init_entities();
|
|
1677
|
+
init_http();
|
|
1573
1678
|
WWR_RSS_URL = "https://weworkremotely.com/remote-jobs.rss";
|
|
1574
1679
|
wwr = {
|
|
1575
1680
|
source: "wwr",
|
|
1576
1681
|
async fetch(opts) {
|
|
1577
1682
|
const limit = opts?.limit ?? 200;
|
|
1578
|
-
const res = await
|
|
1683
|
+
const res = await fetchWithTimeout(WWR_RSS_URL, {
|
|
1579
1684
|
headers: { Accept: "application/rss+xml, application/xml, text/xml" }
|
|
1580
1685
|
});
|
|
1581
1686
|
if (!res.ok) {
|
|
@@ -1583,6 +1688,11 @@ var init_wwr = __esm({
|
|
|
1583
1688
|
}
|
|
1584
1689
|
const xml = await res.text();
|
|
1585
1690
|
const items = parseRss(xml).slice(0, limit);
|
|
1691
|
+
function safeIso(s) {
|
|
1692
|
+
if (!s) return void 0;
|
|
1693
|
+
const d = new Date(s);
|
|
1694
|
+
return Number.isNaN(d.getTime()) ? void 0 : d.toISOString();
|
|
1695
|
+
}
|
|
1586
1696
|
return items.map((item) => ({
|
|
1587
1697
|
id: extractId(item.link),
|
|
1588
1698
|
source: "wwr",
|
|
@@ -1594,7 +1704,7 @@ var init_wwr = __esm({
|
|
|
1594
1704
|
location: "Remote",
|
|
1595
1705
|
tags: extractTags5(item),
|
|
1596
1706
|
roleType: inferRoleType(item.category),
|
|
1597
|
-
postedAt:
|
|
1707
|
+
postedAt: safeIso(item.pubDate),
|
|
1598
1708
|
applyMode: "direct",
|
|
1599
1709
|
raw: item
|
|
1600
1710
|
}));
|
|
@@ -1660,13 +1770,14 @@ var init_hn = __esm({
|
|
|
1660
1770
|
"use strict";
|
|
1661
1771
|
init_vocabulary();
|
|
1662
1772
|
init_entities();
|
|
1773
|
+
init_http();
|
|
1663
1774
|
ALGOLIA_SEARCH = "https://hn.algolia.com/api/v1/search?query=Ask+HN%3A+Who+is+Hiring%3F&tags=story,ask_hn&hitsPerPage=1";
|
|
1664
1775
|
ALGOLIA_ITEMS = "https://hn.algolia.com/api/v1/items/";
|
|
1665
1776
|
hn = {
|
|
1666
1777
|
source: "hn",
|
|
1667
1778
|
async fetch(opts) {
|
|
1668
1779
|
const limit = opts?.limit ?? 150;
|
|
1669
|
-
const searchRes = await
|
|
1780
|
+
const searchRes = await fetchWithTimeout(ALGOLIA_SEARCH, {
|
|
1670
1781
|
headers: { Accept: "application/json" }
|
|
1671
1782
|
});
|
|
1672
1783
|
if (!searchRes.ok) {
|
|
@@ -1677,7 +1788,7 @@ var init_hn = __esm({
|
|
|
1677
1788
|
if (!story) {
|
|
1678
1789
|
throw new Error('HN: No "Who is Hiring" story found');
|
|
1679
1790
|
}
|
|
1680
|
-
const itemRes = await
|
|
1791
|
+
const itemRes = await fetchWithTimeout(`${ALGOLIA_ITEMS}${story.objectID}`, {
|
|
1681
1792
|
headers: { Accept: "application/json" }
|
|
1682
1793
|
});
|
|
1683
1794
|
if (!itemRes.ok) {
|
|
@@ -1771,7 +1882,7 @@ function isBountyIssue(issue) {
|
|
|
1771
1882
|
async function ghJson(path) {
|
|
1772
1883
|
let res;
|
|
1773
1884
|
try {
|
|
1774
|
-
res = await
|
|
1885
|
+
res = await fetchWithTimeout(`${GITHUB_API}${path}`, { headers: authHeaders() });
|
|
1775
1886
|
} catch (err) {
|
|
1776
1887
|
console.warn(`[github-bounties] network error ${path} \u2014`, err);
|
|
1777
1888
|
return null;
|
|
@@ -1853,31 +1964,328 @@ async function fetchRepoBounties(repoFullName) {
|
|
|
1853
1964
|
};
|
|
1854
1965
|
}));
|
|
1855
1966
|
}
|
|
1856
|
-
|
|
1967
|
+
function repoFullNameFromApiUrl(url) {
|
|
1968
|
+
const m = url.match(/\/repos\/([^/]+)\/([^/]+)\/?$/);
|
|
1969
|
+
return m ? `${m[1]}/${m[2]}` : null;
|
|
1970
|
+
}
|
|
1971
|
+
async function searchBountyIssues() {
|
|
1972
|
+
const byUrl = /* @__PURE__ */ new Map();
|
|
1973
|
+
for (const q of SEARCH_QUERIES) {
|
|
1974
|
+
const res = await ghJson(
|
|
1975
|
+
`/search/issues?q=${encodeURIComponent(q)}&sort=created&order=desc&per_page=${SEARCH_PER_PAGE}`
|
|
1976
|
+
);
|
|
1977
|
+
for (const it of res?.items ?? []) {
|
|
1978
|
+
if (it.pull_request) continue;
|
|
1979
|
+
if (!byUrl.has(it.html_url)) byUrl.set(it.html_url, it);
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
return [...byUrl.values()].sort(
|
|
1983
|
+
(a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
|
|
1984
|
+
);
|
|
1985
|
+
}
|
|
1986
|
+
async function repoMetaCached(fullName) {
|
|
1987
|
+
const hit = repoMetaCache.get(fullName);
|
|
1988
|
+
if (hit !== void 0) return hit;
|
|
1989
|
+
const r = await ghJson(`/repos/${fullName}`) ?? null;
|
|
1990
|
+
repoMetaCache.set(fullName, r);
|
|
1991
|
+
return r;
|
|
1992
|
+
}
|
|
1993
|
+
async function fetchSearchBounties() {
|
|
1994
|
+
const issues = (await searchBountyIssues()).slice(0, MAX_SEARCH_ISSUES_SCANNED);
|
|
1995
|
+
const distinctRepos = [
|
|
1996
|
+
...new Set(
|
|
1997
|
+
issues.map((i) => repoFullNameFromApiUrl(i.repository_url)).filter((x) => !!x)
|
|
1998
|
+
)
|
|
1999
|
+
];
|
|
2000
|
+
for (let i = 0; i < distinctRepos.length; i += REPO_META_CONCURRENCY) {
|
|
2001
|
+
await Promise.all(distinctRepos.slice(i, i + REPO_META_CONCURRENCY).map(repoMetaCached));
|
|
2002
|
+
}
|
|
2003
|
+
const jobs = [];
|
|
2004
|
+
const perRepo = /* @__PURE__ */ new Map();
|
|
2005
|
+
for (const issue of issues) {
|
|
2006
|
+
if (jobs.length >= MAX_SEARCH_BOUNTIES) break;
|
|
2007
|
+
const fullName = repoFullNameFromApiUrl(issue.repository_url);
|
|
2008
|
+
if (!fullName) continue;
|
|
2009
|
+
if ((perRepo.get(fullName) ?? 0) >= MAX_BOUNTIES_PER_REPO) continue;
|
|
2010
|
+
const repo = await repoMetaCached(fullName);
|
|
2011
|
+
if (!repo) continue;
|
|
2012
|
+
const passes = passesMaturityGate({
|
|
2013
|
+
fullName: repo.full_name,
|
|
2014
|
+
stargazers: repo.stargazers_count,
|
|
2015
|
+
createdAt: repo.created_at,
|
|
2016
|
+
archived: repo.archived,
|
|
2017
|
+
disabled: repo.disabled
|
|
2018
|
+
});
|
|
2019
|
+
if (!passes) continue;
|
|
2020
|
+
const title = decodeEntities(issue.title).trim();
|
|
2021
|
+
const body = issue.body ? decodeEntities(issue.body) : "";
|
|
2022
|
+
const labels = labelNames(issue);
|
|
2023
|
+
let amountUSD = parseAmountUSD(title) ?? parseAmountUSD(labels.join(" ")) ?? parseAmountUSD(body);
|
|
2024
|
+
if (amountUSD == null && labels.some((n) => /💎|💰/.test(n))) {
|
|
2025
|
+
amountUSD = await fetchCommentAmount(fullName, issue.number);
|
|
2026
|
+
}
|
|
2027
|
+
if (amountUSD == null) continue;
|
|
2028
|
+
if (amountUSD > SEARCH_HIGH_VALUE_USD && repo.stargazers_count < SEARCH_HIGH_VALUE_MIN_STARS) continue;
|
|
2029
|
+
const tags = normalize(
|
|
2030
|
+
tokenize2([title, labels.join(" "), body.slice(0, 2e3)].join(" "))
|
|
2031
|
+
);
|
|
2032
|
+
perRepo.set(fullName, (perRepo.get(fullName) ?? 0) + 1);
|
|
2033
|
+
jobs.push({
|
|
2034
|
+
id: `bounty:${fullName}#${issue.number}`,
|
|
2035
|
+
source: "bounty",
|
|
2036
|
+
title,
|
|
2037
|
+
company: repo.owner.login,
|
|
2038
|
+
url: issue.html_url,
|
|
2039
|
+
remote: true,
|
|
2040
|
+
location: "Remote",
|
|
2041
|
+
tags,
|
|
2042
|
+
roleType: "freelance",
|
|
2043
|
+
postedAt: issue.created_at,
|
|
2044
|
+
applyMode: "direct",
|
|
2045
|
+
bounty: {
|
|
2046
|
+
amountUSD,
|
|
2047
|
+
estimatedEffort: effortFromAmount(amountUSD),
|
|
2048
|
+
bountySource: "github",
|
|
2049
|
+
claimUrl: issue.html_url,
|
|
2050
|
+
repoFullName: fullName,
|
|
2051
|
+
repoStars: repo.stargazers_count,
|
|
2052
|
+
issueBody: body.slice(0, 1e3) || void 0
|
|
2053
|
+
},
|
|
2054
|
+
raw: issue
|
|
2055
|
+
});
|
|
2056
|
+
}
|
|
2057
|
+
return jobs;
|
|
2058
|
+
}
|
|
2059
|
+
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;
|
|
1857
2060
|
var init_github_bounties = __esm({
|
|
1858
2061
|
"../../packages/core/src/feeds/github-bounties.ts"() {
|
|
1859
2062
|
"use strict";
|
|
1860
2063
|
init_vocabulary();
|
|
1861
2064
|
init_entities();
|
|
1862
2065
|
init_bounty_gate();
|
|
2066
|
+
init_http();
|
|
1863
2067
|
GITHUB_API = "https://api.github.com";
|
|
1864
2068
|
BOUNTY_LABEL_RE = /bounty|reward|funded|💎|💰/i;
|
|
2069
|
+
SEARCH_QUERIES = [
|
|
2070
|
+
'label:"\u{1F48E} Bounty" type:issue state:open',
|
|
2071
|
+
// Algora-applied — highest signal
|
|
2072
|
+
"label:bounty type:issue state:open",
|
|
2073
|
+
'label:"\u{1F4B0} Bounty" type:issue state:open'
|
|
2074
|
+
];
|
|
2075
|
+
SEARCH_PER_PAGE = 100;
|
|
2076
|
+
MAX_SEARCH_BOUNTIES = 150;
|
|
2077
|
+
MAX_SEARCH_ISSUES_SCANNED = 300;
|
|
2078
|
+
REPO_META_CONCURRENCY = 15;
|
|
2079
|
+
SEARCH_HIGH_VALUE_USD = 500;
|
|
2080
|
+
SEARCH_HIGH_VALUE_MIN_STARS = 50;
|
|
2081
|
+
repoMetaCache = /* @__PURE__ */ new Map();
|
|
1865
2082
|
githubBounties = {
|
|
1866
2083
|
source: "bounty",
|
|
1867
2084
|
async fetch(opts) {
|
|
1868
|
-
const
|
|
1869
|
-
|
|
1870
|
-
|
|
2085
|
+
const allowlist = opts?.slugs && opts.slugs.length > 0 ? opts.slugs : DEFAULT_BOUNTY_REPOS;
|
|
2086
|
+
const [searched, listed] = await Promise.all([
|
|
2087
|
+
fetchSearchBounties().catch((e) => {
|
|
2088
|
+
console.warn("[github-bounties] search discovery failed:", e);
|
|
2089
|
+
return [];
|
|
2090
|
+
}),
|
|
2091
|
+
Promise.allSettled(allowlist.map(fetchRepoBounties)).then(
|
|
2092
|
+
(settled) => settled.flatMap((r) => r.status === "fulfilled" ? r.value : [])
|
|
2093
|
+
)
|
|
2094
|
+
]);
|
|
2095
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2096
|
+
const out = [];
|
|
2097
|
+
for (const j of [...searched, ...listed]) {
|
|
2098
|
+
if (!seen.has(j.id)) {
|
|
2099
|
+
seen.add(j.id);
|
|
2100
|
+
out.push(j);
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
console.info(
|
|
2104
|
+
`[github-bounties] total: ${out.length} bounties (${searched.length} search + ${listed.length} allowlist, deduped)`
|
|
2105
|
+
);
|
|
2106
|
+
return out;
|
|
2107
|
+
}
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
});
|
|
2111
|
+
|
|
2112
|
+
// ../../packages/core/src/feeds/opire.ts
|
|
2113
|
+
function tokenize3(text) {
|
|
2114
|
+
return text.toLowerCase().replace(/[^a-z0-9.\-+#]/g, " ").split(/\s+/).filter((w) => w.length > 1);
|
|
2115
|
+
}
|
|
2116
|
+
function effortFromAmount2(usd) {
|
|
2117
|
+
if (usd == null) return void 0;
|
|
2118
|
+
if (usd < 150) return "small";
|
|
2119
|
+
if (usd < 750) return "medium";
|
|
2120
|
+
return "large";
|
|
2121
|
+
}
|
|
2122
|
+
function priceToUSD(p) {
|
|
2123
|
+
if (!p || typeof p.value !== "number") return void 0;
|
|
2124
|
+
if (p.unit === "USD_CENT") return Math.round(p.value) / 100;
|
|
2125
|
+
if (p.unit === "USD") return p.value;
|
|
2126
|
+
return void 0;
|
|
2127
|
+
}
|
|
2128
|
+
function repoFullNameFromUrl(url) {
|
|
2129
|
+
const m = url?.match(/github\.com\/([^/]+)\/([^/]+)/i);
|
|
2130
|
+
return m ? `${m[1]}/${m[2].replace(/\.git$/, "")}` : void 0;
|
|
2131
|
+
}
|
|
2132
|
+
var OPIRE_REWARDS_URL, MIN_USD, MAX_USD, MAX_OPIRE_BOUNTIES, opire;
|
|
2133
|
+
var init_opire = __esm({
|
|
2134
|
+
"../../packages/core/src/feeds/opire.ts"() {
|
|
2135
|
+
"use strict";
|
|
2136
|
+
init_vocabulary();
|
|
2137
|
+
init_http();
|
|
2138
|
+
OPIRE_REWARDS_URL = "https://api.opire.dev/rewards";
|
|
2139
|
+
MIN_USD = 25;
|
|
2140
|
+
MAX_USD = 25e3;
|
|
2141
|
+
MAX_OPIRE_BOUNTIES = 100;
|
|
2142
|
+
opire = {
|
|
2143
|
+
source: "bounty",
|
|
2144
|
+
async fetch() {
|
|
2145
|
+
let rewards;
|
|
2146
|
+
try {
|
|
2147
|
+
const res = await fetchWithTimeout(OPIRE_REWARDS_URL, {
|
|
2148
|
+
headers: { Accept: "application/json", "User-Agent": "terminalhire" }
|
|
2149
|
+
});
|
|
2150
|
+
if (!res.ok) {
|
|
2151
|
+
console.warn(`[opire] HTTP ${res.status}`);
|
|
2152
|
+
return [];
|
|
2153
|
+
}
|
|
2154
|
+
const json = await res.json();
|
|
2155
|
+
rewards = Array.isArray(json) ? json : json?.data ?? json?.items ?? [];
|
|
2156
|
+
} catch (err) {
|
|
2157
|
+
console.warn("[opire] fetch failed \u2014", err);
|
|
2158
|
+
return [];
|
|
2159
|
+
}
|
|
2160
|
+
const jobs = [];
|
|
2161
|
+
for (const r of rewards) {
|
|
2162
|
+
if (r.platform !== "GitHub") continue;
|
|
2163
|
+
if (r.project && r.project.isPublic === false) continue;
|
|
2164
|
+
const repoFullName = repoFullNameFromUrl(r.project?.url ?? r.url);
|
|
2165
|
+
if (!repoFullName) continue;
|
|
2166
|
+
const amountUSD = priceToUSD(r.pendingPrice);
|
|
2167
|
+
if (amountUSD == null || amountUSD < MIN_USD || amountUSD > MAX_USD) continue;
|
|
2168
|
+
const title = (r.title ?? "").trim();
|
|
2169
|
+
if (title.length < 4) continue;
|
|
2170
|
+
const tags = normalize([...r.programmingLanguages ?? [], ...tokenize3(title)]);
|
|
2171
|
+
jobs.push({
|
|
2172
|
+
id: `bounty:opire:${r.id}`,
|
|
2173
|
+
source: "bounty",
|
|
2174
|
+
title,
|
|
2175
|
+
company: r.organization?.name ?? repoFullName.split("/")[0],
|
|
2176
|
+
url: r.url,
|
|
2177
|
+
remote: true,
|
|
2178
|
+
location: "Remote",
|
|
2179
|
+
tags,
|
|
2180
|
+
roleType: "freelance",
|
|
2181
|
+
postedAt: Number.isFinite(r.createdAt) ? new Date(r.createdAt).toISOString() : void 0,
|
|
2182
|
+
applyMode: "direct",
|
|
2183
|
+
bounty: {
|
|
2184
|
+
amountUSD,
|
|
2185
|
+
estimatedEffort: effortFromAmount2(amountUSD),
|
|
2186
|
+
bountySource: "opire",
|
|
2187
|
+
claimUrl: r.url,
|
|
2188
|
+
repoFullName
|
|
2189
|
+
},
|
|
2190
|
+
raw: r
|
|
2191
|
+
});
|
|
2192
|
+
if (jobs.length >= MAX_OPIRE_BOUNTIES) break;
|
|
2193
|
+
}
|
|
2194
|
+
console.info(`[opire] ${jobs.length} bounties (from ${rewards.length} rewards)`);
|
|
2195
|
+
return jobs;
|
|
2196
|
+
}
|
|
2197
|
+
};
|
|
2198
|
+
}
|
|
2199
|
+
});
|
|
2200
|
+
|
|
2201
|
+
// ../../packages/core/src/feeds/workable.ts
|
|
2202
|
+
function locationStr(loc) {
|
|
2203
|
+
if (!loc) return "";
|
|
2204
|
+
return [loc.city, loc.country].filter(Boolean).join(", ");
|
|
2205
|
+
}
|
|
2206
|
+
function isRemote(j) {
|
|
2207
|
+
return j.remote === true || (j.workplace ?? "").toLowerCase() === "remote";
|
|
2208
|
+
}
|
|
2209
|
+
function extractTags7(j) {
|
|
2210
|
+
const body = [...j.department ?? [], locationStr(j.location)].filter(Boolean).join(" ");
|
|
2211
|
+
return extractSkillTags(j.title, body);
|
|
2212
|
+
}
|
|
2213
|
+
async function fetchAccount(account) {
|
|
2214
|
+
const url = `https://apply.workable.com/api/v3/accounts/${account}/jobs`;
|
|
2215
|
+
const out = [];
|
|
2216
|
+
let token;
|
|
2217
|
+
for (let page = 0; page < MAX_PAGES; page++) {
|
|
2218
|
+
let res;
|
|
2219
|
+
try {
|
|
2220
|
+
res = await fetchWithTimeout(url, {
|
|
2221
|
+
method: "POST",
|
|
2222
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
2223
|
+
body: JSON.stringify(token ? { token } : {})
|
|
2224
|
+
});
|
|
2225
|
+
} catch (err) {
|
|
2226
|
+
console.warn(`[workable] ${account}: network error \u2014`, err);
|
|
2227
|
+
break;
|
|
2228
|
+
}
|
|
2229
|
+
if (!res.ok) {
|
|
2230
|
+
console.warn(`[workable] ${account}: HTTP ${res.status}`);
|
|
2231
|
+
break;
|
|
2232
|
+
}
|
|
2233
|
+
let data;
|
|
2234
|
+
try {
|
|
2235
|
+
data = await res.json();
|
|
2236
|
+
} catch (err) {
|
|
2237
|
+
console.warn(`[workable] ${account}: JSON parse error \u2014`, err);
|
|
2238
|
+
break;
|
|
2239
|
+
}
|
|
2240
|
+
const results = data.results ?? [];
|
|
2241
|
+
for (const j of results) {
|
|
2242
|
+
if (j.state && j.state !== "published") continue;
|
|
2243
|
+
out.push({
|
|
2244
|
+
id: `workable:${j.id}`,
|
|
2245
|
+
source: "workable",
|
|
2246
|
+
title: j.title,
|
|
2247
|
+
company: account,
|
|
2248
|
+
url: `https://apply.workable.com/${account}/j/${j.shortcode}/`,
|
|
2249
|
+
remote: isRemote(j),
|
|
2250
|
+
location: locationStr(j.location) || void 0,
|
|
2251
|
+
tags: extractTags7(j),
|
|
2252
|
+
roleType: "full_time",
|
|
2253
|
+
postedAt: j.published,
|
|
2254
|
+
applyMode: "direct",
|
|
2255
|
+
raw: j
|
|
2256
|
+
});
|
|
2257
|
+
}
|
|
2258
|
+
token = data.token;
|
|
2259
|
+
if (!token || results.length === 0) break;
|
|
2260
|
+
}
|
|
2261
|
+
if (out.length > 0) console.info(`[workable] ${account}: ${out.length} jobs`);
|
|
2262
|
+
return out;
|
|
2263
|
+
}
|
|
2264
|
+
var FALLBACK_ACCOUNTS, MAX_PAGES, workable;
|
|
2265
|
+
var init_workable = __esm({
|
|
2266
|
+
"../../packages/core/src/feeds/workable.ts"() {
|
|
2267
|
+
"use strict";
|
|
2268
|
+
init_vocabulary();
|
|
2269
|
+
init_http();
|
|
2270
|
+
FALLBACK_ACCOUNTS = ["zego", "workmotion"];
|
|
2271
|
+
MAX_PAGES = 5;
|
|
2272
|
+
workable = {
|
|
2273
|
+
source: "workable",
|
|
2274
|
+
async fetch(opts) {
|
|
2275
|
+
const accounts = opts?.slugs && opts.slugs.length > 0 ? opts.slugs : FALLBACK_ACCOUNTS;
|
|
2276
|
+
console.info(`[workable] fetching ${accounts.length} accounts: ${accounts.join(", ")}`);
|
|
2277
|
+
const results = await Promise.allSettled(accounts.map(fetchAccount));
|
|
1871
2278
|
const jobs = [];
|
|
1872
2279
|
let failures = 0;
|
|
1873
|
-
for (const r of
|
|
1874
|
-
if (r.status === "fulfilled")
|
|
1875
|
-
|
|
2280
|
+
for (const r of results) {
|
|
2281
|
+
if (r.status === "fulfilled") {
|
|
2282
|
+
jobs.push(...r.value);
|
|
2283
|
+
} else {
|
|
1876
2284
|
failures++;
|
|
1877
|
-
console.warn("[
|
|
2285
|
+
console.warn("[workable] account fetch rejected:", r.reason);
|
|
1878
2286
|
}
|
|
1879
2287
|
}
|
|
1880
|
-
console.info(`[
|
|
2288
|
+
console.info(`[workable] total: ${jobs.length} jobs, ${failures} account failures`);
|
|
1881
2289
|
return jobs;
|
|
1882
2290
|
}
|
|
1883
2291
|
};
|
|
@@ -1886,7 +2294,19 @@ var init_github_bounties = __esm({
|
|
|
1886
2294
|
|
|
1887
2295
|
// ../../packages/core/src/feeds/index.ts
|
|
1888
2296
|
async function aggregateBounties(opts) {
|
|
1889
|
-
|
|
2297
|
+
const [gh, op] = await Promise.all([
|
|
2298
|
+
githubBounties.fetch({ slugs: opts?.repos }),
|
|
2299
|
+
opire.fetch()
|
|
2300
|
+
]);
|
|
2301
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2302
|
+
const out = [];
|
|
2303
|
+
for (const j of [...gh, ...op]) {
|
|
2304
|
+
const key = j.bounty?.claimUrl ?? j.url;
|
|
2305
|
+
if (seen.has(key)) continue;
|
|
2306
|
+
seen.add(key);
|
|
2307
|
+
out.push(j);
|
|
2308
|
+
}
|
|
2309
|
+
return out;
|
|
1890
2310
|
}
|
|
1891
2311
|
function flattenTiers(t) {
|
|
1892
2312
|
return [.../* @__PURE__ */ new Set([...t.bigco, ...t.scaleup, ...t.startup])];
|
|
@@ -1895,18 +2315,20 @@ async function aggregate(opts) {
|
|
|
1895
2315
|
const ghSlugs = opts?.slugs?.["greenhouse"] ?? DEFAULT_GREENHOUSE_SLUGS;
|
|
1896
2316
|
const ashbySlugs = opts?.slugs?.["ashby"] ?? DEFAULT_ASHBY_SLUGS;
|
|
1897
2317
|
const leverSlugs = opts?.slugs?.["lever"] ?? DEFAULT_LEVER_SLUGS;
|
|
2318
|
+
const workableSlugs = opts?.slugs?.["workable"] ?? DEFAULT_WORKABLE_SLUGS;
|
|
1898
2319
|
const limit = opts?.limit ?? 150;
|
|
1899
2320
|
const settled = await Promise.allSettled([
|
|
1900
2321
|
greenhouse.fetch({ slugs: ghSlugs, limit }),
|
|
1901
2322
|
ashby.fetch({ slugs: ashbySlugs, limit }),
|
|
1902
2323
|
lever.fetch({ slugs: leverSlugs, limit }),
|
|
2324
|
+
workable.fetch({ slugs: workableSlugs, limit }),
|
|
1903
2325
|
himalayas.fetch({ limit }),
|
|
1904
2326
|
wwr.fetch({ limit }),
|
|
1905
2327
|
hn.fetch({ limit })
|
|
1906
2328
|
]);
|
|
1907
2329
|
const seen = /* @__PURE__ */ new Set();
|
|
1908
2330
|
const jobs = [];
|
|
1909
|
-
const sourceNames = ["greenhouse", "ashby", "lever", "himalayas", "wwr", "hn"];
|
|
2331
|
+
const sourceNames = ["greenhouse", "ashby", "lever", "workable", "himalayas", "wwr", "hn"];
|
|
1910
2332
|
for (let i = 0; i < settled.length; i++) {
|
|
1911
2333
|
const result = settled[i];
|
|
1912
2334
|
if (result.status === "rejected") {
|
|
@@ -1922,7 +2344,7 @@ async function aggregate(opts) {
|
|
|
1922
2344
|
}
|
|
1923
2345
|
if (opts?.includeBounties !== false) {
|
|
1924
2346
|
try {
|
|
1925
|
-
const bounties = await
|
|
2347
|
+
const bounties = await aggregateBounties({ repos: opts?.slugs?.["bounty"] });
|
|
1926
2348
|
for (const b of bounties) {
|
|
1927
2349
|
if (!seen.has(b.id)) {
|
|
1928
2350
|
seen.add(b.id);
|
|
@@ -1935,7 +2357,7 @@ async function aggregate(opts) {
|
|
|
1935
2357
|
}
|
|
1936
2358
|
return jobs;
|
|
1937
2359
|
}
|
|
1938
|
-
var FEEDS, GREENHOUSE_SLUGS_BY_TIER, ASHBY_SLUGS_BY_TIER, LEVER_SLUGS_BY_TIER, DEFAULT_GREENHOUSE_SLUGS, DEFAULT_ASHBY_SLUGS, DEFAULT_LEVER_SLUGS;
|
|
2360
|
+
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;
|
|
1939
2361
|
var init_feeds = __esm({
|
|
1940
2362
|
"../../packages/core/src/feeds/index.ts"() {
|
|
1941
2363
|
"use strict";
|
|
@@ -1946,8 +2368,10 @@ var init_feeds = __esm({
|
|
|
1946
2368
|
init_wwr();
|
|
1947
2369
|
init_hn();
|
|
1948
2370
|
init_github_bounties();
|
|
2371
|
+
init_opire();
|
|
2372
|
+
init_workable();
|
|
1949
2373
|
init_bounty_gate();
|
|
1950
|
-
FEEDS = [greenhouse, ashby, lever, himalayas, wwr, hn];
|
|
2374
|
+
FEEDS = [greenhouse, ashby, lever, workable, himalayas, wwr, hn];
|
|
1951
2375
|
GREENHOUSE_SLUGS_BY_TIER = {
|
|
1952
2376
|
bigco: [
|
|
1953
2377
|
"stripe",
|
|
@@ -2053,6 +2477,7 @@ var init_feeds = __esm({
|
|
|
2053
2477
|
DEFAULT_GREENHOUSE_SLUGS = flattenTiers(GREENHOUSE_SLUGS_BY_TIER);
|
|
2054
2478
|
DEFAULT_ASHBY_SLUGS = flattenTiers(ASHBY_SLUGS_BY_TIER);
|
|
2055
2479
|
DEFAULT_LEVER_SLUGS = flattenTiers(LEVER_SLUGS_BY_TIER);
|
|
2480
|
+
DEFAULT_WORKABLE_SLUGS = ["zego", "workmotion"];
|
|
2056
2481
|
}
|
|
2057
2482
|
});
|
|
2058
2483
|
|
|
@@ -2153,6 +2578,7 @@ __export(src_exports, {
|
|
|
2153
2578
|
DEFAULT_BOUNTY_REPOS: () => DEFAULT_BOUNTY_REPOS,
|
|
2154
2579
|
DEFAULT_GREENHOUSE_SLUGS: () => DEFAULT_GREENHOUSE_SLUGS,
|
|
2155
2580
|
DEFAULT_LEVER_SLUGS: () => DEFAULT_LEVER_SLUGS,
|
|
2581
|
+
DEFAULT_WORKABLE_SLUGS: () => DEFAULT_WORKABLE_SLUGS,
|
|
2156
2582
|
EXAMPLE_BUYER: () => EXAMPLE_BUYER,
|
|
2157
2583
|
FEEDS: () => FEEDS,
|
|
2158
2584
|
GRAPH: () => GRAPH,
|
|
@@ -2171,10 +2597,13 @@ __export(src_exports, {
|
|
|
2171
2597
|
buildIndex: () => buildIndex,
|
|
2172
2598
|
buildReason: () => buildReason,
|
|
2173
2599
|
computeAcceptanceCredential: () => computeAcceptanceCredential,
|
|
2600
|
+
computeAcceptanceCredentialPublic: () => computeAcceptanceCredentialPublic,
|
|
2174
2601
|
coreTagsFromTitle: () => coreTagsFromTitle,
|
|
2602
|
+
deriveResumeTrend: () => deriveResumeTrend,
|
|
2175
2603
|
expandWeighted: () => expandWeighted,
|
|
2176
2604
|
extractSkillTags: () => extractSkillTags,
|
|
2177
2605
|
fetchGitHubProfile: () => fetchGitHubProfile,
|
|
2606
|
+
fetchRepoRecency: () => fetchRepoRecency,
|
|
2178
2607
|
flattenTiers: () => flattenTiers,
|
|
2179
2608
|
getBuyer: () => getBuyer,
|
|
2180
2609
|
githubBounties: () => githubBounties,
|
|
@@ -2188,9 +2617,11 @@ __export(src_exports, {
|
|
|
2188
2617
|
looksLikeEngRole: () => looksLikeEngRole,
|
|
2189
2618
|
match: () => match,
|
|
2190
2619
|
normalize: () => normalize,
|
|
2620
|
+
opire: () => opire,
|
|
2191
2621
|
passesMaturityGate: () => passesMaturityGate,
|
|
2192
2622
|
tokenize: () => tokenize,
|
|
2193
2623
|
validateGraph: () => validateGraph,
|
|
2624
|
+
workable: () => workable,
|
|
2194
2625
|
wwr: () => wwr
|
|
2195
2626
|
});
|
|
2196
2627
|
var init_src = __esm({
|
|
@@ -3548,7 +3979,7 @@ function buildContextVerbs(topMatches, sessionTags) {
|
|
|
3548
3979
|
}
|
|
3549
3980
|
const list = Array.isArray(topMatches) ? topMatches : [];
|
|
3550
3981
|
const hasBounty = list.some((m) => m && m.source === "bounty");
|
|
3551
|
-
if (hasBounty) headers.
|
|
3982
|
+
if (hasBounty) headers.push(`\u2726 Roles + \u{1F48E} paid bounties in your stack \u2014 link below`);
|
|
3552
3983
|
return headers;
|
|
3553
3984
|
}
|
|
3554
3985
|
function buildSpinnerPool(topMatches, max = 6, opts = {}) {
|
|
@@ -3640,8 +4071,8 @@ function buildTips(topMatches, baseUrl, max = 8) {
|
|
|
3640
4071
|
let bi = 0;
|
|
3641
4072
|
let ri = 0;
|
|
3642
4073
|
while (bi < bountyQ.length || ri < roleQ.length) {
|
|
3643
|
-
if (bi < bountyQ.length) ordered.push(bountyQ[bi++]);
|
|
3644
4074
|
if (ri < roleQ.length) ordered.push(roleQ[ri++]);
|
|
4075
|
+
if (bi < bountyQ.length) ordered.push(bountyQ[bi++]);
|
|
3645
4076
|
}
|
|
3646
4077
|
for (const m of ordered) {
|
|
3647
4078
|
if (!m || !m.title || !m.company || !m.id) continue;
|