terminalhire 0.22.0 → 0.23.0

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.
@@ -45,9 +45,9 @@ var init_keytar = __esm({
45
45
  }
46
46
  });
47
47
 
48
- // node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/claim-frictionless/node_modules/keytar/build/Release/keytar.node
48
+ // node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/91995792-9cff-48f4-ae9c-dee9e36fc319/scratchpad/release-v0230/node_modules/keytar/build/Release/keytar.node
49
49
  var require_keytar = __commonJS({
50
- "node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/claim-frictionless/node_modules/keytar/build/Release/keytar.node"(exports, module) {
50
+ "node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/91995792-9cff-48f4-ae9c-dee9e36fc319/scratchpad/release-v0230/node_modules/keytar/build/Release/keytar.node"(exports, module) {
51
51
  "use strict";
52
52
  init_keytar();
53
53
  try {
@@ -749,6 +749,30 @@ var init_contribution_gate = __esm({
749
749
  }
750
750
  });
751
751
 
752
+ // ../../packages/core/src/credential/rigor.ts
753
+ function deriveRigorTiers(input) {
754
+ const tiers = {};
755
+ if (input.reviewerAssociations !== void 0) {
756
+ tiers.maintainerReviewed = input.reviewerAssociations.some(
757
+ (a) => MAINTAINER_SET.has(String(a).toUpperCase())
758
+ );
759
+ }
760
+ return tiers;
761
+ }
762
+ var RIGOR, MAINTAINER_SET;
763
+ var init_rigor = __esm({
764
+ "../../packages/core/src/credential/rigor.ts"() {
765
+ "use strict";
766
+ RIGOR = {
767
+ /** `authorAssociation` values that count as a maintainer review. */
768
+ MAINTAINER_ASSOCIATIONS: ["OWNER", "MEMBER", "COLLABORATOR"]
769
+ };
770
+ MAINTAINER_SET = new Set(
771
+ RIGOR.MAINTAINER_ASSOCIATIONS.map((a) => a.toUpperCase())
772
+ );
773
+ }
774
+ });
775
+
752
776
  // ../../packages/core/src/github.ts
753
777
  function ghHeaders(token) {
754
778
  const headers = {
@@ -1027,6 +1051,33 @@ async function computeAcceptanceFromSearch(login, token, ownedOrgs, cache, gates
1027
1051
  if (mergedAt > b.lastMergedAt) b.lastMergedAt = mergedAt;
1028
1052
  }
1029
1053
  }
1054
+ if (token) {
1055
+ const enrichStats = { transient: 0 };
1056
+ const enrichCount = Math.min(qualifyingPRs.length, MAX_ENRICH_PRS);
1057
+ for (let i = 0; i < enrichCount; i++) {
1058
+ const pr = qualifyingPRs[i];
1059
+ const ref = parseGitHubRef(pr.url);
1060
+ if (!ref || ref.kind !== "pull") continue;
1061
+ try {
1062
+ const reviews = await ghFetch(
1063
+ `/repos/${ref.owner}/${ref.repo}/pulls/${ref.number}/reviews?per_page=100`,
1064
+ token
1065
+ );
1066
+ const reviewerAssociations = reviews.map((r) => r.author_association);
1067
+ const tiers = deriveRigorTiers({ reviewerAssociations });
1068
+ if (tiers.maintainerReviewed !== void 0) pr.maintainerReviewed = tiers.maintainerReviewed;
1069
+ } catch (err) {
1070
+ const msg = err instanceof Error ? err.message : String(err);
1071
+ if (TRANSIENT_META_ERROR.test(msg)) {
1072
+ enrichStats.transient += 1;
1073
+ console.warn(
1074
+ `[acceptance] ${login}: per-PR rigor enrichment transient failure (${enrichStats.transient}) \u2014 leaving remaining tiers undefined rather than fabricating a false`
1075
+ );
1076
+ break;
1077
+ }
1078
+ }
1079
+ }
1080
+ }
1030
1081
  const finalDomains = {};
1031
1082
  for (const [d, b] of Object.entries(byDomain)) {
1032
1083
  finalDomains[d] = {
@@ -1233,6 +1284,17 @@ async function fetchPRScoringFacts(prUrl, token, signal) {
1233
1284
  }
1234
1285
  const contributors = await repoContributorCount(owner, repo, token, sig);
1235
1286
  const { closesIssues, linkageSource } = await resolveClosingIssues(owner, repo, number, pr.body ?? "", token, sig);
1287
+ let reviewerAssociations;
1288
+ try {
1289
+ const reviews = await ghFetch(
1290
+ `/repos/${owner}/${repo}/pulls/${number}/reviews?per_page=100`,
1291
+ token,
1292
+ sig
1293
+ );
1294
+ reviewerAssociations = reviews.map((r) => r.author_association);
1295
+ } catch {
1296
+ reviewerAssociations = void 0;
1297
+ }
1236
1298
  return {
1237
1299
  repo: `${owner}/${repo}`,
1238
1300
  prNumber: number,
@@ -1250,17 +1312,24 @@ async function fetchPRScoringFacts(prUrl, token, signal) {
1250
1312
  repoArchived: !!repoMeta?.archived,
1251
1313
  repoFork: !!repoMeta?.fork,
1252
1314
  repoPrivate: !!repoMeta?.private,
1315
+ additions: pr.additions ?? null,
1316
+ deletions: pr.deletions ?? null,
1317
+ changedFiles: pr.changed_files ?? null,
1318
+ repoForks: repoMeta?.forks_count ?? null,
1319
+ reviewerAssociations,
1253
1320
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
1254
1321
  };
1255
1322
  }
1256
- var TRACTION_TOP_N, CANDIDATE_PR_PAGE, OPEN_PR_PAGE, TRANSIENT_META_ERROR, RESUME_DECAY_HALF_LIFE_MS, RESUME_MIN_SCORE;
1323
+ var TRACTION_TOP_N, CANDIDATE_PR_PAGE, MAX_ENRICH_PRS, OPEN_PR_PAGE, TRANSIENT_META_ERROR, RESUME_DECAY_HALF_LIFE_MS, RESUME_MIN_SCORE;
1257
1324
  var init_github = __esm({
1258
1325
  "../../packages/core/src/github.ts"() {
1259
1326
  "use strict";
1260
1327
  init_vocabulary();
1261
1328
  init_contribution_gate();
1329
+ init_rigor();
1262
1330
  TRACTION_TOP_N = 6;
1263
1331
  CANDIDATE_PR_PAGE = 50;
1332
+ MAX_ENRICH_PRS = 12;
1264
1333
  OPEN_PR_PAGE = 20;
1265
1334
  TRANSIENT_META_ERROR = /HTTP 403|HTTP 429|rate limit|HTTP 5\d\d|timeout|network|fetch failed/i;
1266
1335
  RESUME_DECAY_HALF_LIFE_MS = 30 * 24 * 60 * 60 * 1e3;
@@ -6772,10 +6841,13 @@ function deriveLegibleProfile(credential, recency, traction, seniorityBand) {
6772
6841
  if (daysAgo !== void 0) s += ` \u2014 most recent ${daysAgo}d ago`;
6773
6842
  proofSentence = `${s}.`;
6774
6843
  }
6844
+ const enrichedPRs = ok ? credential.qualifyingPRs ?? [] : [];
6845
+ const maintainerReviewedCount = enrichedPRs.some((p) => p.maintainerReviewed !== void 0) ? enrichedPRs.filter((p) => p.maintainerReviewed === true).length : void 0;
6775
6846
  const auditableBadge = ok ? {
6776
6847
  mergedTotal: credential.qualifyingTotal,
6777
6848
  distinctOrgs: orgCount,
6778
- thresholds: { stars: MIN_STARS, contributors: MIN_CONTRIBUTORS }
6849
+ thresholds: { stars: MIN_STARS, contributors: MIN_CONTRIBUTORS },
6850
+ ...maintainerReviewedCount !== void 0 ? { maintainerReviewedCount } : {}
6779
6851
  } : null;
6780
6852
  const profile = {
6781
6853
  headline,
@@ -6971,6 +7043,7 @@ __export(src_exports, {
6971
7043
  MENTION_DELTA: () => MENTION_DELTA,
6972
7044
  MIN_CONTRIBUTORS: () => MIN_CONTRIBUTORS,
6973
7045
  MIN_STARS: () => MIN_STARS,
7046
+ RIGOR: () => RIGOR,
6974
7047
  STRONG_MATCH_THRESHOLD: () => STRONG_MATCH_THRESHOLD,
6975
7048
  SYNONYMS: () => SYNONYMS,
6976
7049
  TRIVIAL_PR_TITLE: () => TRIVIAL_PR_TITLE,
@@ -6999,6 +7072,7 @@ __export(src_exports, {
6999
7072
  decryptMessage: () => decryptMessage,
7000
7073
  deriveLegibleProfile: () => deriveLegibleProfile,
7001
7074
  deriveResumeTrend: () => deriveResumeTrend,
7075
+ deriveRigorTiers: () => deriveRigorTiers,
7002
7076
  deriveSharedKey: () => deriveSharedKey,
7003
7077
  deriveTrajectoryNarrative: () => deriveTrajectoryNarrative,
7004
7078
  displayableDrift: () => displayableDrift,
@@ -7078,6 +7152,7 @@ var init_src = __esm({
7078
7152
  init_job_status();
7079
7153
  init_legible();
7080
7154
  init_legible_trajectory();
7155
+ init_rigor();
7081
7156
  init_short_token();
7082
7157
  }
7083
7158
  });
@@ -7090,9 +7165,9 @@ var init_keytar = __esm({
7090
7165
  }
7091
7166
  });
7092
7167
 
7093
- // node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/claim-frictionless/node_modules/keytar/build/Release/keytar.node
7168
+ // node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/91995792-9cff-48f4-ae9c-dee9e36fc319/scratchpad/release-v0230/node_modules/keytar/build/Release/keytar.node
7094
7169
  var require_keytar = __commonJS({
7095
- "node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/claim-frictionless/node_modules/keytar/build/Release/keytar.node"(exports, module) {
7170
+ "node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/91995792-9cff-48f4-ae9c-dee9e36fc319/scratchpad/release-v0230/node_modules/keytar/build/Release/keytar.node"(exports, module) {
7096
7171
  "use strict";
7097
7172
  init_keytar();
7098
7173
  try {
@@ -7512,36 +7587,46 @@ ${i + 1}. ${linkTitle(job.title, job.url)} [${ref}]`);
7512
7587
  console.log(` Claim: ${sanitizeText(b.claimUrl ?? job.url)}`);
7513
7588
  console.log(` \u2192 terminalhire claim ${ref}`);
7514
7589
  }
7590
+ async function getBounties({ quiet = false, offline = false } = {}) {
7591
+ if (!quiet) console.log(`Fetching bounty index from ${API_URL}/api/index...`);
7592
+ const index = offline ? readIndexCache() : await fetchIndex();
7593
+ if (offline && !index) return { status: "no-cache" };
7594
+ let bounties = (index.jobs ?? []).filter((j) => j.source === "bounty");
7595
+ if (PRICED_ONLY) bounties = bounties.filter((j) => j.bounty?.amountUSD != null);
7596
+ if (WINNABLE_ONLY) bounties = bounties.filter((j) => (j.bounty?.competingOpenPRs ?? 0) === 0);
7597
+ if (bounties.length === 0) {
7598
+ return { status: "empty" };
7599
+ }
7600
+ const ranked = /* @__PURE__ */ new Map();
7601
+ try {
7602
+ const { readProfile: readProfile2, profileToFingerprint: profileToFingerprint2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
7603
+ const { match: match2 } = await Promise.resolve().then(() => (init_src(), src_exports));
7604
+ const profile = await readProfile2();
7605
+ if (profile.skillTags.length > 0) {
7606
+ const fp = profileToFingerprint2(profile);
7607
+ for (const r of match2(fp, bounties, bounties.length)) {
7608
+ ranked.set(r.job.id, { score: r.score, reason: r.reason, matchedTags: r.matchedTags });
7609
+ }
7610
+ }
7611
+ } catch {
7612
+ }
7613
+ const score = (j) => ranked.get(j.id)?.score ?? 0;
7614
+ const amt = (j) => j.bounty?.amountUSD ?? -1;
7615
+ const contested = (j) => (j.bounty?.competingOpenPRs ?? 0) > 0 ? 1 : 0;
7616
+ bounties.sort((a, b) => contested(a) - contested(b) || score(b) - score(a) || amt(b) - amt(a));
7617
+ const matchedCount = bounties.filter((j) => score(j) > 0).length;
7618
+ return { status: "ok", bounties, ranked, matchedCount };
7619
+ }
7515
7620
  async function run() {
7516
7621
  try {
7517
- console.log(`Fetching bounty index from ${API_URL}/api/index...`);
7518
- const index = await fetchIndex();
7519
- let bounties = (index.jobs ?? []).filter((j) => j.source === "bounty");
7520
- if (PRICED_ONLY) bounties = bounties.filter((j) => j.bounty?.amountUSD != null);
7521
- if (WINNABLE_ONLY) bounties = bounties.filter((j) => (j.bounty?.competingOpenPRs ?? 0) === 0);
7522
- if (bounties.length === 0) {
7622
+ const result = await getBounties();
7623
+ if (result.status === "empty") {
7523
7624
  console.log("\nNo bounties available right now. Try again later \u2014 supply refreshes through the day.");
7524
7625
  return;
7525
7626
  }
7526
- const ranked = /* @__PURE__ */ new Map();
7527
- try {
7528
- const { readProfile: readProfile2, profileToFingerprint: profileToFingerprint2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
7529
- const { match: match2 } = await Promise.resolve().then(() => (init_src(), src_exports));
7530
- const profile = await readProfile2();
7531
- if (profile.skillTags.length > 0) {
7532
- const fp = profileToFingerprint2(profile);
7533
- for (const r of match2(fp, bounties, bounties.length)) {
7534
- ranked.set(r.job.id, { score: r.score, reason: r.reason, matchedTags: r.matchedTags });
7535
- }
7536
- }
7537
- } catch {
7538
- }
7539
- const score = (j) => ranked.get(j.id)?.score ?? 0;
7540
- const amt = (j) => j.bounty?.amountUSD ?? -1;
7541
- const contested = (j) => (j.bounty?.competingOpenPRs ?? 0) > 0 ? 1 : 0;
7542
- bounties.sort((a, b) => contested(a) - contested(b) || score(b) - score(a) || amt(b) - amt(a));
7627
+ if (result.status !== "ok") return;
7628
+ const { bounties, ranked, matchedCount } = result;
7543
7629
  const shown = SHOW_ALL ? bounties : bounties.slice(0, LIMIT);
7544
- const matchedCount = bounties.filter((j) => score(j) > 0).length;
7545
7630
  console.log(
7546
7631
  `
7547
7632
  \u26A1 ${bounties.length} bount${bounties.length === 1 ? "y" : "ies"} you could knock out` + (matchedCount ? ` \u2014 ${matchedCount} matched to your profile` : "") + ` (local rank \u2014 no data sent)
@@ -7573,6 +7658,7 @@ Open this to claim/work the bounty (you go straight to the source \u2014 we neve
7573
7658
  }
7574
7659
  }
7575
7660
  export {
7661
+ getBounties,
7576
7662
  run
7577
7663
  };
7578
7664
  /*! Bundled license information:
@@ -462,6 +462,21 @@ var init_contribution_gate = __esm({
462
462
  }
463
463
  });
464
464
 
465
+ // ../../packages/core/src/credential/rigor.ts
466
+ var RIGOR, MAINTAINER_SET;
467
+ var init_rigor = __esm({
468
+ "../../packages/core/src/credential/rigor.ts"() {
469
+ "use strict";
470
+ RIGOR = {
471
+ /** `authorAssociation` values that count as a maintainer review. */
472
+ MAINTAINER_ASSOCIATIONS: ["OWNER", "MEMBER", "COLLABORATOR"]
473
+ };
474
+ MAINTAINER_SET = new Set(
475
+ RIGOR.MAINTAINER_ASSOCIATIONS.map((a) => a.toUpperCase())
476
+ );
477
+ }
478
+ });
479
+
465
480
  // ../../packages/core/src/github.ts
466
481
  var RESUME_DECAY_HALF_LIFE_MS;
467
482
  var init_github = __esm({
@@ -469,6 +484,7 @@ var init_github = __esm({
469
484
  "use strict";
470
485
  init_vocabulary();
471
486
  init_contribution_gate();
487
+ init_rigor();
472
488
  RESUME_DECAY_HALF_LIFE_MS = 30 * 24 * 60 * 60 * 1e3;
473
489
  }
474
490
  });
@@ -3963,6 +3979,7 @@ var init_src = __esm({
3963
3979
  init_job_status();
3964
3980
  init_legible();
3965
3981
  init_legible_trajectory();
3982
+ init_rigor();
3966
3983
  init_short_token();
3967
3984
  }
3968
3985
  });
@@ -3975,9 +3992,9 @@ var init_keytar = __esm({
3975
3992
  }
3976
3993
  });
3977
3994
 
3978
- // node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/claim-frictionless/node_modules/keytar/build/Release/keytar.node
3995
+ // node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/91995792-9cff-48f4-ae9c-dee9e36fc319/scratchpad/release-v0230/node_modules/keytar/build/Release/keytar.node
3979
3996
  var require_keytar = __commonJS({
3980
- "node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/claim-frictionless/node_modules/keytar/build/Release/keytar.node"(exports, module) {
3997
+ "node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/91995792-9cff-48f4-ae9c-dee9e36fc319/scratchpad/release-v0230/node_modules/keytar/build/Release/keytar.node"(exports, module) {
3981
3998
  "use strict";
3982
3999
  init_keytar();
3983
4000
  try {
@@ -4439,14 +4456,62 @@ var init_config = __esm({
4439
4456
  }
4440
4457
  });
4441
4458
 
4459
+ // bin/tui-core.js
4460
+ function sanitizeLine(text) {
4461
+ return String(text).replace(ANSI_CSI, "").replace(ANSI_OSC, "").replace(ANSI_OTHER, "").replace(C0_C1_DEL, "");
4462
+ }
4463
+ var ANSI_CSI, ANSI_OSC, ANSI_OTHER, C0_C1_DEL, PALETTE, COLOR_ROLES;
4464
+ var init_tui_core = __esm({
4465
+ "bin/tui-core.js"() {
4466
+ "use strict";
4467
+ ANSI_CSI = /\x1b\[[0-?]*[ -/]*[@-~]/g;
4468
+ ANSI_OSC = /\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g;
4469
+ ANSI_OTHER = /\x1b[@-_]/g;
4470
+ C0_C1_DEL = /[\x00-\x1f\x7f-\x9f]/g;
4471
+ PALETTE = {
4472
+ truecolor: {
4473
+ bg: "\x1B[48;2;13;17;23m",
4474
+ panel: "\x1B[48;2;22;27;34m",
4475
+ rule: "\x1B[38;2;48;54;61m",
4476
+ text: "\x1B[38;2;201;209;217m",
4477
+ muted: "\x1B[38;2;125;133;144m",
4478
+ accent: "\x1B[38;2;88;166;255m",
4479
+ "accent-bright": "\x1B[38;2;121;192;255m",
4480
+ green: "\x1B[38;2;63;185;80m",
4481
+ amber: "\x1B[38;2;210;153;34m"
4482
+ },
4483
+ 256: {
4484
+ bg: "\x1B[48;5;233m",
4485
+ panel: "\x1B[48;5;235m",
4486
+ rule: "\x1B[38;5;240m",
4487
+ text: "\x1B[38;5;252m",
4488
+ muted: "\x1B[38;5;245m",
4489
+ accent: "\x1B[38;5;75m",
4490
+ "accent-bright": "\x1B[38;5;117m",
4491
+ green: "\x1B[38;5;71m",
4492
+ amber: "\x1B[38;5;178m"
4493
+ },
4494
+ 16: {
4495
+ bg: "\x1B[40m",
4496
+ panel: "\x1B[100m",
4497
+ rule: "\x1B[90m",
4498
+ text: "\x1B[37m",
4499
+ muted: "\x1B[90m",
4500
+ accent: "\x1B[94m",
4501
+ "accent-bright": "\x1B[96m",
4502
+ green: "\x1B[92m",
4503
+ amber: "\x1B[93m"
4504
+ }
4505
+ };
4506
+ COLOR_ROLES = Object.keys(PALETTE.truecolor);
4507
+ }
4508
+ });
4509
+
4442
4510
  // bin/jpi-chat.js
4443
4511
  import { createInterface } from "readline";
4444
4512
  import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
4445
4513
  import { homedir as homedir6 } from "os";
4446
4514
  import { join as join7 } from "path";
4447
- function sanitizeLine(text) {
4448
- return String(text).replace(ANSI_CSI, "").replace(ANSI_OSC, "").replace(ANSI_OTHER, "").replace(C0_C1_DEL, "");
4449
- }
4450
4515
  function defaultPromptAck({ input = process.stdin, output = process.stdout } = {}) {
4451
4516
  if (!input || input.isTTY !== true) return Promise.resolve(false);
4452
4517
  const rl = createInterface({ input, output });
@@ -4565,19 +4630,16 @@ function formatPresence(presence, now = /* @__PURE__ */ new Date()) {
4565
4630
  }
4566
4631
  return "\u25D0 reachable";
4567
4632
  }
4568
- var CHAT_BASE2, GH_SESSION_COOKIE2, ANSI_CSI, ANSI_OSC, ANSI_OTHER, C0_C1_DEL, CHAT_DISCLOSURE, CHAT_AT_REST, CHAT_CODE_OF_CONDUCT, CHAT_MIN_AGE, ACTIVE_WINDOW_MS;
4633
+ var CHAT_BASE2, GH_SESSION_COOKIE2, CHAT_DISCLOSURE, CHAT_AT_REST, CHAT_CODE_OF_CONDUCT, CHAT_MIN_AGE, ACTIVE_WINDOW_MS;
4569
4634
  var init_jpi_chat = __esm({
4570
4635
  "bin/jpi-chat.js"() {
4571
4636
  "use strict";
4572
4637
  init_chat_client();
4573
4638
  init_config();
4574
4639
  init_web_session();
4640
+ init_tui_core();
4575
4641
  CHAT_BASE2 = process.env["TERMINALHIRE_API_URL"] || "https://terminalhire.com";
4576
4642
  GH_SESSION_COOKIE2 = "__jpi_gh_session";
4577
- ANSI_CSI = /\x1b\[[0-?]*[ -/]*[@-~]/g;
4578
- ANSI_OSC = /\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g;
4579
- ANSI_OTHER = /\x1b[@-_]/g;
4580
- C0_C1_DEL = /[\x00-\x1f\x7f-\x9f]/g;
4581
4643
  CHAT_DISCLOSURE = "Messages are end-to-end encrypted using keys stored only on your device. Our server cannot read message content. Since we distribute your contact's public key, verify your connection by comparing Safety Numbers to rule out a server-side substitution. We store metadata: who messaged whom, when, and message count. Content is purged after 90 days.";
4582
4644
  CHAT_AT_REST = "Your private key is encrypted against casual access, not full machine compromise.";
4583
4645
  CHAT_CODE_OF_CONDUCT = "Code of conduct: keep it professional \u2014 harassment, spam, or abuse gets you blocked and removed.";