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-bounties.js
CHANGED
|
@@ -148,7 +148,7 @@ var init_graph_data = __esm({
|
|
|
148
148
|
{ id: "airflow", parents: ["data-engineering"], synonyms: ["apache-airflow"] },
|
|
149
149
|
{ id: "dbt", parents: ["data-engineering"] },
|
|
150
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"], related: [{ to: "langchain", w: 0.5 }, { to: "rag", w: 0.55 }, { to: "openai", w: 0.45 }, { to: "anthropic", w: 0.45 }] },
|
|
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
154
|
{ id: "pandas", parents: ["python"], related: [{ to: "numpy", w: 0.6 }, { to: "data-engineering", w: 0.45 }, { to: "spark", w: 0.4 }] },
|
|
@@ -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 }] },
|
|
@@ -583,7 +591,10 @@ var init_vocabulary = __esm({
|
|
|
583
591
|
function ghHeaders(token) {
|
|
584
592
|
const headers = {
|
|
585
593
|
Accept: "application/vnd.github+json",
|
|
586
|
-
"X-GitHub-Api-Version": "2022-11-28"
|
|
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"
|
|
587
598
|
};
|
|
588
599
|
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
589
600
|
return headers;
|
|
@@ -727,16 +738,25 @@ async function fetchRepoMeta(owner, name, token, cache) {
|
|
|
727
738
|
cache.set(key, meta);
|
|
728
739
|
return meta;
|
|
729
740
|
}
|
|
730
|
-
|
|
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
|
+
}) {
|
|
731
759
|
const computedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
732
|
-
const empty = (status) => ({
|
|
733
|
-
status,
|
|
734
|
-
byDomain: {},
|
|
735
|
-
qualifyingTotal: 0,
|
|
736
|
-
computedAt
|
|
737
|
-
});
|
|
738
|
-
if (!token) return empty("no-token");
|
|
739
|
-
const ownedOrgs = await fetchOwnedOrgs(token);
|
|
740
760
|
const loginLc = login.toLowerCase();
|
|
741
761
|
let items;
|
|
742
762
|
try {
|
|
@@ -747,8 +767,9 @@ async function computeAcceptanceCredential(login, token, cache = /* @__PURE__ */
|
|
|
747
767
|
);
|
|
748
768
|
items = res.items ?? [];
|
|
749
769
|
} catch (err) {
|
|
750
|
-
const msg = String(err);
|
|
751
|
-
|
|
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");
|
|
752
773
|
}
|
|
753
774
|
const byDomain = {};
|
|
754
775
|
let qualifyingTotal = 0;
|
|
@@ -762,8 +783,8 @@ async function computeAcceptanceCredential(login, token, cache = /* @__PURE__ */
|
|
|
762
783
|
const meta = await fetchRepoMeta(repo.owner, repo.name, token, cache);
|
|
763
784
|
if (!meta) continue;
|
|
764
785
|
if (meta.archived || meta.fork) continue;
|
|
765
|
-
if (meta.stars <
|
|
766
|
-
if (meta.contributors !== void 0 && meta.contributors <
|
|
786
|
+
if (meta.stars < gates.minStars) continue;
|
|
787
|
+
if (meta.contributors !== void 0 && meta.contributors < gates.minContributors) continue;
|
|
767
788
|
qualifyingTotal += 1;
|
|
768
789
|
const mergedAt = item.pull_request?.merged_at ?? item.closed_at ?? item.created_at;
|
|
769
790
|
const rawDomains = [meta.language ?? "", ...meta.topics].filter(Boolean);
|
|
@@ -784,6 +805,18 @@ async function computeAcceptanceCredential(login, token, cache = /* @__PURE__ */
|
|
|
784
805
|
}
|
|
785
806
|
return { status: "ok", byDomain: finalDomains, qualifyingTotal, computedAt };
|
|
786
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
|
+
}
|
|
787
820
|
function acceptanceCountForDomains(cred, domains) {
|
|
788
821
|
if (cred.status !== "ok") return 0;
|
|
789
822
|
let max = 0;
|
|
@@ -802,7 +835,60 @@ function bestAcceptanceDomain(cred, domains) {
|
|
|
802
835
|
}
|
|
803
836
|
return best;
|
|
804
837
|
}
|
|
805
|
-
|
|
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);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
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
|
+
});
|
|
888
|
+
}
|
|
889
|
+
return scored.sort((a, b) => b.weight - a.weight).slice(0, 12).map((s) => s.t);
|
|
890
|
+
}
|
|
891
|
+
var MIN_STARS, MIN_CONTRIBUTORS, CANDIDATE_PR_PAGE, TRIVIAL_PR_TITLE, RESUME_DECAY_HALF_LIFE_MS, RESUME_MIN_SCORE;
|
|
806
892
|
var init_github = __esm({
|
|
807
893
|
"../../packages/core/src/github.ts"() {
|
|
808
894
|
"use strict";
|
|
@@ -811,6 +897,8 @@ var init_github = __esm({
|
|
|
811
897
|
MIN_CONTRIBUTORS = 10;
|
|
812
898
|
CANDIDATE_PR_PAGE = 50;
|
|
813
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;
|
|
814
902
|
}
|
|
815
903
|
});
|
|
816
904
|
|
|
@@ -958,6 +1046,18 @@ var init_matcher = __esm({
|
|
|
958
1046
|
}
|
|
959
1047
|
});
|
|
960
1048
|
|
|
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) });
|
|
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
|
+
|
|
961
1061
|
// ../../packages/core/src/feeds/greenhouse.ts
|
|
962
1062
|
function extractTags(job) {
|
|
963
1063
|
const body = [
|
|
@@ -976,7 +1076,7 @@ async function fetchSlug(slug) {
|
|
|
976
1076
|
const url = `https://boards-api.greenhouse.io/v1/boards/${slug}/jobs?content=true`;
|
|
977
1077
|
let res;
|
|
978
1078
|
try {
|
|
979
|
-
res = await
|
|
1079
|
+
res = await fetchWithTimeout(url, { headers: { Accept: "application/json" } });
|
|
980
1080
|
} catch (err) {
|
|
981
1081
|
console.warn(`[greenhouse] ${slug}: network error \u2014`, err);
|
|
982
1082
|
return [];
|
|
@@ -1018,6 +1118,7 @@ var init_greenhouse = __esm({
|
|
|
1018
1118
|
"../../packages/core/src/feeds/greenhouse.ts"() {
|
|
1019
1119
|
"use strict";
|
|
1020
1120
|
init_vocabulary();
|
|
1121
|
+
init_http();
|
|
1021
1122
|
FALLBACK_SLUGS = [
|
|
1022
1123
|
"stripe",
|
|
1023
1124
|
"linear",
|
|
@@ -1088,7 +1189,7 @@ function inferRemote2(job) {
|
|
|
1088
1189
|
}
|
|
1089
1190
|
async function fetchSlug2(slug) {
|
|
1090
1191
|
const url = `https://api.ashbyhq.com/posting-api/job-board/${slug}`;
|
|
1091
|
-
const res = await
|
|
1192
|
+
const res = await fetchWithTimeout(url, {
|
|
1092
1193
|
headers: { Accept: "application/json" }
|
|
1093
1194
|
});
|
|
1094
1195
|
if (!res.ok) {
|
|
@@ -1120,6 +1221,7 @@ var init_ashby = __esm({
|
|
|
1120
1221
|
"../../packages/core/src/feeds/ashby.ts"() {
|
|
1121
1222
|
"use strict";
|
|
1122
1223
|
init_vocabulary();
|
|
1224
|
+
init_http();
|
|
1123
1225
|
ashby = {
|
|
1124
1226
|
source: "ashby",
|
|
1125
1227
|
async fetch(opts) {
|
|
@@ -1170,7 +1272,7 @@ function toIso(ms) {
|
|
|
1170
1272
|
}
|
|
1171
1273
|
async function fetchSlug3(slug) {
|
|
1172
1274
|
const url = `https://api.lever.co/v0/postings/${slug}?mode=json`;
|
|
1173
|
-
const res = await
|
|
1275
|
+
const res = await fetchWithTimeout(url, { headers: { Accept: "application/json" } });
|
|
1174
1276
|
if (!res.ok) {
|
|
1175
1277
|
throw new Error(`Lever ${slug}: HTTP ${res.status}`);
|
|
1176
1278
|
}
|
|
@@ -1201,6 +1303,7 @@ var init_lever = __esm({
|
|
|
1201
1303
|
"../../packages/core/src/feeds/lever.ts"() {
|
|
1202
1304
|
"use strict";
|
|
1203
1305
|
init_vocabulary();
|
|
1306
|
+
init_http();
|
|
1204
1307
|
lever = {
|
|
1205
1308
|
source: "lever",
|
|
1206
1309
|
async fetch(opts) {
|
|
@@ -1242,12 +1345,13 @@ var init_himalayas = __esm({
|
|
|
1242
1345
|
"../../packages/core/src/feeds/himalayas.ts"() {
|
|
1243
1346
|
"use strict";
|
|
1244
1347
|
init_vocabulary();
|
|
1348
|
+
init_http();
|
|
1245
1349
|
himalayas = {
|
|
1246
1350
|
source: "himalayas",
|
|
1247
1351
|
async fetch(opts) {
|
|
1248
1352
|
const limit = opts?.limit ?? 100;
|
|
1249
1353
|
const url = `https://himalayas.app/jobs/api?limit=${limit}`;
|
|
1250
|
-
const res = await
|
|
1354
|
+
const res = await fetchWithTimeout(url, {
|
|
1251
1355
|
headers: { Accept: "application/json" }
|
|
1252
1356
|
});
|
|
1253
1357
|
if (!res.ok) {
|
|
@@ -1356,12 +1460,13 @@ var init_wwr = __esm({
|
|
|
1356
1460
|
"use strict";
|
|
1357
1461
|
init_vocabulary();
|
|
1358
1462
|
init_entities();
|
|
1463
|
+
init_http();
|
|
1359
1464
|
WWR_RSS_URL = "https://weworkremotely.com/remote-jobs.rss";
|
|
1360
1465
|
wwr = {
|
|
1361
1466
|
source: "wwr",
|
|
1362
1467
|
async fetch(opts) {
|
|
1363
1468
|
const limit = opts?.limit ?? 200;
|
|
1364
|
-
const res = await
|
|
1469
|
+
const res = await fetchWithTimeout(WWR_RSS_URL, {
|
|
1365
1470
|
headers: { Accept: "application/rss+xml, application/xml, text/xml" }
|
|
1366
1471
|
});
|
|
1367
1472
|
if (!res.ok) {
|
|
@@ -1369,6 +1474,11 @@ var init_wwr = __esm({
|
|
|
1369
1474
|
}
|
|
1370
1475
|
const xml = await res.text();
|
|
1371
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
|
+
}
|
|
1372
1482
|
return items.map((item) => ({
|
|
1373
1483
|
id: extractId(item.link),
|
|
1374
1484
|
source: "wwr",
|
|
@@ -1380,7 +1490,7 @@ var init_wwr = __esm({
|
|
|
1380
1490
|
location: "Remote",
|
|
1381
1491
|
tags: extractTags5(item),
|
|
1382
1492
|
roleType: inferRoleType(item.category),
|
|
1383
|
-
postedAt:
|
|
1493
|
+
postedAt: safeIso(item.pubDate),
|
|
1384
1494
|
applyMode: "direct",
|
|
1385
1495
|
raw: item
|
|
1386
1496
|
}));
|
|
@@ -1446,13 +1556,14 @@ var init_hn = __esm({
|
|
|
1446
1556
|
"use strict";
|
|
1447
1557
|
init_vocabulary();
|
|
1448
1558
|
init_entities();
|
|
1559
|
+
init_http();
|
|
1449
1560
|
ALGOLIA_SEARCH = "https://hn.algolia.com/api/v1/search?query=Ask+HN%3A+Who+is+Hiring%3F&tags=story,ask_hn&hitsPerPage=1";
|
|
1450
1561
|
ALGOLIA_ITEMS = "https://hn.algolia.com/api/v1/items/";
|
|
1451
1562
|
hn = {
|
|
1452
1563
|
source: "hn",
|
|
1453
1564
|
async fetch(opts) {
|
|
1454
1565
|
const limit = opts?.limit ?? 150;
|
|
1455
|
-
const searchRes = await
|
|
1566
|
+
const searchRes = await fetchWithTimeout(ALGOLIA_SEARCH, {
|
|
1456
1567
|
headers: { Accept: "application/json" }
|
|
1457
1568
|
});
|
|
1458
1569
|
if (!searchRes.ok) {
|
|
@@ -1463,7 +1574,7 @@ var init_hn = __esm({
|
|
|
1463
1574
|
if (!story) {
|
|
1464
1575
|
throw new Error('HN: No "Who is Hiring" story found');
|
|
1465
1576
|
}
|
|
1466
|
-
const itemRes = await
|
|
1577
|
+
const itemRes = await fetchWithTimeout(`${ALGOLIA_ITEMS}${story.objectID}`, {
|
|
1467
1578
|
headers: { Accept: "application/json" }
|
|
1468
1579
|
});
|
|
1469
1580
|
if (!itemRes.ok) {
|
|
@@ -1557,7 +1668,7 @@ function isBountyIssue(issue) {
|
|
|
1557
1668
|
async function ghJson(path) {
|
|
1558
1669
|
let res;
|
|
1559
1670
|
try {
|
|
1560
|
-
res = await
|
|
1671
|
+
res = await fetchWithTimeout(`${GITHUB_API}${path}`, { headers: authHeaders() });
|
|
1561
1672
|
} catch (err) {
|
|
1562
1673
|
console.warn(`[github-bounties] network error ${path} \u2014`, err);
|
|
1563
1674
|
return null;
|
|
@@ -1639,31 +1750,328 @@ async function fetchRepoBounties(repoFullName) {
|
|
|
1639
1750
|
};
|
|
1640
1751
|
}));
|
|
1641
1752
|
}
|
|
1642
|
-
|
|
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;
|
|
1643
1846
|
var init_github_bounties = __esm({
|
|
1644
1847
|
"../../packages/core/src/feeds/github-bounties.ts"() {
|
|
1645
1848
|
"use strict";
|
|
1646
1849
|
init_vocabulary();
|
|
1647
1850
|
init_entities();
|
|
1648
1851
|
init_bounty_gate();
|
|
1852
|
+
init_http();
|
|
1649
1853
|
GITHUB_API = "https://api.github.com";
|
|
1650
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();
|
|
1651
1868
|
githubBounties = {
|
|
1652
1869
|
source: "bounty",
|
|
1653
1870
|
async fetch(opts) {
|
|
1654
|
-
const
|
|
1655
|
-
|
|
1656
|
-
|
|
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));
|
|
1657
2064
|
const jobs = [];
|
|
1658
2065
|
let failures = 0;
|
|
1659
|
-
for (const r of
|
|
1660
|
-
if (r.status === "fulfilled")
|
|
1661
|
-
|
|
2066
|
+
for (const r of results) {
|
|
2067
|
+
if (r.status === "fulfilled") {
|
|
2068
|
+
jobs.push(...r.value);
|
|
2069
|
+
} else {
|
|
1662
2070
|
failures++;
|
|
1663
|
-
console.warn("[
|
|
2071
|
+
console.warn("[workable] account fetch rejected:", r.reason);
|
|
1664
2072
|
}
|
|
1665
2073
|
}
|
|
1666
|
-
console.info(`[
|
|
2074
|
+
console.info(`[workable] total: ${jobs.length} jobs, ${failures} account failures`);
|
|
1667
2075
|
return jobs;
|
|
1668
2076
|
}
|
|
1669
2077
|
};
|
|
@@ -1672,7 +2080,19 @@ var init_github_bounties = __esm({
|
|
|
1672
2080
|
|
|
1673
2081
|
// ../../packages/core/src/feeds/index.ts
|
|
1674
2082
|
async function aggregateBounties(opts) {
|
|
1675
|
-
|
|
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;
|
|
1676
2096
|
}
|
|
1677
2097
|
function flattenTiers(t) {
|
|
1678
2098
|
return [.../* @__PURE__ */ new Set([...t.bigco, ...t.scaleup, ...t.startup])];
|
|
@@ -1681,18 +2101,20 @@ async function aggregate(opts) {
|
|
|
1681
2101
|
const ghSlugs = opts?.slugs?.["greenhouse"] ?? DEFAULT_GREENHOUSE_SLUGS;
|
|
1682
2102
|
const ashbySlugs = opts?.slugs?.["ashby"] ?? DEFAULT_ASHBY_SLUGS;
|
|
1683
2103
|
const leverSlugs = opts?.slugs?.["lever"] ?? DEFAULT_LEVER_SLUGS;
|
|
2104
|
+
const workableSlugs = opts?.slugs?.["workable"] ?? DEFAULT_WORKABLE_SLUGS;
|
|
1684
2105
|
const limit = opts?.limit ?? 150;
|
|
1685
2106
|
const settled = await Promise.allSettled([
|
|
1686
2107
|
greenhouse.fetch({ slugs: ghSlugs, limit }),
|
|
1687
2108
|
ashby.fetch({ slugs: ashbySlugs, limit }),
|
|
1688
2109
|
lever.fetch({ slugs: leverSlugs, limit }),
|
|
2110
|
+
workable.fetch({ slugs: workableSlugs, limit }),
|
|
1689
2111
|
himalayas.fetch({ limit }),
|
|
1690
2112
|
wwr.fetch({ limit }),
|
|
1691
2113
|
hn.fetch({ limit })
|
|
1692
2114
|
]);
|
|
1693
2115
|
const seen = /* @__PURE__ */ new Set();
|
|
1694
2116
|
const jobs = [];
|
|
1695
|
-
const sourceNames = ["greenhouse", "ashby", "lever", "himalayas", "wwr", "hn"];
|
|
2117
|
+
const sourceNames = ["greenhouse", "ashby", "lever", "workable", "himalayas", "wwr", "hn"];
|
|
1696
2118
|
for (let i = 0; i < settled.length; i++) {
|
|
1697
2119
|
const result = settled[i];
|
|
1698
2120
|
if (result.status === "rejected") {
|
|
@@ -1708,7 +2130,7 @@ async function aggregate(opts) {
|
|
|
1708
2130
|
}
|
|
1709
2131
|
if (opts?.includeBounties !== false) {
|
|
1710
2132
|
try {
|
|
1711
|
-
const bounties = await
|
|
2133
|
+
const bounties = await aggregateBounties({ repos: opts?.slugs?.["bounty"] });
|
|
1712
2134
|
for (const b of bounties) {
|
|
1713
2135
|
if (!seen.has(b.id)) {
|
|
1714
2136
|
seen.add(b.id);
|
|
@@ -1721,7 +2143,7 @@ async function aggregate(opts) {
|
|
|
1721
2143
|
}
|
|
1722
2144
|
return jobs;
|
|
1723
2145
|
}
|
|
1724
|
-
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;
|
|
1725
2147
|
var init_feeds = __esm({
|
|
1726
2148
|
"../../packages/core/src/feeds/index.ts"() {
|
|
1727
2149
|
"use strict";
|
|
@@ -1732,8 +2154,10 @@ var init_feeds = __esm({
|
|
|
1732
2154
|
init_wwr();
|
|
1733
2155
|
init_hn();
|
|
1734
2156
|
init_github_bounties();
|
|
2157
|
+
init_opire();
|
|
2158
|
+
init_workable();
|
|
1735
2159
|
init_bounty_gate();
|
|
1736
|
-
FEEDS = [greenhouse, ashby, lever, himalayas, wwr, hn];
|
|
2160
|
+
FEEDS = [greenhouse, ashby, lever, workable, himalayas, wwr, hn];
|
|
1737
2161
|
GREENHOUSE_SLUGS_BY_TIER = {
|
|
1738
2162
|
bigco: [
|
|
1739
2163
|
"stripe",
|
|
@@ -1839,6 +2263,7 @@ var init_feeds = __esm({
|
|
|
1839
2263
|
DEFAULT_GREENHOUSE_SLUGS = flattenTiers(GREENHOUSE_SLUGS_BY_TIER);
|
|
1840
2264
|
DEFAULT_ASHBY_SLUGS = flattenTiers(ASHBY_SLUGS_BY_TIER);
|
|
1841
2265
|
DEFAULT_LEVER_SLUGS = flattenTiers(LEVER_SLUGS_BY_TIER);
|
|
2266
|
+
DEFAULT_WORKABLE_SLUGS = ["zego", "workmotion"];
|
|
1842
2267
|
}
|
|
1843
2268
|
});
|
|
1844
2269
|
|
|
@@ -1939,6 +2364,7 @@ __export(src_exports, {
|
|
|
1939
2364
|
DEFAULT_BOUNTY_REPOS: () => DEFAULT_BOUNTY_REPOS,
|
|
1940
2365
|
DEFAULT_GREENHOUSE_SLUGS: () => DEFAULT_GREENHOUSE_SLUGS,
|
|
1941
2366
|
DEFAULT_LEVER_SLUGS: () => DEFAULT_LEVER_SLUGS,
|
|
2367
|
+
DEFAULT_WORKABLE_SLUGS: () => DEFAULT_WORKABLE_SLUGS,
|
|
1942
2368
|
EXAMPLE_BUYER: () => EXAMPLE_BUYER,
|
|
1943
2369
|
FEEDS: () => FEEDS,
|
|
1944
2370
|
GRAPH: () => GRAPH,
|
|
@@ -1957,10 +2383,13 @@ __export(src_exports, {
|
|
|
1957
2383
|
buildIndex: () => buildIndex,
|
|
1958
2384
|
buildReason: () => buildReason,
|
|
1959
2385
|
computeAcceptanceCredential: () => computeAcceptanceCredential,
|
|
2386
|
+
computeAcceptanceCredentialPublic: () => computeAcceptanceCredentialPublic,
|
|
1960
2387
|
coreTagsFromTitle: () => coreTagsFromTitle,
|
|
2388
|
+
deriveResumeTrend: () => deriveResumeTrend,
|
|
1961
2389
|
expandWeighted: () => expandWeighted,
|
|
1962
2390
|
extractSkillTags: () => extractSkillTags,
|
|
1963
2391
|
fetchGitHubProfile: () => fetchGitHubProfile,
|
|
2392
|
+
fetchRepoRecency: () => fetchRepoRecency,
|
|
1964
2393
|
flattenTiers: () => flattenTiers,
|
|
1965
2394
|
getBuyer: () => getBuyer,
|
|
1966
2395
|
githubBounties: () => githubBounties,
|
|
@@ -1974,9 +2403,11 @@ __export(src_exports, {
|
|
|
1974
2403
|
looksLikeEngRole: () => looksLikeEngRole,
|
|
1975
2404
|
match: () => match,
|
|
1976
2405
|
normalize: () => normalize,
|
|
2406
|
+
opire: () => opire,
|
|
1977
2407
|
passesMaturityGate: () => passesMaturityGate,
|
|
1978
2408
|
tokenize: () => tokenize,
|
|
1979
2409
|
validateGraph: () => validateGraph,
|
|
2410
|
+
workable: () => workable,
|
|
1980
2411
|
wwr: () => wwr
|
|
1981
2412
|
});
|
|
1982
2413
|
var init_src = __esm({
|