terminalhire 0.17.2 → 0.18.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.
@@ -921,6 +921,7 @@ async function fetchRepoMeta(owner, name, token, cache) {
921
921
  stars: r.stargazers_count ?? 0,
922
922
  archived: !!r.archived,
923
923
  fork: !!r.fork,
924
+ private: !!r.private,
924
925
  language: r.language ?? null,
925
926
  topics: r.topics ?? [],
926
927
  contributors
@@ -953,7 +954,7 @@ async function computeAcceptanceFromSearch(login, token, ownedOrgs, cache, gates
953
954
  const loginLc = login.toLowerCase();
954
955
  let items;
955
956
  try {
956
- const q = encodeURIComponent(`type:pr is:merged author:${login} -user:${login} sort:updated`);
957
+ const q = encodeURIComponent(`type:pr is:merged is:public author:${login} -user:${login} sort:updated`);
957
958
  const res = await ghFetch(
958
959
  `/search/issues?q=${q}&per_page=${CANDIDATE_PR_PAGE}`,
959
960
  token
@@ -966,6 +967,7 @@ async function computeAcceptanceFromSearch(login, token, ownedOrgs, cache, gates
966
967
  }
967
968
  const byDomain = {};
968
969
  let qualifyingTotal = 0;
970
+ const qualifyingPRs = [];
969
971
  for (const item of items) {
970
972
  const repo = parseRepoUrl(item.repository_url);
971
973
  if (!repo) continue;
@@ -975,13 +977,22 @@ async function computeAcceptanceFromSearch(login, token, ownedOrgs, cache, gates
975
977
  if (isTrivialPRTitle(item.title)) continue;
976
978
  const meta = await fetchRepoMeta(repo.owner, repo.name, token, cache);
977
979
  if (!meta) continue;
980
+ if (meta.private) continue;
978
981
  if (meta.archived || meta.fork) continue;
979
982
  if (meta.stars < gates.minStars) continue;
980
983
  if (meta.contributors !== void 0 && meta.contributors < gates.minContributors) continue;
981
984
  qualifyingTotal += 1;
982
985
  const mergedAt = item.pull_request?.merged_at ?? item.closed_at ?? item.created_at;
983
986
  const rawDomains = [meta.language ?? "", ...meta.topics].filter(Boolean);
984
- for (const d of new Set(normalize(rawDomains))) {
987
+ const domainTags = [...new Set(normalize(rawDomains))];
988
+ qualifyingPRs.push({
989
+ url: item.html_url,
990
+ title: item.title,
991
+ repo: `${repo.owner}/${repo.name}`,
992
+ domains: domainTags,
993
+ mergedAt
994
+ });
995
+ for (const d of domainTags) {
985
996
  const b = byDomain[d] ?? (byDomain[d] = { mergedPRs: 0, distinctOrgs: 0, lastMergedAt: mergedAt, orgs: /* @__PURE__ */ new Set() });
986
997
  b.mergedPRs += 1;
987
998
  b.orgs.add(ownerLc);
@@ -996,7 +1007,7 @@ async function computeAcceptanceFromSearch(login, token, ownedOrgs, cache, gates
996
1007
  lastMergedAt: b.lastMergedAt
997
1008
  };
998
1009
  }
999
- return { status: "ok", byDomain: finalDomains, qualifyingTotal, computedAt };
1010
+ return { status: "ok", byDomain: finalDomains, qualifyingTotal, qualifyingPRs, computedAt };
1000
1011
  }
1001
1012
  async function computeAcceptanceCredential(login, token, cache = /* @__PURE__ */ new Map()) {
1002
1013
  if (!token) return emptyCredential("no-token");
@@ -1807,6 +1818,19 @@ var init_concurrency = __esm({
1807
1818
  }
1808
1819
  });
1809
1820
 
1821
+ // ../../packages/core/src/feeds/effort.ts
1822
+ function effortFromAmount(amount) {
1823
+ if (amount == null) return void 0;
1824
+ if (amount <= 500) return "small";
1825
+ if (amount <= 2e3) return "medium";
1826
+ return "large";
1827
+ }
1828
+ var init_effort = __esm({
1829
+ "../../packages/core/src/feeds/effort.ts"() {
1830
+ "use strict";
1831
+ }
1832
+ });
1833
+
1810
1834
  // ../../packages/core/src/feeds/github-bounties.ts
1811
1835
  function authHeaders() {
1812
1836
  const token = process.env["GITHUB_TOKEN"] ?? process.env["GH_TOKEN"];
@@ -1829,12 +1853,6 @@ function parseAmountUSD(text) {
1829
1853
  if (!Number.isFinite(n) || n <= 0 || n > 1e6) return void 0;
1830
1854
  return Math.round(n);
1831
1855
  }
1832
- function effortFromAmount(amount) {
1833
- if (amount == null) return void 0;
1834
- if (amount <= 500) return "small";
1835
- if (amount <= 2e3) return "medium";
1836
- return "large";
1837
- }
1838
1856
  function labelNames(issue) {
1839
1857
  return (issue.labels ?? []).map((l) => typeof l === "string" ? l : l.name ?? "").filter(Boolean);
1840
1858
  }
@@ -2082,6 +2100,7 @@ var init_github_bounties = __esm({
2082
2100
  init_bounty_gate();
2083
2101
  init_http();
2084
2102
  init_concurrency();
2103
+ init_effort();
2085
2104
  GITHUB_API = "https://api.github.com";
2086
2105
  BOUNTY_LABEL_RE = /bounty|reward|funded|💎|💰/i;
2087
2106
  SEARCH_QUERIES = [
@@ -2133,12 +2152,6 @@ var init_github_bounties = __esm({
2133
2152
  function tokenize3(text) {
2134
2153
  return text.toLowerCase().replace(/[^a-z0-9.\-+#]/g, " ").split(/\s+/).filter((w) => w.length > 1);
2135
2154
  }
2136
- function effortFromAmount2(usd) {
2137
- if (usd == null) return void 0;
2138
- if (usd < 150) return "small";
2139
- if (usd < 750) return "medium";
2140
- return "large";
2141
- }
2142
2155
  function priceToUSD(p) {
2143
2156
  if (!p || typeof p.value !== "number") return void 0;
2144
2157
  if (p.unit === "USD_CENT") return Math.round(p.value) / 100;
@@ -2161,6 +2174,7 @@ var init_opire = __esm({
2161
2174
  init_bounty_gate();
2162
2175
  init_github_bounties();
2163
2176
  init_http();
2177
+ init_effort();
2164
2178
  OPIRE_REWARDS_URL = "https://api.opire.dev/rewards";
2165
2179
  MIN_USD = 25;
2166
2180
  MAX_USD = 25e3;
@@ -2197,7 +2211,7 @@ var init_opire = __esm({
2197
2211
  const tags = normalize([...r.programmingLanguages ?? [], ...tokenize3(title)]);
2198
2212
  const bounty = {
2199
2213
  amountUSD,
2200
- estimatedEffort: effortFromAmount2(amountUSD),
2214
+ estimatedEffort: effortFromAmount(amountUSD),
2201
2215
  bountySource: "opire",
2202
2216
  claimUrl: r.url,
2203
2217
  repoFullName
@@ -6544,6 +6558,7 @@ __export(src_exports, {
6544
6558
  isBounty: () => isBounty,
6545
6559
  isContribution: () => isContribution,
6546
6560
  isOverIntroLimit: () => isOverIntroLimit,
6561
+ isTrivialPRTitle: () => isTrivialPRTitle,
6547
6562
  joinLabels: () => joinLabels,
6548
6563
  labelFor: () => labelFor,
6549
6564
  lever: () => lever,
@@ -6600,9 +6615,9 @@ var init_keytar = __esm({
6600
6615
  }
6601
6616
  });
6602
6617
 
6603
- // node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/3ffac25b-ca95-4a86-9e17-a4cf326551de/scratchpad/rel2/node_modules/keytar/build/Release/keytar.node
6618
+ // node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node
6604
6619
  var require_keytar = __commonJS({
6605
- "node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/3ffac25b-ca95-4a86-9e17-a4cf326551de/scratchpad/rel2/node_modules/keytar/build/Release/keytar.node"(exports, module) {
6620
+ "node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node"(exports, module) {
6606
6621
  "use strict";
6607
6622
  init_keytar();
6608
6623
  try {
@@ -6928,6 +6943,37 @@ function updateIndexCache(patch) {
6928
6943
  return entry;
6929
6944
  }
6930
6945
 
6946
+ // bin/sanitize.js
6947
+ var CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
6948
+ function sanitizeText(s) {
6949
+ if (s == null) return "";
6950
+ return String(s).replace(CONTROL_CHARS, "");
6951
+ }
6952
+ function safeHttpUrl(url) {
6953
+ if (url == null) return null;
6954
+ const raw = String(url);
6955
+ CONTROL_CHARS.lastIndex = 0;
6956
+ if (CONTROL_CHARS.test(raw)) return null;
6957
+ let parsed;
6958
+ try {
6959
+ parsed = new URL(raw);
6960
+ } catch {
6961
+ return null;
6962
+ }
6963
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
6964
+ return parsed.href;
6965
+ }
6966
+ function linkTitle(title, url) {
6967
+ const safeTitle = sanitizeText(title);
6968
+ const href = safeHttpUrl(url);
6969
+ const isTTY = process.stdout.isTTY;
6970
+ const noColor = process.env["NO_COLOR"] !== void 0;
6971
+ if (isTTY && !noColor && href) {
6972
+ return `\x1B]8;;${href}\x1B\\${safeTitle}\x1B]8;;\x1B\\`;
6973
+ }
6974
+ return href ? `${safeTitle} (${href})` : safeTitle;
6975
+ }
6976
+
6931
6977
  // bin/jpi-bounties.js
6932
6978
  var TERMINALHIRE_DIR3 = process.env.TERMINALHIRE_DIR || join4(homedir3(), ".terminalhire");
6933
6979
  var INDEX_CACHE_FILE2 = join4(TERMINALHIRE_DIR3, "index-cache.json");
@@ -6974,12 +7020,6 @@ function formatAmount(b) {
6974
7020
  return b.amountUSD != null ? "$" + b.amountUSD.toLocaleString() : "$\u2014";
6975
7021
  }
6976
7022
  var EFFORT_LABEL = { small: "small (~\xBD day)", medium: "medium (~1 day)", large: "large (multi-day)" };
6977
- function linkTitle(title, url) {
6978
- const isTTY = process.stdout.isTTY;
6979
- const noColor = process.env["NO_COLOR"] !== void 0;
6980
- if (isTTY && !noColor && url) return `\x1B]8;;${url}\x1B\\${title}\x1B]8;;\x1B\\`;
6981
- return url ? `${title} (${url})` : title;
6982
- }
6983
7023
  function printBounty(i, job, score, reason, matchedTags) {
6984
7024
  const b = job.bounty ?? {};
6985
7025
  const stars = b.repoStars != null ? ` \xB7 ${b.repoStars}\u2605` : "";
@@ -6990,11 +7030,11 @@ function printBounty(i, job, score, reason, matchedTags) {
6990
7030
  const ref = opportunityShortToken(job.id);
6991
7031
  console.log(`
6992
7032
  ${i + 1}. ${linkTitle(job.title, job.url)} [${ref}]`);
6993
- console.log(` ${formatAmount(b)}${effort} \xB7 ${b.repoFullName ?? job.company}${stars}${scoreStr}${contend}`);
7033
+ console.log(` ${formatAmount(b)}${effort} \xB7 ${sanitizeText(b.repoFullName ?? job.company)}${stars}${scoreStr}${contend}`);
6994
7034
  if (reason) console.log(` ${reason}`);
6995
7035
  if (matchedTags && matchedTags.length) console.log(` Tags matched: ${matchedTags.slice(0, 5).join(", ")}`);
6996
7036
  console.log(` id: ${job.id}`);
6997
- console.log(` Claim: ${b.claimUrl ?? job.url}`);
7037
+ console.log(` Claim: ${sanitizeText(b.claimUrl ?? job.url)}`);
6998
7038
  console.log(` \u2192 terminalhire claim ${ref}`);
6999
7039
  }
7000
7040
  async function run() {
@@ -7050,7 +7090,7 @@ Enter a number to open a bounty's claim page, or press Enter to exit: `);
7050
7090
  console.log(
7051
7091
  `
7052
7092
  Open this to claim/work the bounty (you go straight to the source \u2014 we never touch payment):
7053
- ${chosen.bounty?.claimUrl ?? chosen.url}`
7093
+ ${sanitizeText(chosen.bounty?.claimUrl ?? chosen.url)}`
7054
7094
  );
7055
7095
  } catch (err) {
7056
7096
  console.error("terminalhire bounties error:", err.message ?? err);
@@ -559,6 +559,13 @@ var init_concurrency = __esm({
559
559
  }
560
560
  });
561
561
 
562
+ // ../../packages/core/src/feeds/effort.ts
563
+ var init_effort = __esm({
564
+ "../../packages/core/src/feeds/effort.ts"() {
565
+ "use strict";
566
+ }
567
+ });
568
+
562
569
  // ../../packages/core/src/feeds/github-bounties.ts
563
570
  var init_github_bounties = __esm({
564
571
  "../../packages/core/src/feeds/github-bounties.ts"() {
@@ -568,6 +575,7 @@ var init_github_bounties = __esm({
568
575
  init_bounty_gate();
569
576
  init_http();
570
577
  init_concurrency();
578
+ init_effort();
571
579
  }
572
580
  });
573
581
 
@@ -579,6 +587,7 @@ var init_opire = __esm({
579
587
  init_bounty_gate();
580
588
  init_github_bounties();
581
589
  init_http();
590
+ init_effort();
582
591
  }
583
592
  });
584
593
 
@@ -3925,9 +3934,9 @@ var init_keytar = __esm({
3925
3934
  }
3926
3935
  });
3927
3936
 
3928
- // node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/3ffac25b-ca95-4a86-9e17-a4cf326551de/scratchpad/rel2/node_modules/keytar/build/Release/keytar.node
3937
+ // node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node
3929
3938
  var require_keytar = __commonJS({
3930
- "node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/3ffac25b-ca95-4a86-9e17-a4cf326551de/scratchpad/rel2/node_modules/keytar/build/Release/keytar.node"(exports, module) {
3939
+ "node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node"(exports, module) {
3931
3940
  "use strict";
3932
3941
  init_keytar();
3933
3942
  try {
@@ -576,6 +576,13 @@ var init_concurrency = __esm({
576
576
  }
577
577
  });
578
578
 
579
+ // ../../packages/core/src/feeds/effort.ts
580
+ var init_effort = __esm({
581
+ "../../packages/core/src/feeds/effort.ts"() {
582
+ "use strict";
583
+ }
584
+ });
585
+
579
586
  // ../../packages/core/src/feeds/github-bounties.ts
580
587
  var init_github_bounties = __esm({
581
588
  "../../packages/core/src/feeds/github-bounties.ts"() {
@@ -585,6 +592,7 @@ var init_github_bounties = __esm({
585
592
  init_bounty_gate();
586
593
  init_http();
587
594
  init_concurrency();
595
+ init_effort();
588
596
  }
589
597
  });
590
598
 
@@ -596,6 +604,7 @@ var init_opire = __esm({
596
604
  init_bounty_gate();
597
605
  init_github_bounties();
598
606
  init_http();
607
+ init_effort();
599
608
  }
600
609
  });
601
610
 
@@ -3953,9 +3962,9 @@ var init_keytar = __esm({
3953
3962
  }
3954
3963
  });
3955
3964
 
3956
- // node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/3ffac25b-ca95-4a86-9e17-a4cf326551de/scratchpad/rel2/node_modules/keytar/build/Release/keytar.node
3965
+ // node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node
3957
3966
  var require_keytar = __commonJS({
3958
- "node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/3ffac25b-ca95-4a86-9e17-a4cf326551de/scratchpad/rel2/node_modules/keytar/build/Release/keytar.node"(exports, module) {
3967
+ "node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node"(exports, module) {
3959
3968
  "use strict";
3960
3969
  init_keytar();
3961
3970
  try {
@@ -894,6 +894,13 @@ var init_concurrency = __esm({
894
894
  }
895
895
  });
896
896
 
897
+ // ../../packages/core/src/feeds/effort.ts
898
+ var init_effort = __esm({
899
+ "../../packages/core/src/feeds/effort.ts"() {
900
+ "use strict";
901
+ }
902
+ });
903
+
897
904
  // ../../packages/core/src/feeds/github-bounties.ts
898
905
  var init_github_bounties = __esm({
899
906
  "../../packages/core/src/feeds/github-bounties.ts"() {
@@ -903,6 +910,7 @@ var init_github_bounties = __esm({
903
910
  init_bounty_gate();
904
911
  init_http();
905
912
  init_concurrency();
913
+ init_effort();
906
914
  }
907
915
  });
908
916
 
@@ -914,6 +922,7 @@ var init_opire = __esm({
914
922
  init_bounty_gate();
915
923
  init_github_bounties();
916
924
  init_http();
925
+ init_effort();
917
926
  }
918
927
  });
919
928
 
@@ -1242,9 +1251,9 @@ var init_keytar = __esm({
1242
1251
  }
1243
1252
  });
1244
1253
 
1245
- // node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/3ffac25b-ca95-4a86-9e17-a4cf326551de/scratchpad/rel2/node_modules/keytar/build/Release/keytar.node
1254
+ // node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node
1246
1255
  var require_keytar = __commonJS({
1247
- "node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/3ffac25b-ca95-4a86-9e17-a4cf326551de/scratchpad/rel2/node_modules/keytar/build/Release/keytar.node"(exports, module) {
1256
+ "node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node"(exports, module) {
1248
1257
  "use strict";
1249
1258
  init_keytar();
1250
1259
  try {
@@ -1608,6 +1617,37 @@ function isContributeEnabled() {
1608
1617
  return readConfig().contributeEnabled === true;
1609
1618
  }
1610
1619
 
1620
+ // bin/sanitize.js
1621
+ var CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
1622
+ function sanitizeText(s) {
1623
+ if (s == null) return "";
1624
+ return String(s).replace(CONTROL_CHARS, "");
1625
+ }
1626
+ function safeHttpUrl(url) {
1627
+ if (url == null) return null;
1628
+ const raw = String(url);
1629
+ CONTROL_CHARS.lastIndex = 0;
1630
+ if (CONTROL_CHARS.test(raw)) return null;
1631
+ let parsed;
1632
+ try {
1633
+ parsed = new URL(raw);
1634
+ } catch {
1635
+ return null;
1636
+ }
1637
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
1638
+ return parsed.href;
1639
+ }
1640
+ function linkTitle(title, url) {
1641
+ const safeTitle = sanitizeText(title);
1642
+ const href = safeHttpUrl(url);
1643
+ const isTTY = process.stdout.isTTY;
1644
+ const noColor = process.env["NO_COLOR"] !== void 0;
1645
+ if (isTTY && !noColor && href) {
1646
+ return `\x1B]8;;${href}\x1B\\${safeTitle}\x1B]8;;\x1B\\`;
1647
+ }
1648
+ return href ? `${safeTitle} (${href})` : safeTitle;
1649
+ }
1650
+
1611
1651
  // bin/jpi-contribute.js
1612
1652
  var TERMINALHIRE_DIR4 = process.env.TERMINALHIRE_DIR || join5(homedir4(), ".terminalhire");
1613
1653
  var INDEX_CACHE_FILE2 = join5(TERMINALHIRE_DIR4, "index-cache.json");
@@ -1709,19 +1749,13 @@ function displayLanguage(job) {
1709
1749
  const tag = (job.tags ?? []).find((t) => LANGUAGES.has(String(t).toLowerCase()));
1710
1750
  return tag ?? "\u2014";
1711
1751
  }
1712
- function linkTitle(title, url) {
1713
- const isTTY = process.stdout.isTTY;
1714
- const noColor = process.env["NO_COLOR"] !== void 0;
1715
- if (isTTY && !noColor && url) return `\x1B]8;;${url}\x1B\\${title}\x1B]8;;\x1B\\`;
1716
- return url ? `${title} (${url})` : title;
1717
- }
1718
1752
  function renderRow(i, result) {
1719
1753
  const job = result.job;
1720
1754
  const c = job.contribution ?? {};
1721
- const repo = c.repoFullName ?? job.company ?? "";
1755
+ const repo = sanitizeText(c.repoFullName ?? job.company ?? "");
1722
1756
  const num = c.issueNumber != null ? `#${c.issueNumber}` : "";
1723
- const label = c.labels && c.labels.length ? c.labels[0] : "\u2014";
1724
- const lang = displayLanguage(job);
1757
+ const label = c.labels && c.labels.length ? sanitizeText(c.labels[0]) : "\u2014";
1758
+ const lang = sanitizeText(displayLanguage(job));
1725
1759
  const scorePct = `match ${Math.round((result.score ?? 0) * 100)}%`;
1726
1760
  const ref = opportunityShortToken(job.id);
1727
1761
  const line1 = `${i + 1}. ${linkTitle(job.title, c.issueUrl ?? job.url)} [${ref}]`;
@@ -921,6 +921,7 @@ async function fetchRepoMeta(owner, name, token, cache) {
921
921
  stars: r.stargazers_count ?? 0,
922
922
  archived: !!r.archived,
923
923
  fork: !!r.fork,
924
+ private: !!r.private,
924
925
  language: r.language ?? null,
925
926
  topics: r.topics ?? [],
926
927
  contributors
@@ -953,7 +954,7 @@ async function computeAcceptanceFromSearch(login, token, ownedOrgs, cache, gates
953
954
  const loginLc = login.toLowerCase();
954
955
  let items;
955
956
  try {
956
- const q = encodeURIComponent(`type:pr is:merged author:${login} -user:${login} sort:updated`);
957
+ const q = encodeURIComponent(`type:pr is:merged is:public author:${login} -user:${login} sort:updated`);
957
958
  const res = await ghFetch(
958
959
  `/search/issues?q=${q}&per_page=${CANDIDATE_PR_PAGE}`,
959
960
  token
@@ -966,6 +967,7 @@ async function computeAcceptanceFromSearch(login, token, ownedOrgs, cache, gates
966
967
  }
967
968
  const byDomain = {};
968
969
  let qualifyingTotal = 0;
970
+ const qualifyingPRs = [];
969
971
  for (const item of items) {
970
972
  const repo = parseRepoUrl(item.repository_url);
971
973
  if (!repo) continue;
@@ -975,13 +977,22 @@ async function computeAcceptanceFromSearch(login, token, ownedOrgs, cache, gates
975
977
  if (isTrivialPRTitle(item.title)) continue;
976
978
  const meta = await fetchRepoMeta(repo.owner, repo.name, token, cache);
977
979
  if (!meta) continue;
980
+ if (meta.private) continue;
978
981
  if (meta.archived || meta.fork) continue;
979
982
  if (meta.stars < gates.minStars) continue;
980
983
  if (meta.contributors !== void 0 && meta.contributors < gates.minContributors) continue;
981
984
  qualifyingTotal += 1;
982
985
  const mergedAt = item.pull_request?.merged_at ?? item.closed_at ?? item.created_at;
983
986
  const rawDomains = [meta.language ?? "", ...meta.topics].filter(Boolean);
984
- for (const d of new Set(normalize(rawDomains))) {
987
+ const domainTags = [...new Set(normalize(rawDomains))];
988
+ qualifyingPRs.push({
989
+ url: item.html_url,
990
+ title: item.title,
991
+ repo: `${repo.owner}/${repo.name}`,
992
+ domains: domainTags,
993
+ mergedAt
994
+ });
995
+ for (const d of domainTags) {
985
996
  const b = byDomain[d] ?? (byDomain[d] = { mergedPRs: 0, distinctOrgs: 0, lastMergedAt: mergedAt, orgs: /* @__PURE__ */ new Set() });
986
997
  b.mergedPRs += 1;
987
998
  b.orgs.add(ownerLc);
@@ -996,7 +1007,7 @@ async function computeAcceptanceFromSearch(login, token, ownedOrgs, cache, gates
996
1007
  lastMergedAt: b.lastMergedAt
997
1008
  };
998
1009
  }
999
- return { status: "ok", byDomain: finalDomains, qualifyingTotal, computedAt };
1010
+ return { status: "ok", byDomain: finalDomains, qualifyingTotal, qualifyingPRs, computedAt };
1000
1011
  }
1001
1012
  async function computeAcceptanceCredential(login, token, cache = /* @__PURE__ */ new Map()) {
1002
1013
  if (!token) return emptyCredential("no-token");
@@ -1807,6 +1818,19 @@ var init_concurrency = __esm({
1807
1818
  }
1808
1819
  });
1809
1820
 
1821
+ // ../../packages/core/src/feeds/effort.ts
1822
+ function effortFromAmount(amount) {
1823
+ if (amount == null) return void 0;
1824
+ if (amount <= 500) return "small";
1825
+ if (amount <= 2e3) return "medium";
1826
+ return "large";
1827
+ }
1828
+ var init_effort = __esm({
1829
+ "../../packages/core/src/feeds/effort.ts"() {
1830
+ "use strict";
1831
+ }
1832
+ });
1833
+
1810
1834
  // ../../packages/core/src/feeds/github-bounties.ts
1811
1835
  function authHeaders() {
1812
1836
  const token = process.env["GITHUB_TOKEN"] ?? process.env["GH_TOKEN"];
@@ -1829,12 +1853,6 @@ function parseAmountUSD(text) {
1829
1853
  if (!Number.isFinite(n) || n <= 0 || n > 1e6) return void 0;
1830
1854
  return Math.round(n);
1831
1855
  }
1832
- function effortFromAmount(amount) {
1833
- if (amount == null) return void 0;
1834
- if (amount <= 500) return "small";
1835
- if (amount <= 2e3) return "medium";
1836
- return "large";
1837
- }
1838
1856
  function labelNames(issue) {
1839
1857
  return (issue.labels ?? []).map((l) => typeof l === "string" ? l : l.name ?? "").filter(Boolean);
1840
1858
  }
@@ -2082,6 +2100,7 @@ var init_github_bounties = __esm({
2082
2100
  init_bounty_gate();
2083
2101
  init_http();
2084
2102
  init_concurrency();
2103
+ init_effort();
2085
2104
  GITHUB_API = "https://api.github.com";
2086
2105
  BOUNTY_LABEL_RE = /bounty|reward|funded|💎|💰/i;
2087
2106
  SEARCH_QUERIES = [
@@ -2133,12 +2152,6 @@ var init_github_bounties = __esm({
2133
2152
  function tokenize3(text) {
2134
2153
  return text.toLowerCase().replace(/[^a-z0-9.\-+#]/g, " ").split(/\s+/).filter((w) => w.length > 1);
2135
2154
  }
2136
- function effortFromAmount2(usd) {
2137
- if (usd == null) return void 0;
2138
- if (usd < 150) return "small";
2139
- if (usd < 750) return "medium";
2140
- return "large";
2141
- }
2142
2155
  function priceToUSD(p) {
2143
2156
  if (!p || typeof p.value !== "number") return void 0;
2144
2157
  if (p.unit === "USD_CENT") return Math.round(p.value) / 100;
@@ -2161,6 +2174,7 @@ var init_opire = __esm({
2161
2174
  init_bounty_gate();
2162
2175
  init_github_bounties();
2163
2176
  init_http();
2177
+ init_effort();
2164
2178
  OPIRE_REWARDS_URL = "https://api.opire.dev/rewards";
2165
2179
  MIN_USD = 25;
2166
2180
  MAX_USD = 25e3;
@@ -2197,7 +2211,7 @@ var init_opire = __esm({
2197
2211
  const tags = normalize([...r.programmingLanguages ?? [], ...tokenize3(title)]);
2198
2212
  const bounty = {
2199
2213
  amountUSD,
2200
- estimatedEffort: effortFromAmount2(amountUSD),
2214
+ estimatedEffort: effortFromAmount(amountUSD),
2201
2215
  bountySource: "opire",
2202
2216
  claimUrl: r.url,
2203
2217
  repoFullName
@@ -6544,6 +6558,7 @@ __export(src_exports, {
6544
6558
  isBounty: () => isBounty,
6545
6559
  isContribution: () => isContribution,
6546
6560
  isOverIntroLimit: () => isOverIntroLimit,
6561
+ isTrivialPRTitle: () => isTrivialPRTitle,
6547
6562
  joinLabels: () => joinLabels,
6548
6563
  labelFor: () => labelFor,
6549
6564
  lever: () => lever,
@@ -6600,9 +6615,9 @@ var init_keytar = __esm({
6600
6615
  }
6601
6616
  });
6602
6617
 
6603
- // node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/3ffac25b-ca95-4a86-9e17-a4cf326551de/scratchpad/rel2/node_modules/keytar/build/Release/keytar.node
6618
+ // node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node
6604
6619
  var require_keytar = __commonJS({
6605
- "node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/3ffac25b-ca95-4a86-9e17-a4cf326551de/scratchpad/rel2/node_modules/keytar/build/Release/keytar.node"(exports, module) {
6620
+ "node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node"(exports, module) {
6606
6621
  "use strict";
6607
6622
  init_keytar();
6608
6623
  try {
@@ -6974,6 +6989,37 @@ function excludeOwnCard(results, ownLogin) {
6974
6989
  });
6975
6990
  }
6976
6991
 
6992
+ // bin/sanitize.js
6993
+ var CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
6994
+ function sanitizeText(s) {
6995
+ if (s == null) return "";
6996
+ return String(s).replace(CONTROL_CHARS, "");
6997
+ }
6998
+ function safeHttpUrl(url) {
6999
+ if (url == null) return null;
7000
+ const raw = String(url);
7001
+ CONTROL_CHARS.lastIndex = 0;
7002
+ if (CONTROL_CHARS.test(raw)) return null;
7003
+ let parsed;
7004
+ try {
7005
+ parsed = new URL(raw);
7006
+ } catch {
7007
+ return null;
7008
+ }
7009
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
7010
+ return parsed.href;
7011
+ }
7012
+ function linkTitle(title, url) {
7013
+ const safeTitle = sanitizeText(title);
7014
+ const href = safeHttpUrl(url);
7015
+ const isTTY = process.stdout.isTTY;
7016
+ const noColor = process.env["NO_COLOR"] !== void 0;
7017
+ if (isTTY && !noColor && href) {
7018
+ return `\x1B]8;;${href}\x1B\\${safeTitle}\x1B]8;;\x1B\\`;
7019
+ }
7020
+ return href ? `${safeTitle} (${href})` : safeTitle;
7021
+ }
7022
+
6977
7023
  // bin/jpi-devs.js
6978
7024
  var API_URL2 = process.env["TERMINALHIRE_API_URL"] ?? process.env["JPI_API_URL"] ?? "https://terminalhire.com";
6979
7025
  var DEFAULT_LIMIT = 10;
@@ -6995,24 +7041,18 @@ function credentialUrl(job) {
6995
7041
  const u = job.url ?? "";
6996
7042
  return u.startsWith("http") ? u : `${API_URL2}${u}`;
6997
7043
  }
6998
- function linkTitle(title, url) {
6999
- const isTTY = process.stdout.isTTY;
7000
- const noColor = process.env["NO_COLOR"] !== void 0;
7001
- if (isTTY && !noColor && url) return `\x1B]8;;${url}\x1B\\${title}\x1B]8;;\x1B\\`;
7002
- return url ? `${title} (${url})` : title;
7003
- }
7004
7044
  function printCard(i, result) {
7005
7045
  const { job, score, matchedTags, reason } = result;
7006
7046
  const url = credentialUrl(job);
7007
7047
  const kind = job.source === "project" ? "project" : "developer";
7008
- const byline = job.source === "project" ? ` \xB7 by @${job.company}` : "";
7048
+ const byline = job.source === "project" ? ` \xB7 by @${sanitizeText(job.company)}` : "";
7009
7049
  const scoreStr = score > 0 ? ` \xB7 match ${Math.round(score * 100)}%` : "";
7010
7050
  console.log(`
7011
7051
  ${i + 1}. ${linkTitle(job.title, url)} \u2014 ${kind}${byline}${scoreStr}`);
7012
7052
  console.log(` id: ${job.id}`);
7013
7053
  if (reason) console.log(` ${reason}`);
7014
7054
  if (matchedTags && matchedTags.length) console.log(` Tags matched: ${matchedTags.slice(0, 5).join(", ")}`);
7015
- console.log(` Profile: ${url}`);
7055
+ console.log(` Profile: ${sanitizeText(url)}`);
7016
7056
  }
7017
7057
  async function run() {
7018
7058
  try {