terminalhire 0.4.5 → 0.4.6

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.
@@ -1640,6 +1640,28 @@ var init_bounty_gate = __esm({
1640
1640
  }
1641
1641
  });
1642
1642
 
1643
+ // ../../packages/core/src/concurrency.ts
1644
+ async function mapWithConcurrency(items, limit, fn) {
1645
+ const results = new Array(items.length);
1646
+ if (items.length === 0) return results;
1647
+ const workers = Math.max(1, Math.min(Math.floor(limit) || 1, items.length));
1648
+ let next = 0;
1649
+ async function run2() {
1650
+ for (; ; ) {
1651
+ const i = next++;
1652
+ if (i >= items.length) return;
1653
+ results[i] = await fn(items[i], i);
1654
+ }
1655
+ }
1656
+ await Promise.all(Array.from({ length: workers }, run2));
1657
+ return results;
1658
+ }
1659
+ var init_concurrency = __esm({
1660
+ "../../packages/core/src/concurrency.ts"() {
1661
+ "use strict";
1662
+ }
1663
+ });
1664
+
1643
1665
  // ../../packages/core/src/feeds/github-bounties.ts
1644
1666
  function authHeaders() {
1645
1667
  const token = process.env["GITHUB_TOKEN"] ?? process.env["GH_TOKEN"];
@@ -1731,7 +1753,7 @@ async function fetchRepoBounties(repoFullName) {
1731
1753
  if (!issues) return [];
1732
1754
  const bounties = issues.filter(isBountyIssue).slice(0, MAX_BOUNTIES_PER_REPO);
1733
1755
  const owner = repo.owner.login;
1734
- return Promise.all(bounties.map(async (issue) => {
1756
+ return mapWithConcurrency(bounties, BOUNTY_FETCH_CONCURRENCY, async (issue) => {
1735
1757
  const title = decodeEntities(issue.title).trim();
1736
1758
  const body = issue.body ? decodeEntities(issue.body) : "";
1737
1759
  const amountUSD = parseAmountUSD(title) ?? parseAmountUSD(body) ?? await fetchCommentAmount(repoFullName, issue.number);
@@ -1760,7 +1782,7 @@ async function fetchRepoBounties(repoFullName) {
1760
1782
  },
1761
1783
  raw: issue
1762
1784
  };
1763
- }));
1785
+ });
1764
1786
  }
1765
1787
  function repoFullNameFromApiUrl(url) {
1766
1788
  const m = url.match(/\/repos\/([^/]+)\/([^/]+)\/?$/);
@@ -1902,7 +1924,7 @@ async function fetchSearchBounties() {
1902
1924
  }
1903
1925
  return jobs;
1904
1926
  }
1905
- var GITHUB_API, BOUNTY_LABEL_RE, SEARCH_QUERIES, SEARCH_PER_PAGE, MAX_SEARCH_BOUNTIES, MAX_SEARCH_ISSUES_SCANNED, REPO_META_CONCURRENCY, repoMetaCache, MAX_PR_PAGES, repoOpenPRRefsCache, issueStateCache, githubBounties;
1927
+ var GITHUB_API, BOUNTY_LABEL_RE, SEARCH_QUERIES, SEARCH_PER_PAGE, MAX_SEARCH_BOUNTIES, MAX_SEARCH_ISSUES_SCANNED, REPO_META_CONCURRENCY, BOUNTY_FETCH_CONCURRENCY, repoMetaCache, MAX_PR_PAGES, repoOpenPRRefsCache, issueStateCache, githubBounties;
1906
1928
  var init_github_bounties = __esm({
1907
1929
  "../../packages/core/src/feeds/github-bounties.ts"() {
1908
1930
  "use strict";
@@ -1910,6 +1932,7 @@ var init_github_bounties = __esm({
1910
1932
  init_entities();
1911
1933
  init_bounty_gate();
1912
1934
  init_http();
1935
+ init_concurrency();
1913
1936
  GITHUB_API = "https://api.github.com";
1914
1937
  BOUNTY_LABEL_RE = /bounty|reward|funded|💎|💰/i;
1915
1938
  SEARCH_QUERIES = [
@@ -1922,6 +1945,7 @@ var init_github_bounties = __esm({
1922
1945
  MAX_SEARCH_BOUNTIES = 150;
1923
1946
  MAX_SEARCH_ISSUES_SCANNED = 300;
1924
1947
  REPO_META_CONCURRENCY = 15;
1948
+ BOUNTY_FETCH_CONCURRENCY = 6;
1925
1949
  repoMetaCache = /* @__PURE__ */ new Map();
1926
1950
  MAX_PR_PAGES = 3;
1927
1951
  repoOpenPRRefsCache = /* @__PURE__ */ new Map();
@@ -9,6 +9,35 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
+ // src/open-url.js
13
+ import { spawn } from "child_process";
14
+ function openInBrowser(url) {
15
+ let cmd;
16
+ let args3;
17
+ if (process.platform === "darwin") {
18
+ cmd = "open";
19
+ args3 = [url];
20
+ } else if (process.platform === "win32") {
21
+ cmd = "cmd";
22
+ args3 = ["/c", "start", "", url];
23
+ } else {
24
+ cmd = "xdg-open";
25
+ args3 = [url];
26
+ }
27
+ try {
28
+ const child = spawn(cmd, args3, { stdio: "ignore", detached: true });
29
+ child.on("error", () => {
30
+ });
31
+ child.unref();
32
+ } catch {
33
+ }
34
+ }
35
+ var init_open_url = __esm({
36
+ "src/open-url.js"() {
37
+ "use strict";
38
+ }
39
+ });
40
+
12
41
  // src/github-auth.ts
13
42
  var github_auth_exports = {};
14
43
  __export(github_auth_exports, {
@@ -1854,6 +1883,28 @@ var init_bounty_gate = __esm({
1854
1883
  }
1855
1884
  });
1856
1885
 
1886
+ // ../../packages/core/src/concurrency.ts
1887
+ async function mapWithConcurrency(items, limit, fn) {
1888
+ const results = new Array(items.length);
1889
+ if (items.length === 0) return results;
1890
+ const workers = Math.max(1, Math.min(Math.floor(limit) || 1, items.length));
1891
+ let next = 0;
1892
+ async function run13() {
1893
+ for (; ; ) {
1894
+ const i = next++;
1895
+ if (i >= items.length) return;
1896
+ results[i] = await fn(items[i], i);
1897
+ }
1898
+ }
1899
+ await Promise.all(Array.from({ length: workers }, run13));
1900
+ return results;
1901
+ }
1902
+ var init_concurrency = __esm({
1903
+ "../../packages/core/src/concurrency.ts"() {
1904
+ "use strict";
1905
+ }
1906
+ });
1907
+
1857
1908
  // ../../packages/core/src/feeds/github-bounties.ts
1858
1909
  function authHeaders() {
1859
1910
  const token = process.env["GITHUB_TOKEN"] ?? process.env["GH_TOKEN"];
@@ -1945,7 +1996,7 @@ async function fetchRepoBounties(repoFullName) {
1945
1996
  if (!issues) return [];
1946
1997
  const bounties = issues.filter(isBountyIssue).slice(0, MAX_BOUNTIES_PER_REPO);
1947
1998
  const owner = repo.owner.login;
1948
- return Promise.all(bounties.map(async (issue) => {
1999
+ return mapWithConcurrency(bounties, BOUNTY_FETCH_CONCURRENCY, async (issue) => {
1949
2000
  const title = decodeEntities(issue.title).trim();
1950
2001
  const body = issue.body ? decodeEntities(issue.body) : "";
1951
2002
  const amountUSD = parseAmountUSD(title) ?? parseAmountUSD(body) ?? await fetchCommentAmount(repoFullName, issue.number);
@@ -1974,7 +2025,7 @@ async function fetchRepoBounties(repoFullName) {
1974
2025
  },
1975
2026
  raw: issue
1976
2027
  };
1977
- }));
2028
+ });
1978
2029
  }
1979
2030
  function repoFullNameFromApiUrl(url) {
1980
2031
  const m = url.match(/\/repos\/([^/]+)\/([^/]+)\/?$/);
@@ -2116,7 +2167,7 @@ async function fetchSearchBounties() {
2116
2167
  }
2117
2168
  return jobs;
2118
2169
  }
2119
- var GITHUB_API, BOUNTY_LABEL_RE, SEARCH_QUERIES, SEARCH_PER_PAGE, MAX_SEARCH_BOUNTIES, MAX_SEARCH_ISSUES_SCANNED, REPO_META_CONCURRENCY, repoMetaCache, MAX_PR_PAGES, repoOpenPRRefsCache, issueStateCache, githubBounties;
2170
+ var GITHUB_API, BOUNTY_LABEL_RE, SEARCH_QUERIES, SEARCH_PER_PAGE, MAX_SEARCH_BOUNTIES, MAX_SEARCH_ISSUES_SCANNED, REPO_META_CONCURRENCY, BOUNTY_FETCH_CONCURRENCY, repoMetaCache, MAX_PR_PAGES, repoOpenPRRefsCache, issueStateCache, githubBounties;
2120
2171
  var init_github_bounties = __esm({
2121
2172
  "../../packages/core/src/feeds/github-bounties.ts"() {
2122
2173
  "use strict";
@@ -2124,6 +2175,7 @@ var init_github_bounties = __esm({
2124
2175
  init_entities();
2125
2176
  init_bounty_gate();
2126
2177
  init_http();
2178
+ init_concurrency();
2127
2179
  GITHUB_API = "https://api.github.com";
2128
2180
  BOUNTY_LABEL_RE = /bounty|reward|funded|💎|💰/i;
2129
2181
  SEARCH_QUERIES = [
@@ -2136,6 +2188,7 @@ var init_github_bounties = __esm({
2136
2188
  MAX_SEARCH_BOUNTIES = 150;
2137
2189
  MAX_SEARCH_ISSUES_SCANNED = 300;
2138
2190
  REPO_META_CONCURRENCY = 15;
2191
+ BOUNTY_FETCH_CONCURRENCY = 6;
2139
2192
  repoMetaCache = /* @__PURE__ */ new Map();
2140
2193
  MAX_PR_PAGES = 3;
2141
2194
  repoOpenPRRefsCache = /* @__PURE__ */ new Map();
@@ -3124,6 +3177,26 @@ async function runLogin() {
3124
3177
  console.log(" Profile updated at ~/.terminalhire/profile.enc (encrypted at rest)");
3125
3178
  console.log(" GitHub data stays on your machine unless you consent to share it in a lead.");
3126
3179
  console.log("");
3180
+ const skipWeb = process.argv.includes("--no-web");
3181
+ if (!isMock && !skipWeb) {
3182
+ try {
3183
+ const OAUTH_BASE = "https://www.terminalhire.com";
3184
+ const webUrl = `${OAUTH_BASE}/api/auth/github?next=/dashboard`;
3185
+ console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
3186
+ console.log(" Your web profile & r\xE9sum\xE9 \u2014 public GitHub data only,");
3187
+ console.log(" your local profile is NOT uploaded.");
3188
+ console.log(` \u2192 ${webUrl}`);
3189
+ if (process.stdout.isTTY) {
3190
+ console.log(" Opening it now to sign you in at terminalhire.com\u2026");
3191
+ openInBrowser(webUrl);
3192
+ } else {
3193
+ console.log(" Open the link above to sign in & view your r\xE9sum\xE9.");
3194
+ }
3195
+ console.log(" (skip next time with: terminalhire login --no-web)");
3196
+ console.log("");
3197
+ } catch {
3198
+ }
3199
+ }
3127
3200
  console.log(" Run `terminalhire jobs` to see matching roles using your enriched profile.");
3128
3201
  console.log("");
3129
3202
  } catch (err) {
@@ -3161,6 +3234,7 @@ async function runLogout() {
3161
3234
  var init_jpi_login = __esm({
3162
3235
  "bin/jpi-login.js"() {
3163
3236
  "use strict";
3237
+ init_open_url();
3164
3238
  }
3165
3239
  });
3166
3240
 
@@ -4949,7 +5023,6 @@ import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, mkdirS
4949
5023
  import { join as join12 } from "path";
4950
5024
  import { homedir as homedir10, hostname as osHostname } from "os";
4951
5025
  import { createInterface as createInterface5 } from "readline";
4952
- import { spawn } from "child_process";
4953
5026
  function ask2(question) {
4954
5027
  const rl = createInterface5({ input: process.stdin, output: process.stdout });
4955
5028
  return new Promise((res) => {
@@ -5018,27 +5091,6 @@ function renderPreview(fields) {
5018
5091
  console.log(" This is NOT required to use terminalhire.");
5019
5092
  console.log("");
5020
5093
  }
5021
- function openInBrowser(url) {
5022
- let cmd;
5023
- let args3;
5024
- if (process.platform === "darwin") {
5025
- cmd = "open";
5026
- args3 = [url];
5027
- } else if (process.platform === "win32") {
5028
- cmd = "cmd";
5029
- args3 = ["/c", "start", "", url];
5030
- } else {
5031
- cmd = "xdg-open";
5032
- args3 = [url];
5033
- }
5034
- try {
5035
- const child = spawn(cmd, args3, { stdio: "ignore", detached: true });
5036
- child.on("error", () => {
5037
- });
5038
- child.unref();
5039
- } catch {
5040
- }
5041
- }
5042
5094
  function sleep2(ms) {
5043
5095
  return new Promise((res) => setTimeout(res, ms));
5044
5096
  }
@@ -5307,6 +5359,7 @@ var TH_DIR3, TIER1_MARKER, API_URL3, SYNC_BASE, POLL_INTERVAL_MS, POLL_TIMEOUT_M
5307
5359
  var init_jpi_sync = __esm({
5308
5360
  "bin/jpi-sync.js"() {
5309
5361
  "use strict";
5362
+ init_open_url();
5310
5363
  TH_DIR3 = process.env["TERMINALHIRE_DIR"] || join12(homedir10(), ".terminalhire");
5311
5364
  TIER1_MARKER = join12(TH_DIR3, "tier1.json");
5312
5365
  API_URL3 = process.env["TERMINALHIRE_API_URL"] || process.env["JPI_API_URL"] || "https://terminalhire.com";
@@ -1640,6 +1640,28 @@ var init_bounty_gate = __esm({
1640
1640
  }
1641
1641
  });
1642
1642
 
1643
+ // ../../packages/core/src/concurrency.ts
1644
+ async function mapWithConcurrency(items, limit, fn) {
1645
+ const results = new Array(items.length);
1646
+ if (items.length === 0) return results;
1647
+ const workers = Math.max(1, Math.min(Math.floor(limit) || 1, items.length));
1648
+ let next = 0;
1649
+ async function run2() {
1650
+ for (; ; ) {
1651
+ const i = next++;
1652
+ if (i >= items.length) return;
1653
+ results[i] = await fn(items[i], i);
1654
+ }
1655
+ }
1656
+ await Promise.all(Array.from({ length: workers }, run2));
1657
+ return results;
1658
+ }
1659
+ var init_concurrency = __esm({
1660
+ "../../packages/core/src/concurrency.ts"() {
1661
+ "use strict";
1662
+ }
1663
+ });
1664
+
1643
1665
  // ../../packages/core/src/feeds/github-bounties.ts
1644
1666
  function authHeaders() {
1645
1667
  const token = process.env["GITHUB_TOKEN"] ?? process.env["GH_TOKEN"];
@@ -1731,7 +1753,7 @@ async function fetchRepoBounties(repoFullName) {
1731
1753
  if (!issues) return [];
1732
1754
  const bounties = issues.filter(isBountyIssue).slice(0, MAX_BOUNTIES_PER_REPO);
1733
1755
  const owner = repo.owner.login;
1734
- return Promise.all(bounties.map(async (issue) => {
1756
+ return mapWithConcurrency(bounties, BOUNTY_FETCH_CONCURRENCY, async (issue) => {
1735
1757
  const title = decodeEntities(issue.title).trim();
1736
1758
  const body = issue.body ? decodeEntities(issue.body) : "";
1737
1759
  const amountUSD = parseAmountUSD(title) ?? parseAmountUSD(body) ?? await fetchCommentAmount(repoFullName, issue.number);
@@ -1760,7 +1782,7 @@ async function fetchRepoBounties(repoFullName) {
1760
1782
  },
1761
1783
  raw: issue
1762
1784
  };
1763
- }));
1785
+ });
1764
1786
  }
1765
1787
  function repoFullNameFromApiUrl(url) {
1766
1788
  const m = url.match(/\/repos\/([^/]+)\/([^/]+)\/?$/);
@@ -1902,7 +1924,7 @@ async function fetchSearchBounties() {
1902
1924
  }
1903
1925
  return jobs;
1904
1926
  }
1905
- var GITHUB_API, BOUNTY_LABEL_RE, SEARCH_QUERIES, SEARCH_PER_PAGE, MAX_SEARCH_BOUNTIES, MAX_SEARCH_ISSUES_SCANNED, REPO_META_CONCURRENCY, repoMetaCache, MAX_PR_PAGES, repoOpenPRRefsCache, issueStateCache, githubBounties;
1927
+ var GITHUB_API, BOUNTY_LABEL_RE, SEARCH_QUERIES, SEARCH_PER_PAGE, MAX_SEARCH_BOUNTIES, MAX_SEARCH_ISSUES_SCANNED, REPO_META_CONCURRENCY, BOUNTY_FETCH_CONCURRENCY, repoMetaCache, MAX_PR_PAGES, repoOpenPRRefsCache, issueStateCache, githubBounties;
1906
1928
  var init_github_bounties = __esm({
1907
1929
  "../../packages/core/src/feeds/github-bounties.ts"() {
1908
1930
  "use strict";
@@ -1910,6 +1932,7 @@ var init_github_bounties = __esm({
1910
1932
  init_entities();
1911
1933
  init_bounty_gate();
1912
1934
  init_http();
1935
+ init_concurrency();
1913
1936
  GITHUB_API = "https://api.github.com";
1914
1937
  BOUNTY_LABEL_RE = /bounty|reward|funded|💎|💰/i;
1915
1938
  SEARCH_QUERIES = [
@@ -1922,6 +1945,7 @@ var init_github_bounties = __esm({
1922
1945
  MAX_SEARCH_BOUNTIES = 150;
1923
1946
  MAX_SEARCH_ISSUES_SCANNED = 300;
1924
1947
  REPO_META_CONCURRENCY = 15;
1948
+ BOUNTY_FETCH_CONCURRENCY = 6;
1925
1949
  repoMetaCache = /* @__PURE__ */ new Map();
1926
1950
  MAX_PR_PAGES = 3;
1927
1951
  repoOpenPRRefsCache = /* @__PURE__ */ new Map();
@@ -509,6 +509,13 @@ var init_bounty_gate = __esm({
509
509
  }
510
510
  });
511
511
 
512
+ // ../../packages/core/src/concurrency.ts
513
+ var init_concurrency = __esm({
514
+ "../../packages/core/src/concurrency.ts"() {
515
+ "use strict";
516
+ }
517
+ });
518
+
512
519
  // ../../packages/core/src/feeds/github-bounties.ts
513
520
  var init_github_bounties = __esm({
514
521
  "../../packages/core/src/feeds/github-bounties.ts"() {
@@ -517,6 +524,7 @@ var init_github_bounties = __esm({
517
524
  init_entities();
518
525
  init_bounty_gate();
519
526
  init_http();
527
+ init_concurrency();
520
528
  }
521
529
  });
522
530
 
@@ -1854,6 +1854,28 @@ var init_bounty_gate = __esm({
1854
1854
  }
1855
1855
  });
1856
1856
 
1857
+ // ../../packages/core/src/concurrency.ts
1858
+ async function mapWithConcurrency(items, limit, fn) {
1859
+ const results = new Array(items.length);
1860
+ if (items.length === 0) return results;
1861
+ const workers = Math.max(1, Math.min(Math.floor(limit) || 1, items.length));
1862
+ let next = 0;
1863
+ async function run2() {
1864
+ for (; ; ) {
1865
+ const i = next++;
1866
+ if (i >= items.length) return;
1867
+ results[i] = await fn(items[i], i);
1868
+ }
1869
+ }
1870
+ await Promise.all(Array.from({ length: workers }, run2));
1871
+ return results;
1872
+ }
1873
+ var init_concurrency = __esm({
1874
+ "../../packages/core/src/concurrency.ts"() {
1875
+ "use strict";
1876
+ }
1877
+ });
1878
+
1857
1879
  // ../../packages/core/src/feeds/github-bounties.ts
1858
1880
  function authHeaders() {
1859
1881
  const token = process.env["GITHUB_TOKEN"] ?? process.env["GH_TOKEN"];
@@ -1945,7 +1967,7 @@ async function fetchRepoBounties(repoFullName) {
1945
1967
  if (!issues) return [];
1946
1968
  const bounties = issues.filter(isBountyIssue).slice(0, MAX_BOUNTIES_PER_REPO);
1947
1969
  const owner = repo.owner.login;
1948
- return Promise.all(bounties.map(async (issue) => {
1970
+ return mapWithConcurrency(bounties, BOUNTY_FETCH_CONCURRENCY, async (issue) => {
1949
1971
  const title = decodeEntities(issue.title).trim();
1950
1972
  const body = issue.body ? decodeEntities(issue.body) : "";
1951
1973
  const amountUSD = parseAmountUSD(title) ?? parseAmountUSD(body) ?? await fetchCommentAmount(repoFullName, issue.number);
@@ -1974,7 +1996,7 @@ async function fetchRepoBounties(repoFullName) {
1974
1996
  },
1975
1997
  raw: issue
1976
1998
  };
1977
- }));
1999
+ });
1978
2000
  }
1979
2001
  function repoFullNameFromApiUrl(url) {
1980
2002
  const m = url.match(/\/repos\/([^/]+)\/([^/]+)\/?$/);
@@ -2116,7 +2138,7 @@ async function fetchSearchBounties() {
2116
2138
  }
2117
2139
  return jobs;
2118
2140
  }
2119
- var GITHUB_API, BOUNTY_LABEL_RE, SEARCH_QUERIES, SEARCH_PER_PAGE, MAX_SEARCH_BOUNTIES, MAX_SEARCH_ISSUES_SCANNED, REPO_META_CONCURRENCY, repoMetaCache, MAX_PR_PAGES, repoOpenPRRefsCache, issueStateCache, githubBounties;
2141
+ var GITHUB_API, BOUNTY_LABEL_RE, SEARCH_QUERIES, SEARCH_PER_PAGE, MAX_SEARCH_BOUNTIES, MAX_SEARCH_ISSUES_SCANNED, REPO_META_CONCURRENCY, BOUNTY_FETCH_CONCURRENCY, repoMetaCache, MAX_PR_PAGES, repoOpenPRRefsCache, issueStateCache, githubBounties;
2120
2142
  var init_github_bounties = __esm({
2121
2143
  "../../packages/core/src/feeds/github-bounties.ts"() {
2122
2144
  "use strict";
@@ -2124,6 +2146,7 @@ var init_github_bounties = __esm({
2124
2146
  init_entities();
2125
2147
  init_bounty_gate();
2126
2148
  init_http();
2149
+ init_concurrency();
2127
2150
  GITHUB_API = "https://api.github.com";
2128
2151
  BOUNTY_LABEL_RE = /bounty|reward|funded|💎|💰/i;
2129
2152
  SEARCH_QUERIES = [
@@ -2136,6 +2159,7 @@ var init_github_bounties = __esm({
2136
2159
  MAX_SEARCH_BOUNTIES = 150;
2137
2160
  MAX_SEARCH_ISSUES_SCANNED = 300;
2138
2161
  REPO_META_CONCURRENCY = 15;
2162
+ BOUNTY_FETCH_CONCURRENCY = 6;
2139
2163
  repoMetaCache = /* @__PURE__ */ new Map();
2140
2164
  MAX_PR_PAGES = 3;
2141
2165
  repoOpenPRRefsCache = /* @__PURE__ */ new Map();
@@ -3035,6 +3059,30 @@ var init_profile = __esm({
3035
3059
  }
3036
3060
  });
3037
3061
 
3062
+ // src/open-url.js
3063
+ import { spawn } from "child_process";
3064
+ function openInBrowser(url) {
3065
+ let cmd;
3066
+ let args;
3067
+ if (process.platform === "darwin") {
3068
+ cmd = "open";
3069
+ args = [url];
3070
+ } else if (process.platform === "win32") {
3071
+ cmd = "cmd";
3072
+ args = ["/c", "start", "", url];
3073
+ } else {
3074
+ cmd = "xdg-open";
3075
+ args = [url];
3076
+ }
3077
+ try {
3078
+ const child = spawn(cmd, args, { stdio: "ignore", detached: true });
3079
+ child.on("error", () => {
3080
+ });
3081
+ child.unref();
3082
+ } catch {
3083
+ }
3084
+ }
3085
+
3038
3086
  // bin/jpi-login.js
3039
3087
  async function run() {
3040
3088
  const subcommand = process.argv[2];
@@ -3120,6 +3168,26 @@ async function runLogin() {
3120
3168
  console.log(" Profile updated at ~/.terminalhire/profile.enc (encrypted at rest)");
3121
3169
  console.log(" GitHub data stays on your machine unless you consent to share it in a lead.");
3122
3170
  console.log("");
3171
+ const skipWeb = process.argv.includes("--no-web");
3172
+ if (!isMock && !skipWeb) {
3173
+ try {
3174
+ const OAUTH_BASE = "https://www.terminalhire.com";
3175
+ const webUrl = `${OAUTH_BASE}/api/auth/github?next=/dashboard`;
3176
+ console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
3177
+ console.log(" Your web profile & r\xE9sum\xE9 \u2014 public GitHub data only,");
3178
+ console.log(" your local profile is NOT uploaded.");
3179
+ console.log(` \u2192 ${webUrl}`);
3180
+ if (process.stdout.isTTY) {
3181
+ console.log(" Opening it now to sign you in at terminalhire.com\u2026");
3182
+ openInBrowser(webUrl);
3183
+ } else {
3184
+ console.log(" Open the link above to sign in & view your r\xE9sum\xE9.");
3185
+ }
3186
+ console.log(" (skip next time with: terminalhire login --no-web)");
3187
+ console.log("");
3188
+ } catch {
3189
+ }
3190
+ }
3123
3191
  console.log(" Run `terminalhire jobs` to see matching roles using your enriched profile.");
3124
3192
  console.log("");
3125
3193
  } catch (err) {
@@ -509,6 +509,13 @@ var init_bounty_gate = __esm({
509
509
  }
510
510
  });
511
511
 
512
+ // ../../packages/core/src/concurrency.ts
513
+ var init_concurrency = __esm({
514
+ "../../packages/core/src/concurrency.ts"() {
515
+ "use strict";
516
+ }
517
+ });
518
+
512
519
  // ../../packages/core/src/feeds/github-bounties.ts
513
520
  var init_github_bounties = __esm({
514
521
  "../../packages/core/src/feeds/github-bounties.ts"() {
@@ -517,6 +524,7 @@ var init_github_bounties = __esm({
517
524
  init_entities();
518
525
  init_bounty_gate();
519
526
  init_http();
527
+ init_concurrency();
520
528
  }
521
529
  });
522
530
 
@@ -1640,6 +1640,28 @@ var init_bounty_gate = __esm({
1640
1640
  }
1641
1641
  });
1642
1642
 
1643
+ // ../../packages/core/src/concurrency.ts
1644
+ async function mapWithConcurrency(items, limit, fn) {
1645
+ const results = new Array(items.length);
1646
+ if (items.length === 0) return results;
1647
+ const workers = Math.max(1, Math.min(Math.floor(limit) || 1, items.length));
1648
+ let next = 0;
1649
+ async function run2() {
1650
+ for (; ; ) {
1651
+ const i = next++;
1652
+ if (i >= items.length) return;
1653
+ results[i] = await fn(items[i], i);
1654
+ }
1655
+ }
1656
+ await Promise.all(Array.from({ length: workers }, run2));
1657
+ return results;
1658
+ }
1659
+ var init_concurrency = __esm({
1660
+ "../../packages/core/src/concurrency.ts"() {
1661
+ "use strict";
1662
+ }
1663
+ });
1664
+
1643
1665
  // ../../packages/core/src/feeds/github-bounties.ts
1644
1666
  function authHeaders() {
1645
1667
  const token = process.env["GITHUB_TOKEN"] ?? process.env["GH_TOKEN"];
@@ -1731,7 +1753,7 @@ async function fetchRepoBounties(repoFullName) {
1731
1753
  if (!issues) return [];
1732
1754
  const bounties = issues.filter(isBountyIssue).slice(0, MAX_BOUNTIES_PER_REPO);
1733
1755
  const owner = repo.owner.login;
1734
- return Promise.all(bounties.map(async (issue) => {
1756
+ return mapWithConcurrency(bounties, BOUNTY_FETCH_CONCURRENCY, async (issue) => {
1735
1757
  const title = decodeEntities(issue.title).trim();
1736
1758
  const body = issue.body ? decodeEntities(issue.body) : "";
1737
1759
  const amountUSD = parseAmountUSD(title) ?? parseAmountUSD(body) ?? await fetchCommentAmount(repoFullName, issue.number);
@@ -1760,7 +1782,7 @@ async function fetchRepoBounties(repoFullName) {
1760
1782
  },
1761
1783
  raw: issue
1762
1784
  };
1763
- }));
1785
+ });
1764
1786
  }
1765
1787
  function repoFullNameFromApiUrl(url) {
1766
1788
  const m = url.match(/\/repos\/([^/]+)\/([^/]+)\/?$/);
@@ -1902,7 +1924,7 @@ async function fetchSearchBounties() {
1902
1924
  }
1903
1925
  return jobs;
1904
1926
  }
1905
- var GITHUB_API, BOUNTY_LABEL_RE, SEARCH_QUERIES, SEARCH_PER_PAGE, MAX_SEARCH_BOUNTIES, MAX_SEARCH_ISSUES_SCANNED, REPO_META_CONCURRENCY, repoMetaCache, MAX_PR_PAGES, repoOpenPRRefsCache, issueStateCache, githubBounties;
1927
+ var GITHUB_API, BOUNTY_LABEL_RE, SEARCH_QUERIES, SEARCH_PER_PAGE, MAX_SEARCH_BOUNTIES, MAX_SEARCH_ISSUES_SCANNED, REPO_META_CONCURRENCY, BOUNTY_FETCH_CONCURRENCY, repoMetaCache, MAX_PR_PAGES, repoOpenPRRefsCache, issueStateCache, githubBounties;
1906
1928
  var init_github_bounties = __esm({
1907
1929
  "../../packages/core/src/feeds/github-bounties.ts"() {
1908
1930
  "use strict";
@@ -1910,6 +1932,7 @@ var init_github_bounties = __esm({
1910
1932
  init_entities();
1911
1933
  init_bounty_gate();
1912
1934
  init_http();
1935
+ init_concurrency();
1913
1936
  GITHUB_API = "https://api.github.com";
1914
1937
  BOUNTY_LABEL_RE = /bounty|reward|funded|💎|💰/i;
1915
1938
  SEARCH_QUERIES = [
@@ -1922,6 +1945,7 @@ var init_github_bounties = __esm({
1922
1945
  MAX_SEARCH_BOUNTIES = 150;
1923
1946
  MAX_SEARCH_ISSUES_SCANNED = 300;
1924
1947
  REPO_META_CONCURRENCY = 15;
1948
+ BOUNTY_FETCH_CONCURRENCY = 6;
1925
1949
  repoMetaCache = /* @__PURE__ */ new Map();
1926
1950
  MAX_PR_PAGES = 3;
1927
1951
  repoOpenPRRefsCache = /* @__PURE__ */ new Map();
@@ -509,6 +509,13 @@ var init_bounty_gate = __esm({
509
509
  }
510
510
  });
511
511
 
512
+ // ../../packages/core/src/concurrency.ts
513
+ var init_concurrency = __esm({
514
+ "../../packages/core/src/concurrency.ts"() {
515
+ "use strict";
516
+ }
517
+ });
518
+
512
519
  // ../../packages/core/src/feeds/github-bounties.ts
513
520
  var init_github_bounties = __esm({
514
521
  "../../packages/core/src/feeds/github-bounties.ts"() {
@@ -517,6 +524,7 @@ var init_github_bounties = __esm({
517
524
  init_entities();
518
525
  init_bounty_gate();
519
526
  init_http();
527
+ init_concurrency();
520
528
  }
521
529
  });
522
530
 
@@ -509,6 +509,13 @@ var init_bounty_gate = __esm({
509
509
  }
510
510
  });
511
511
 
512
+ // ../../packages/core/src/concurrency.ts
513
+ var init_concurrency = __esm({
514
+ "../../packages/core/src/concurrency.ts"() {
515
+ "use strict";
516
+ }
517
+ });
518
+
512
519
  // ../../packages/core/src/feeds/github-bounties.ts
513
520
  var init_github_bounties = __esm({
514
521
  "../../packages/core/src/feeds/github-bounties.ts"() {
@@ -517,6 +524,7 @@ var init_github_bounties = __esm({
517
524
  init_entities();
518
525
  init_bounty_gate();
519
526
  init_http();
527
+ init_concurrency();
520
528
  }
521
529
  });
522
530
 
@@ -954,7 +962,32 @@ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSy
954
962
  import { join as join3 } from "path";
955
963
  import { homedir as homedir2, hostname as osHostname } from "os";
956
964
  import { createInterface } from "readline";
965
+
966
+ // src/open-url.js
957
967
  import { spawn } from "child_process";
968
+ function openInBrowser(url) {
969
+ let cmd;
970
+ let args;
971
+ if (process.platform === "darwin") {
972
+ cmd = "open";
973
+ args = [url];
974
+ } else if (process.platform === "win32") {
975
+ cmd = "cmd";
976
+ args = ["/c", "start", "", url];
977
+ } else {
978
+ cmd = "xdg-open";
979
+ args = [url];
980
+ }
981
+ try {
982
+ const child = spawn(cmd, args, { stdio: "ignore", detached: true });
983
+ child.on("error", () => {
984
+ });
985
+ child.unref();
986
+ } catch {
987
+ }
988
+ }
989
+
990
+ // bin/jpi-sync.js
958
991
  var TH_DIR = process.env["TERMINALHIRE_DIR"] || join3(homedir2(), ".terminalhire");
959
992
  var TIER1_MARKER = join3(TH_DIR, "tier1.json");
960
993
  var API_URL = process.env["TERMINALHIRE_API_URL"] || process.env["JPI_API_URL"] || "https://terminalhire.com";
@@ -1030,27 +1063,6 @@ function renderPreview(fields) {
1030
1063
  console.log(" This is NOT required to use terminalhire.");
1031
1064
  console.log("");
1032
1065
  }
1033
- function openInBrowser(url) {
1034
- let cmd;
1035
- let args;
1036
- if (process.platform === "darwin") {
1037
- cmd = "open";
1038
- args = [url];
1039
- } else if (process.platform === "win32") {
1040
- cmd = "cmd";
1041
- args = ["/c", "start", "", url];
1042
- } else {
1043
- cmd = "xdg-open";
1044
- args = [url];
1045
- }
1046
- try {
1047
- const child = spawn(cmd, args, { stdio: "ignore", detached: true });
1048
- child.on("error", () => {
1049
- });
1050
- child.unref();
1051
- } catch {
1052
- }
1053
- }
1054
1066
  function sleep(ms) {
1055
1067
  return new Promise((res) => setTimeout(res, ms));
1056
1068
  }
@@ -0,0 +1,49 @@
1
+ // src/acceptance-score.ts
2
+ var clamp01 = (n) => Math.max(0, Math.min(1, n));
3
+ function scoreDiffAcceptance(input) {
4
+ const reasons = [];
5
+ let score = 0.5;
6
+ const prs = Math.max(0, Math.floor(input.competingOpenPRs));
7
+ if (prs === 0) {
8
+ score += 0.2;
9
+ reasons.push("no competing open PRs (+0.20)");
10
+ } else if (prs === 1) {
11
+ score -= 0.05;
12
+ reasons.push("1 competing open PR (-0.05)");
13
+ } else if (prs === 2) {
14
+ score -= 0.2;
15
+ reasons.push("2 competing open PRs (-0.20)");
16
+ } else {
17
+ score -= 0.35;
18
+ reasons.push(`${prs} competing open PRs \u2014 heavily contested (-0.35)`);
19
+ }
20
+ if (input.filesChanged <= 3 && input.linesChanged <= 150) {
21
+ score += 0.15;
22
+ reasons.push("small, focused diff (+0.15)");
23
+ } else if (input.filesChanged > 15 || input.linesChanged > 800) {
24
+ score -= 0.2;
25
+ reasons.push("large diff \u2014 harder to review/merge (-0.20)");
26
+ } else {
27
+ reasons.push("moderate diff size (0)");
28
+ }
29
+ if (input.touchesTests) {
30
+ score += 0.1;
31
+ reasons.push("includes test changes (+0.10)");
32
+ } else {
33
+ score -= 0.05;
34
+ reasons.push("no test changes (-0.05)");
35
+ }
36
+ if (input.matchesIssueArea === true) {
37
+ score += 0.1;
38
+ reasons.push("touches the issue's referenced files (+0.10)");
39
+ } else if (input.matchesIssueArea === false) {
40
+ score -= 0.1;
41
+ reasons.push("does not touch the issue's referenced files (-0.10)");
42
+ } else {
43
+ reasons.push("issue-area match unknown (0)");
44
+ }
45
+ return { score: Math.round(clamp01(score) * 100) / 100, reasons };
46
+ }
47
+ export {
48
+ scoreDiffAcceptance
49
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "terminalhire",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
4
4
  "description": "Local-first job matching for developers — ambient job matches in the Claude Code spinner. Matching runs on your machine; your profile never leaves it.",
5
5
  "repository": {
6
6
  "type": "git",