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.
@@ -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
- async function computeAcceptanceCredential(login, token, cache = /* @__PURE__ */ new Map()) {
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
- return empty(/HTTP 403|HTTP 429|rate limit/i.test(msg) ? "rate-limited" : "failed");
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 < MIN_STARS) continue;
766
- if (meta.contributors !== void 0 && meta.contributors < MIN_CONTRIBUTORS) continue;
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
- var MIN_STARS, MIN_CONTRIBUTORS, CANDIDATE_PR_PAGE, TRIVIAL_PR_TITLE;
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 fetch(url, { headers: { Accept: "application/json" } });
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 fetch(url, {
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 fetch(url, { headers: { Accept: "application/json" } });
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 fetch(url, {
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 fetch(WWR_RSS_URL, {
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: item.pubDate ? new Date(item.pubDate).toISOString() : void 0,
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 fetch(ALGOLIA_SEARCH, {
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 fetch(`${ALGOLIA_ITEMS}${story.objectID}`, {
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 fetch(`${GITHUB_API}${path}`, { headers: authHeaders() });
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
- var GITHUB_API, BOUNTY_LABEL_RE, githubBounties;
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 repos = opts?.slugs && opts.slugs.length > 0 ? opts.slugs : DEFAULT_BOUNTY_REPOS;
1655
- console.info(`[github-bounties] scanning ${repos.length} repos`);
1656
- const settled = await Promise.allSettled(repos.map(fetchRepoBounties));
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 settled) {
1660
- if (r.status === "fulfilled") jobs.push(...r.value);
1661
- else {
2066
+ for (const r of results) {
2067
+ if (r.status === "fulfilled") {
2068
+ jobs.push(...r.value);
2069
+ } else {
1662
2070
  failures++;
1663
- console.warn("[github-bounties] repo fetch rejected:", r.reason);
2071
+ console.warn("[workable] account fetch rejected:", r.reason);
1664
2072
  }
1665
2073
  }
1666
- console.info(`[github-bounties] total: ${jobs.length} bounties, ${failures} repo failures`);
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
- return githubBounties.fetch({ slugs: opts?.repos });
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 githubBounties.fetch({ slugs: opts?.slugs?.["bounty"], limit });
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({