terminalhire 0.39.8 → 0.40.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.
package/README.md CHANGED
@@ -60,7 +60,7 @@ terminalhire trajectory --inward # also show private rework/recovery (never exp
60
60
  terminalhire devs # opted-in peers & founders — profile never leaves your machine
61
61
  terminalhire intro <login> # request a double-opt-in intro (typed-yes consent)
62
62
  terminalhire inbox # unread chats, connections & pending intros in one place
63
- terminalhire mcp --print-config # read-only MCP server config for VS Code/Cursor/Codex/Zed/JetBrains
63
+ terminalhire mcp --print-config # local-first match + claim-record MCP config for AI hosts
64
64
 
65
65
  terminalhire profile --show # inspect your encrypted local profile (incl. GitHub fields)
66
66
  terminalhire profile --edit # set displayName, contactEmail, prefs
@@ -0,0 +1,28 @@
1
+ // bin/approved-claims-badge.js
2
+ function normalizeIds(claimIds) {
3
+ if (!Array.isArray(claimIds)) return [];
4
+ return [...new Set(claimIds.filter((id) => typeof id === "string" && id !== ""))].sort();
5
+ }
6
+ function computeApprovedClaims(claimIds, previous) {
7
+ const approved = normalizeIds(claimIds);
8
+ const prior = previous && Array.isArray(previous.acknowledged) ? new Set(previous.acknowledged) : /* @__PURE__ */ new Set();
9
+ const acknowledged = approved.filter((id) => prior.has(id));
10
+ return { count: approved.length - acknowledged.length, acknowledged };
11
+ }
12
+ function acknowledgeApprovedClaim(claimId, previous) {
13
+ const prior = previous && Array.isArray(previous.acknowledged) ? previous.acknowledged : [];
14
+ if (typeof claimId !== "string" || claimId === "") {
15
+ const acknowledged2 = [...new Set(prior)].sort();
16
+ const count2 = previous && typeof previous.count === "number" ? previous.count : 0;
17
+ return { count: count2, acknowledged: acknowledged2 };
18
+ }
19
+ const acknowledged = [.../* @__PURE__ */ new Set([...prior, claimId])].sort();
20
+ const prevCount = previous && typeof previous.count === "number" ? previous.count : 0;
21
+ const alreadyKnown = prior.includes(claimId);
22
+ const count = alreadyKnown ? prevCount : Math.max(0, prevCount - 1);
23
+ return { count, acknowledged };
24
+ }
25
+ export {
26
+ acknowledgeApprovedClaim,
27
+ computeApprovedClaims
28
+ };
@@ -0,0 +1,51 @@
1
+ // bin/approved-claims-sync.js
2
+ var CLAIM_SYNC_BASE = "https://terminalhire.com";
3
+ function approvalsSyncGate({ autoMarkerExists, tokenFileExists }) {
4
+ if (!autoMarkerExists || !tokenFileExists) return { sync: false, reason: "not-opted-in" };
5
+ return { sync: true, reason: "ok" };
6
+ }
7
+ function approvalsNudgeGate({ autoMarkerExists, tokenFileExists, awaitingApproval }) {
8
+ if (approvalsSyncGate({ autoMarkerExists, tokenFileExists }).sync) return false;
9
+ return Number.isInteger(awaitingApproval) && awaitingApproval > 0;
10
+ }
11
+ function buildApprovalsNudge(awaitingApproval) {
12
+ if (!Number.isInteger(awaitingApproval) || awaitingApproval <= 0) return null;
13
+ const n = awaitingApproval;
14
+ return ` \u26A0 ${n} claim${n === 1 ? "" : "s"} awaiting founder approval \u2014 terminalhire cannot check in the background until you enrol:
15
+ terminalhire claim --push --keep-updated (or check one now: terminalhire claim slice <id>)`;
16
+ }
17
+ async function syncApprovedClaims({
18
+ readAutoMarker,
19
+ readPushTokenEnc,
20
+ readPrevious,
21
+ fetchImpl = fetch,
22
+ computeApprovedClaims,
23
+ timeoutMs = 15e3
24
+ } = {}) {
25
+ try {
26
+ const marker = readAutoMarker();
27
+ const token = await readPushTokenEnc();
28
+ if (!approvalsSyncGate({ autoMarkerExists: !!marker, tokenFileExists: !!token }).sync) {
29
+ return null;
30
+ }
31
+ const res = await fetchImpl(`${CLAIM_SYNC_BASE}/api/claim/approvals`, {
32
+ method: "POST",
33
+ headers: { "Content-Type": "application/json" },
34
+ body: JSON.stringify({ pushToken: token }),
35
+ signal: AbortSignal.timeout(timeoutMs)
36
+ });
37
+ if (!res || !res.ok) return null;
38
+ const body = await res.json();
39
+ const claimIds = body && Array.isArray(body.claimIds) ? body.claimIds : null;
40
+ if (!claimIds) return null;
41
+ return computeApprovedClaims(claimIds, readPrevious());
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
46
+ export {
47
+ approvalsNudgeGate,
48
+ approvalsSyncGate,
49
+ buildApprovalsNudge,
50
+ syncApprovedClaims
51
+ };
@@ -92,6 +92,7 @@ var claims_exports = {};
92
92
  __export(claims_exports, {
93
93
  PUSHED_CLAIM_FIELDS: () => PUSHED_CLAIM_FIELDS,
94
94
  acceptedPRRate: () => acceptedPRRate,
95
+ countAwaitingFounderApproval: () => countAwaitingFounderApproval,
95
96
  findClaim: () => findClaim,
96
97
  listClaims: () => listClaims,
97
98
  nextPolledState: () => nextPolledState,
@@ -273,6 +274,15 @@ function removeClaimIfStakeMatches(id, expectedStakePostedAt) {
273
274
  return true;
274
275
  });
275
276
  }
277
+ function countAwaitingFounderApproval(claims = readClaims()) {
278
+ try {
279
+ return claims.filter(
280
+ (c) => c.approval?.mode === "approval-only" && c.approval?.state === "pending"
281
+ ).length;
282
+ } catch {
283
+ return 0;
284
+ }
285
+ }
276
286
  function acceptedPRRate(claims = readClaims()) {
277
287
  const total = claims.length;
278
288
  const merged = claims.filter((c) => c.state === "merged").length;
@@ -0,0 +1,35 @@
1
+ // bin/founder-pin.js
2
+ function isPinnedFounderBounty(j) {
3
+ return j?.bounty?.bountySource === "founder" && j?.bounty?.claimable === true;
4
+ }
5
+
6
+ // bin/founder-paid-badge.js
7
+ function openPaidIds(index) {
8
+ const jobs = index && index.jobs || [];
9
+ const ids = [];
10
+ for (const j of jobs) {
11
+ if (!j || typeof j.id !== "string") continue;
12
+ if (isPinnedFounderBounty(j)) ids.push(j.id);
13
+ }
14
+ return [...new Set(ids)].sort();
15
+ }
16
+ function gate(openIds, seenIds) {
17
+ const seen = new Set(seenIds);
18
+ const acknowledged = openIds.filter((id) => seen.has(id));
19
+ return { count: openIds.length - acknowledged.length, acknowledged };
20
+ }
21
+ function computeFounderPaid(index, previous) {
22
+ const open = openPaidIds(index);
23
+ const prior = previous && previous.acknowledged;
24
+ if (!Array.isArray(prior)) return { count: 0, acknowledged: open };
25
+ return gate(open, prior);
26
+ }
27
+ function acknowledgeFounderPaid({ shown = [], open = [], previous } = {}) {
28
+ const prior = previous && Array.isArray(previous.acknowledged) ? previous.acknowledged : [];
29
+ return gate(openPaidIds({ jobs: open }), [...prior, ...openPaidIds({ jobs: shown })]);
30
+ }
31
+ export {
32
+ acknowledgeFounderPaid,
33
+ computeFounderPaid,
34
+ openPaidIds
35
+ };
@@ -0,0 +1,7 @@
1
+ // bin/founder-pin.js
2
+ function isPinnedFounderBounty(j) {
3
+ return j?.bounty?.bountySource === "founder" && j?.bounty?.claimable === true;
4
+ }
5
+ export {
6
+ isPinnedFounderBounty
7
+ };
@@ -1258,7 +1258,7 @@ async function fetchOwnedRepoTraction(login, token) {
1258
1258
  for (const r of enrichCandidates) {
1259
1259
  r.externalContributors = await repoExternalContributorCount(login, r.name, login, token);
1260
1260
  }
1261
- const gate = qualifiesMaintainer(
1261
+ const gate2 = qualifiesMaintainer(
1262
1262
  ranked.map((r) => ({
1263
1263
  name: r.name,
1264
1264
  stars: r.stars,
@@ -1267,15 +1267,15 @@ async function fetchOwnedRepoTraction(login, token) {
1267
1267
  fork: false
1268
1268
  }))
1269
1269
  );
1270
- const best = gate.qualifies ? ranked.find((r) => r.name === gate.bestRepoName) ?? null : null;
1270
+ const best = gate2.qualifies ? ranked.find((r) => r.name === gate2.bestRepoName) ?? null : null;
1271
1271
  return {
1272
1272
  status: "ok",
1273
1273
  totalStars,
1274
1274
  totalForks,
1275
1275
  reposWithStars,
1276
1276
  top: ranked.slice(0, TRACTION_TOP_N),
1277
- qualifies: gate.qualifies,
1278
- bestRepoName: gate.bestRepoName,
1277
+ qualifies: gate2.qualifies,
1278
+ bestRepoName: gate2.bestRepoName,
1279
1279
  bestRepoExternalContributors: best ? best.externalContributors : null,
1280
1280
  bestRepoStars: best ? best.stars : null,
1281
1281
  computedAt
@@ -1983,6 +1983,7 @@ async function fetchPRScoringFacts(prUrl, token, signal, governor) {
1983
1983
  mergedAt: pr.merged_at ?? null,
1984
1984
  authorId: pr.user?.id ?? null,
1985
1985
  authorLogin: pr.user?.login ?? null,
1986
+ authorAssociation: pr.author_association ?? null,
1986
1987
  mergedById: pr.merged_by?.id ?? null,
1987
1988
  mergedByLogin: pr.merged_by?.login ?? null,
1988
1989
  closesIssues,
@@ -8670,6 +8671,15 @@ function computeEventIndependence(facts) {
8670
8671
  }
8671
8672
  };
8672
8673
  }
8674
+ if (facts.authorAssociation != null && AFFILIATED_AUTHOR_ASSOCIATIONS.has(facts.authorAssociation.toUpperCase())) {
8675
+ return {
8676
+ merger: {
8677
+ party: "merger",
8678
+ independence: "affiliated",
8679
+ reasons: [`PR author is affiliated with the target repo (${facts.authorAssociation})`]
8680
+ }
8681
+ };
8682
+ }
8673
8683
  return {
8674
8684
  merger: {
8675
8685
  party: "merger",
@@ -8700,7 +8710,7 @@ function computeReviewerIndependence(signals) {
8700
8710
  }
8701
8711
  return { party: "reviewer", independence: "unverified", reasons: ["affiliation signal absent (read failed/skipped)"] };
8702
8712
  }
8703
- var PROVENANCE, MS_PER_DAY;
8713
+ var PROVENANCE, MS_PER_DAY, AFFILIATED_AUTHOR_ASSOCIATIONS;
8704
8714
  var init_independence = __esm({
8705
8715
  "../../packages/core/src/credential/independence.ts"() {
8706
8716
  "use strict";
@@ -8715,6 +8725,7 @@ var init_independence = __esm({
8715
8725
  CONTRIB_FLOOR: 5
8716
8726
  };
8717
8727
  MS_PER_DAY = 864e5;
8728
+ AFFILIATED_AUTHOR_ASSOCIATIONS = /* @__PURE__ */ new Set(["OWNER", "MEMBER", "COLLABORATOR"]);
8718
8729
  }
8719
8730
  });
8720
8731
 
@@ -10394,6 +10405,58 @@ var init_state_dir = __esm({
10394
10405
  }
10395
10406
  });
10396
10407
 
10408
+ // bin/cache-store.js
10409
+ var cache_store_exports = {};
10410
+ __export(cache_store_exports, {
10411
+ readCacheEntry: () => readCacheEntry,
10412
+ updateIndexCache: () => updateIndexCache
10413
+ });
10414
+ import { readFileSync as readFileSync2, writeFileSync, renameSync } from "fs";
10415
+ import { join as join2 } from "path";
10416
+ import { homedir } from "os";
10417
+ function readCacheEntry() {
10418
+ try {
10419
+ return JSON.parse(readFileSync2(INDEX_CACHE_FILE, "utf8"));
10420
+ } catch {
10421
+ return null;
10422
+ }
10423
+ }
10424
+ function updateIndexCache(patch) {
10425
+ ensureStateDir(TERMINALHIRE_DIR);
10426
+ const existing = readCacheEntry() ?? {};
10427
+ const entry = {
10428
+ ...existing,
10429
+ ...patch,
10430
+ schemaVersion: SCHEMA_VERSION2,
10431
+ ts: Date.now()
10432
+ };
10433
+ const tmp = `${INDEX_CACHE_FILE}.${process.pid}.${tmpCounter++}.tmp`;
10434
+ writeFileSync(tmp, JSON.stringify(entry), "utf8");
10435
+ renameSync(tmp, INDEX_CACHE_FILE);
10436
+ return entry;
10437
+ }
10438
+ var TERMINALHIRE_DIR, INDEX_CACHE_FILE, SCHEMA_VERSION2, tmpCounter;
10439
+ var init_cache_store = __esm({
10440
+ "bin/cache-store.js"() {
10441
+ "use strict";
10442
+ init_state_dir();
10443
+ TERMINALHIRE_DIR = process.env.TERMINALHIRE_DIR || join2(homedir(), ".terminalhire");
10444
+ INDEX_CACHE_FILE = join2(TERMINALHIRE_DIR, "index-cache.json");
10445
+ SCHEMA_VERSION2 = 1;
10446
+ tmpCounter = 0;
10447
+ }
10448
+ });
10449
+
10450
+ // bin/founder-pin.js
10451
+ function isPinnedFounderBounty(j) {
10452
+ return j?.bounty?.bountySource === "founder" && j?.bounty?.claimable === true;
10453
+ }
10454
+ var init_founder_pin = __esm({
10455
+ "bin/founder-pin.js"() {
10456
+ "use strict";
10457
+ }
10458
+ });
10459
+
10397
10460
  // src/test-race-barrier.ts
10398
10461
  import { closeSync as closeSync2, constants as constants2, existsSync, lstatSync, openSync as openSync2 } from "fs";
10399
10462
  import { join as join3 } from "path";
@@ -11061,6 +11124,7 @@ var claims_exports = {};
11061
11124
  __export(claims_exports, {
11062
11125
  PUSHED_CLAIM_FIELDS: () => PUSHED_CLAIM_FIELDS,
11063
11126
  acceptedPRRate: () => acceptedPRRate,
11127
+ countAwaitingFounderApproval: () => countAwaitingFounderApproval,
11064
11128
  findClaim: () => findClaim,
11065
11129
  listClaims: () => listClaims,
11066
11130
  nextPolledState: () => nextPolledState,
@@ -11242,6 +11306,15 @@ function removeClaimIfStakeMatches(id, expectedStakePostedAt) {
11242
11306
  return true;
11243
11307
  });
11244
11308
  }
11309
+ function countAwaitingFounderApproval(claims = readClaims()) {
11310
+ try {
11311
+ return claims.filter(
11312
+ (c) => c.approval?.mode === "approval-only" && c.approval?.state === "pending"
11313
+ ).length;
11314
+ } catch {
11315
+ return 0;
11316
+ }
11317
+ }
11245
11318
  function acceptedPRRate(claims = readClaims()) {
11246
11319
  const total = claims.length;
11247
11320
  const merged = claims.filter((c) => c.state === "merged").length;
@@ -11290,44 +11363,52 @@ var init_claims = __esm({
11290
11363
  }
11291
11364
  });
11292
11365
 
11366
+ // bin/founder-paid-badge.js
11367
+ var founder_paid_badge_exports = {};
11368
+ __export(founder_paid_badge_exports, {
11369
+ acknowledgeFounderPaid: () => acknowledgeFounderPaid,
11370
+ computeFounderPaid: () => computeFounderPaid,
11371
+ openPaidIds: () => openPaidIds
11372
+ });
11373
+ function openPaidIds(index) {
11374
+ const jobs = index && index.jobs || [];
11375
+ const ids = [];
11376
+ for (const j of jobs) {
11377
+ if (!j || typeof j.id !== "string") continue;
11378
+ if (isPinnedFounderBounty(j)) ids.push(j.id);
11379
+ }
11380
+ return [...new Set(ids)].sort();
11381
+ }
11382
+ function gate(openIds, seenIds) {
11383
+ const seen = new Set(seenIds);
11384
+ const acknowledged = openIds.filter((id) => seen.has(id));
11385
+ return { count: openIds.length - acknowledged.length, acknowledged };
11386
+ }
11387
+ function computeFounderPaid(index, previous) {
11388
+ const open = openPaidIds(index);
11389
+ const prior = previous && previous.acknowledged;
11390
+ if (!Array.isArray(prior)) return { count: 0, acknowledged: open };
11391
+ return gate(open, prior);
11392
+ }
11393
+ function acknowledgeFounderPaid({ shown = [], open = [], previous } = {}) {
11394
+ const prior = previous && Array.isArray(previous.acknowledged) ? previous.acknowledged : [];
11395
+ return gate(openPaidIds({ jobs: open }), [...prior, ...openPaidIds({ jobs: shown })]);
11396
+ }
11397
+ var init_founder_paid_badge = __esm({
11398
+ "bin/founder-paid-badge.js"() {
11399
+ "use strict";
11400
+ init_founder_pin();
11401
+ }
11402
+ });
11403
+
11293
11404
  // bin/jpi-bounties.js
11294
11405
  init_src();
11406
+ init_cache_store();
11295
11407
  import { readFileSync as readFileSync6 } from "fs";
11296
11408
  import { join as join9 } from "path";
11297
11409
  import { homedir as homedir6 } from "os";
11298
11410
  import { createInterface } from "readline";
11299
11411
 
11300
- // bin/cache-store.js
11301
- init_state_dir();
11302
- import { readFileSync as readFileSync2, writeFileSync, renameSync } from "fs";
11303
- import { join as join2 } from "path";
11304
- import { homedir } from "os";
11305
- var TERMINALHIRE_DIR = process.env.TERMINALHIRE_DIR || join2(homedir(), ".terminalhire");
11306
- var INDEX_CACHE_FILE = join2(TERMINALHIRE_DIR, "index-cache.json");
11307
- var SCHEMA_VERSION2 = 1;
11308
- var tmpCounter = 0;
11309
- function readCacheEntry() {
11310
- try {
11311
- return JSON.parse(readFileSync2(INDEX_CACHE_FILE, "utf8"));
11312
- } catch {
11313
- return null;
11314
- }
11315
- }
11316
- function updateIndexCache(patch) {
11317
- ensureStateDir(TERMINALHIRE_DIR);
11318
- const existing = readCacheEntry() ?? {};
11319
- const entry = {
11320
- ...existing,
11321
- ...patch,
11322
- schemaVersion: SCHEMA_VERSION2,
11323
- ts: Date.now()
11324
- };
11325
- const tmp = `${INDEX_CACHE_FILE}.${process.pid}.${tmpCounter++}.tmp`;
11326
- writeFileSync(tmp, JSON.stringify(entry), "utf8");
11327
- renameSync(tmp, INDEX_CACHE_FILE);
11328
- return entry;
11329
- }
11330
-
11331
11412
  // bin/sanitize.js
11332
11413
  var CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
11333
11414
  function sanitizeText(s) {
@@ -11360,6 +11441,7 @@ function linkTitle(title, url) {
11360
11441
  }
11361
11442
 
11362
11443
  // bin/jpi-bounties.js
11444
+ init_founder_pin();
11363
11445
  var TERMINALHIRE_DIR6 = process.env.TERMINALHIRE_DIR || join9(homedir6(), ".terminalhire");
11364
11446
  var INDEX_CACHE_FILE2 = join9(TERMINALHIRE_DIR6, "index-cache.json");
11365
11447
  var INDEX_TTL_MS = 15 * 60 * 1e3;
@@ -11438,6 +11520,7 @@ ${i + 1}. ${linkTitle(job.title, job.url)} [${ref}]`);
11438
11520
  }
11439
11521
  function rankBounties(bounties, { rankMode = "winnability", scoreOf = () => 0 } = {}) {
11440
11522
  const legacy = rankMode === "legacy";
11523
+ const pin = (j) => isPinnedFounderBounty(j) ? 1 : 0;
11441
11524
  const contested = (j) => (j.bounty?.competingOpenPRs ?? 0) > 0 ? 1 : 0;
11442
11525
  const amt = (j) => j.bounty?.amountUSD ?? -1;
11443
11526
  const winTier = (j) => {
@@ -11447,12 +11530,13 @@ function rankBounties(bounties, { rankMode = "winnability", scoreOf = () => 0 }
11447
11530
  };
11448
11531
  const winVal = (j) => j.winnabilityScore ?? 0;
11449
11532
  bounties.sort(
11450
- (a, b) => (legacy ? 0 : winTier(b) - winTier(a)) || (legacy ? 0 : winVal(b) - winVal(a)) || contested(a) - contested(b) || scoreOf(b) - scoreOf(a) || amt(b) - amt(a)
11533
+ (a, b) => pin(b) - pin(a) || (legacy ? 0 : winTier(b) - winTier(a)) || (legacy ? 0 : winVal(b) - winVal(a)) || contested(a) - contested(b) || scoreOf(b) - scoreOf(a) || amt(b) - amt(a)
11451
11534
  );
11452
11535
  return bounties;
11453
11536
  }
11454
11537
  function applyContinuityRank(bounties, continuityOf, { enabled = true } = {}) {
11455
11538
  if (!enabled) return bounties;
11539
+ const pin = (j) => isPinnedFounderBounty(j) ? 1 : 0;
11456
11540
  const winTier = (j) => {
11457
11541
  const s = j.winnabilityScore;
11458
11542
  if (s == null) return 1;
@@ -11464,7 +11548,9 @@ function applyContinuityRank(bounties, continuityOf, { enabled = true } = {}) {
11464
11548
  const c = repo ? continuityOf(repo) || 0 : 0;
11465
11549
  return s * (1 + 0.15 * c);
11466
11550
  };
11467
- bounties.sort((a, b) => winTier(b) - winTier(a) || effectiveVal(b) - effectiveVal(a));
11551
+ bounties.sort(
11552
+ (a, b) => pin(b) - pin(a) || winTier(b) - winTier(a) || effectiveVal(b) - effectiveVal(a)
11553
+ );
11468
11554
  return bounties;
11469
11555
  }
11470
11556
  function continuityNoteForRow(entry, isTTY) {
@@ -11575,6 +11661,18 @@ async function run() {
11575
11661
  continuityNote
11576
11662
  );
11577
11663
  }
11664
+ try {
11665
+ const { acknowledgeFounderPaid: acknowledgeFounderPaid2 } = await Promise.resolve().then(() => (init_founder_paid_badge(), founder_paid_badge_exports));
11666
+ const { readCacheEntry: readCacheEntry2 } = await Promise.resolve().then(() => (init_cache_store(), cache_store_exports));
11667
+ updateIndexCache({
11668
+ founderPaid: acknowledgeFounderPaid2({
11669
+ shown,
11670
+ open: bounties,
11671
+ previous: (readCacheEntry2() ?? {}).founderPaid
11672
+ })
11673
+ });
11674
+ } catch {
11675
+ }
11578
11676
  if (!SHOW_ALL && bounties.length > shown.length) {
11579
11677
  console.log(
11580
11678
  `
@@ -11606,6 +11704,7 @@ export {
11606
11704
  continuityNoteForRow,
11607
11705
  filterPaidVisibility,
11608
11706
  getBounties,
11707
+ isPinnedFounderBounty,
11609
11708
  printBounty,
11610
11709
  rankBounties,
11611
11710
  run