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.
@@ -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
- async function computeAcceptanceCredential(login, token, cache = /* @__PURE__ */ new Map()) {
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
- return empty(/HTTP 403|HTTP 429|rate limit/i.test(msg) ? "rate-limited" : "failed");
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 < MIN_STARS) continue;
980
- if (meta.contributors !== void 0 && meta.contributors < MIN_CONTRIBUTORS) continue;
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
- var MIN_STARS, MIN_CONTRIBUTORS, CANDIDATE_PR_PAGE, TRIVIAL_PR_TITLE;
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 fetch(url, { headers: { Accept: "application/json" } });
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 fetch(url, {
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 fetch(url, { headers: { Accept: "application/json" } });
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 fetch(url, {
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 fetch(WWR_RSS_URL, {
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: item.pubDate ? new Date(item.pubDate).toISOString() : void 0,
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 fetch(ALGOLIA_SEARCH, {
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 fetch(`${ALGOLIA_ITEMS}${story.objectID}`, {
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 fetch(`${GITHUB_API}${path}`, { headers: authHeaders() });
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
- var GITHUB_API, BOUNTY_LABEL_RE, githubBounties;
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 repos = opts?.slugs && opts.slugs.length > 0 ? opts.slugs : DEFAULT_BOUNTY_REPOS;
1869
- console.info(`[github-bounties] scanning ${repos.length} repos`);
1870
- const settled = await Promise.allSettled(repos.map(fetchRepoBounties));
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 settled) {
1874
- if (r.status === "fulfilled") jobs.push(...r.value);
1875
- else {
2280
+ for (const r of results) {
2281
+ if (r.status === "fulfilled") {
2282
+ jobs.push(...r.value);
2283
+ } else {
1876
2284
  failures++;
1877
- console.warn("[github-bounties] repo fetch rejected:", r.reason);
2285
+ console.warn("[workable] account fetch rejected:", r.reason);
1878
2286
  }
1879
2287
  }
1880
- console.info(`[github-bounties] total: ${jobs.length} bounties, ${failures} repo failures`);
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
- return githubBounties.fetch({ slugs: opts?.repos });
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 githubBounties.fetch({ slugs: opts?.slugs?.["bounty"], limit });
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.unshift(`\u2726 Roles + \u{1F48E} paid bounties in your stack \u2014 link below`);
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;