terminalhire 0.32.0 → 0.33.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/dist/bin/jpi-bounties.js +145 -11
- package/dist/bin/jpi-claim.js +1 -1
- package/dist/bin/jpi-contribute.js +142 -11
- package/dist/bin/jpi-devs.js +1 -1
- package/dist/bin/jpi-dispatch.js +165 -146
- package/dist/bin/jpi-hub.js +39 -34
- package/dist/bin/jpi-init.js +1 -1
- package/dist/bin/jpi-jobs.js +1 -1
- package/dist/bin/jpi-login.js +1 -1
- package/dist/bin/jpi-project.js +1 -1
- package/dist/bin/jpi-refresh.js +1 -1
- package/package.json +1 -1
package/dist/bin/jpi-bounties.js
CHANGED
|
@@ -2026,7 +2026,7 @@ function match(fp, jobs, limit = 5, now = Date.now(), opts = {}) {
|
|
|
2026
2026
|
reason: buildReason(details)
|
|
2027
2027
|
};
|
|
2028
2028
|
});
|
|
2029
|
-
return scored.filter((r) => r !== null && r.score >= MIN_SCORE).sort((a, b) => {
|
|
2029
|
+
return scored.filter((r) => r !== null && r.score >= (opts.minScore ?? MIN_SCORE)).sort((a, b) => {
|
|
2030
2030
|
const byScore = b.score - a.score;
|
|
2031
2031
|
if (Math.abs(byScore) > TIEBREAK_EPS) return byScore;
|
|
2032
2032
|
const byAcceptance = (b.acceptance?.count ?? 0) - (a.acceptance?.count ?? 0);
|
|
@@ -8353,11 +8353,134 @@ var init_profile = __esm({
|
|
|
8353
8353
|
}
|
|
8354
8354
|
});
|
|
8355
8355
|
|
|
8356
|
-
//
|
|
8357
|
-
|
|
8358
|
-
|
|
8356
|
+
// src/claims.ts
|
|
8357
|
+
var claims_exports = {};
|
|
8358
|
+
__export(claims_exports, {
|
|
8359
|
+
PUSHED_CLAIM_FIELDS: () => PUSHED_CLAIM_FIELDS,
|
|
8360
|
+
acceptedPRRate: () => acceptedPRRate,
|
|
8361
|
+
findClaim: () => findClaim,
|
|
8362
|
+
listClaims: () => listClaims,
|
|
8363
|
+
readClaims: () => readClaims,
|
|
8364
|
+
recordClaim: () => recordClaim,
|
|
8365
|
+
removeClaim: () => removeClaim,
|
|
8366
|
+
toPushedClaim: () => toPushedClaim,
|
|
8367
|
+
updateClaim: () => updateClaim
|
|
8368
|
+
});
|
|
8369
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, renameSync as renameSync2, existsSync as existsSync2 } from "fs";
|
|
8359
8370
|
import { join as join4 } from "path";
|
|
8360
8371
|
import { homedir as homedir3 } from "os";
|
|
8372
|
+
function toPushedClaim(claim) {
|
|
8373
|
+
return {
|
|
8374
|
+
kind: claim.kind,
|
|
8375
|
+
repoFullName: claim.repoFullName,
|
|
8376
|
+
state: claim.state,
|
|
8377
|
+
prUrl: claim.prUrl,
|
|
8378
|
+
merged: claim.state === "merged",
|
|
8379
|
+
claimedAt: claim.claimedAt,
|
|
8380
|
+
updatedAt: claim.updatedAt
|
|
8381
|
+
};
|
|
8382
|
+
}
|
|
8383
|
+
function nowISO() {
|
|
8384
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
8385
|
+
}
|
|
8386
|
+
function normalizeClaim(c) {
|
|
8387
|
+
return { ...c, kind: c.kind ?? "bounty", policy: c.policy ?? null };
|
|
8388
|
+
}
|
|
8389
|
+
function readClaims() {
|
|
8390
|
+
try {
|
|
8391
|
+
if (!existsSync2(CLAIMS_FILE)) return [];
|
|
8392
|
+
const data = JSON.parse(readFileSync4(CLAIMS_FILE, "utf8"));
|
|
8393
|
+
const claims = Array.isArray(data?.claims) ? data.claims : [];
|
|
8394
|
+
return claims.map(normalizeClaim);
|
|
8395
|
+
} catch {
|
|
8396
|
+
return [];
|
|
8397
|
+
}
|
|
8398
|
+
}
|
|
8399
|
+
function writeClaims(claims) {
|
|
8400
|
+
mkdirSync3(TERMINALHIRE_DIR3, { recursive: true });
|
|
8401
|
+
const tmp = `${CLAIMS_FILE}.tmp`;
|
|
8402
|
+
const payload = { claims };
|
|
8403
|
+
writeFileSync3(tmp, JSON.stringify(payload, null, 2), "utf8");
|
|
8404
|
+
renameSync2(tmp, CLAIMS_FILE);
|
|
8405
|
+
}
|
|
8406
|
+
function findClaim(id) {
|
|
8407
|
+
return readClaims().find((c) => c.id === id) ?? null;
|
|
8408
|
+
}
|
|
8409
|
+
function listClaims(opts = {}) {
|
|
8410
|
+
const claims = readClaims();
|
|
8411
|
+
if (!opts.active) return claims;
|
|
8412
|
+
return claims.filter((c) => !TERMINAL_STATES.has(c.state));
|
|
8413
|
+
}
|
|
8414
|
+
function recordClaim(rec) {
|
|
8415
|
+
const claims = readClaims();
|
|
8416
|
+
if (claims.some((c) => c.id === rec.id)) {
|
|
8417
|
+
throw new Error(
|
|
8418
|
+
`claim already exists for '${rec.id}' \u2014 run 'terminalhire claim status ${rec.id}' or 'terminalhire claim release ${rec.id}'`
|
|
8419
|
+
);
|
|
8420
|
+
}
|
|
8421
|
+
const ts = nowISO();
|
|
8422
|
+
const claim = {
|
|
8423
|
+
...rec,
|
|
8424
|
+
// Defensive default (mirrors normalizeClaim's `kind ?? 'bounty'` pattern):
|
|
8425
|
+
// a caller written before `policy` existed, or a plain-JS caller that skips
|
|
8426
|
+
// it, still produces a valid record instead of `policy: undefined`.
|
|
8427
|
+
policy: rec.policy ?? null,
|
|
8428
|
+
state: "claimed",
|
|
8429
|
+
worktreePath: null,
|
|
8430
|
+
branch: null,
|
|
8431
|
+
prUrl: null,
|
|
8432
|
+
review: null,
|
|
8433
|
+
claimedAt: ts,
|
|
8434
|
+
updatedAt: ts
|
|
8435
|
+
};
|
|
8436
|
+
claims.push(claim);
|
|
8437
|
+
writeClaims(claims);
|
|
8438
|
+
return claim;
|
|
8439
|
+
}
|
|
8440
|
+
function updateClaim(id, patch) {
|
|
8441
|
+
const claims = readClaims();
|
|
8442
|
+
const idx = claims.findIndex((c) => c.id === id);
|
|
8443
|
+
if (idx === -1) return null;
|
|
8444
|
+
claims[idx] = { ...claims[idx], ...patch, updatedAt: nowISO() };
|
|
8445
|
+
writeClaims(claims);
|
|
8446
|
+
return claims[idx];
|
|
8447
|
+
}
|
|
8448
|
+
function removeClaim(id) {
|
|
8449
|
+
const claims = readClaims();
|
|
8450
|
+
const next = claims.filter((c) => c.id !== id);
|
|
8451
|
+
if (next.length === claims.length) return false;
|
|
8452
|
+
writeClaims(next);
|
|
8453
|
+
return true;
|
|
8454
|
+
}
|
|
8455
|
+
function acceptedPRRate(claims = readClaims()) {
|
|
8456
|
+
const total = claims.length;
|
|
8457
|
+
const merged = claims.filter((c) => c.state === "merged").length;
|
|
8458
|
+
return { merged, total, rate: total === 0 ? 0 : merged / total };
|
|
8459
|
+
}
|
|
8460
|
+
var TERMINALHIRE_DIR3, CLAIMS_FILE, PUSHED_CLAIM_FIELDS, TERMINAL_STATES;
|
|
8461
|
+
var init_claims = __esm({
|
|
8462
|
+
"src/claims.ts"() {
|
|
8463
|
+
"use strict";
|
|
8464
|
+
TERMINALHIRE_DIR3 = process.env.TERMINALHIRE_DIR || join4(homedir3(), ".terminalhire");
|
|
8465
|
+
CLAIMS_FILE = join4(TERMINALHIRE_DIR3, "claims.json");
|
|
8466
|
+
PUSHED_CLAIM_FIELDS = [
|
|
8467
|
+
"kind",
|
|
8468
|
+
"repoFullName",
|
|
8469
|
+
"state",
|
|
8470
|
+
"prUrl",
|
|
8471
|
+
"merged",
|
|
8472
|
+
"claimedAt",
|
|
8473
|
+
"updatedAt"
|
|
8474
|
+
];
|
|
8475
|
+
TERMINAL_STATES = /* @__PURE__ */ new Set(["merged", "abandoned"]);
|
|
8476
|
+
}
|
|
8477
|
+
});
|
|
8478
|
+
|
|
8479
|
+
// bin/jpi-bounties.js
|
|
8480
|
+
init_src();
|
|
8481
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
8482
|
+
import { join as join5 } from "path";
|
|
8483
|
+
import { homedir as homedir4 } from "os";
|
|
8361
8484
|
import { createInterface } from "readline";
|
|
8362
8485
|
|
|
8363
8486
|
// bin/cache-store.js
|
|
@@ -8422,8 +8545,8 @@ function linkTitle(title, url) {
|
|
|
8422
8545
|
}
|
|
8423
8546
|
|
|
8424
8547
|
// bin/jpi-bounties.js
|
|
8425
|
-
var
|
|
8426
|
-
var INDEX_CACHE_FILE2 =
|
|
8548
|
+
var TERMINALHIRE_DIR4 = process.env.TERMINALHIRE_DIR || join5(homedir4(), ".terminalhire");
|
|
8549
|
+
var INDEX_CACHE_FILE2 = join5(TERMINALHIRE_DIR4, "index-cache.json");
|
|
8427
8550
|
var INDEX_TTL_MS = 15 * 60 * 1e3;
|
|
8428
8551
|
var API_URL = process.env["TERMINALHIRE_API_URL"] ?? process.env["JPI_API_URL"] ?? "https://terminalhire.com";
|
|
8429
8552
|
var RANK_MODE = process.env["TERMINALHIRE_BOUNTY_RANK"] ?? "winnability";
|
|
@@ -8436,7 +8559,7 @@ var SHOW_ALL = args.includes("--all");
|
|
|
8436
8559
|
var WINNABLE_ONLY = args.includes("--winnable");
|
|
8437
8560
|
function readIndexCache() {
|
|
8438
8561
|
try {
|
|
8439
|
-
const entry = JSON.parse(
|
|
8562
|
+
const entry = JSON.parse(readFileSync5(INDEX_CACHE_FILE2, "utf8"));
|
|
8440
8563
|
if (Date.now() - entry.ts < INDEX_TTL_MS) return entry.index;
|
|
8441
8564
|
return null;
|
|
8442
8565
|
} catch {
|
|
@@ -8468,22 +8591,26 @@ function formatAmount(b) {
|
|
|
8468
8591
|
return b.amountUSD != null ? "$" + b.amountUSD.toLocaleString() : "$\u2014";
|
|
8469
8592
|
}
|
|
8470
8593
|
var EFFORT_LABEL = { small: "small (~\xBD day)", medium: "medium (~1 day)", large: "large (multi-day)" };
|
|
8471
|
-
function printBounty(i, job, score, reason, matchedTags) {
|
|
8594
|
+
function printBounty(i, job, score, reason, matchedTags, claimedIds = /* @__PURE__ */ new Set()) {
|
|
8472
8595
|
const b = job.bounty ?? {};
|
|
8473
8596
|
const stars = b.repoStars != null ? ` \xB7 ${b.repoStars}\u2605` : "";
|
|
8474
8597
|
const effort = b.estimatedEffort ? ` \xB7 ${EFFORT_LABEL[b.estimatedEffort]}` : "";
|
|
8475
8598
|
const scoreStr = score > 0 ? ` \xB7 match ${Math.round(score * 100)}%` : "";
|
|
8476
8599
|
const prs = b.competingOpenPRs;
|
|
8477
8600
|
const contend = prs != null && prs > 0 ? ` \xB7 \u26A0 ${prs} PR${prs === 1 ? "" : "s"} in flight` : "";
|
|
8601
|
+
const claimed = claimedIds.has(job.id);
|
|
8602
|
+
const badge = claimed ? " \xB7 \u25CF claimed by you" : "";
|
|
8478
8603
|
const ref = opportunityShortToken(job.id);
|
|
8479
8604
|
console.log(`
|
|
8480
8605
|
${i + 1}. ${linkTitle(job.title, job.url)} [${ref}]`);
|
|
8481
|
-
console.log(` ${formatAmount(b)}${effort} \xB7 ${sanitizeText(b.repoFullName ?? job.company)}${stars}${scoreStr}${contend}`);
|
|
8606
|
+
console.log(` ${formatAmount(b)}${effort} \xB7 ${sanitizeText(b.repoFullName ?? job.company)}${stars}${scoreStr}${contend}${badge}`);
|
|
8482
8607
|
if (reason) console.log(` ${reason}`);
|
|
8483
8608
|
if (matchedTags && matchedTags.length) console.log(` Tags matched: ${matchedTags.slice(0, 5).join(", ")}`);
|
|
8484
8609
|
console.log(` id: ${job.id}`);
|
|
8485
8610
|
console.log(` Claim: ${sanitizeText(b.claimUrl ?? job.url)}`);
|
|
8486
|
-
console.log(
|
|
8611
|
+
console.log(
|
|
8612
|
+
claimed ? ` \u2192 claimed by you \u2014 terminalhire claim status ${job.id}` : ` \u2192 terminalhire claim ${ref}`
|
|
8613
|
+
);
|
|
8487
8614
|
}
|
|
8488
8615
|
function rankBounties(bounties, { rankMode = "winnability", scoreOf = () => 0 } = {}) {
|
|
8489
8616
|
const legacy = rankMode === "legacy";
|
|
@@ -8561,6 +8688,12 @@ async function run() {
|
|
|
8561
8688
|
if (result.status !== "ok") return;
|
|
8562
8689
|
const { bounties, ranked, matchedCount } = result;
|
|
8563
8690
|
const shown = SHOW_ALL ? bounties : bounties.slice(0, LIMIT);
|
|
8691
|
+
let claimedIds = /* @__PURE__ */ new Set();
|
|
8692
|
+
try {
|
|
8693
|
+
const { listClaims: listClaims2 } = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
8694
|
+
claimedIds = new Set(listClaims2({ active: true }).map((c) => c.id));
|
|
8695
|
+
} catch {
|
|
8696
|
+
}
|
|
8564
8697
|
console.log(
|
|
8565
8698
|
`
|
|
8566
8699
|
\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)
|
|
@@ -8568,7 +8701,7 @@ async function run() {
|
|
|
8568
8701
|
);
|
|
8569
8702
|
for (let i = 0; i < shown.length; i++) {
|
|
8570
8703
|
const r = ranked.get(shown[i].id);
|
|
8571
|
-
printBounty(i, shown[i], r?.score ?? 0, r?.reason, r?.matchedTags);
|
|
8704
|
+
printBounty(i, shown[i], r?.score ?? 0, r?.reason, r?.matchedTags, claimedIds);
|
|
8572
8705
|
}
|
|
8573
8706
|
if (!SHOW_ALL && bounties.length > shown.length) {
|
|
8574
8707
|
console.log(`
|
|
@@ -8595,6 +8728,7 @@ export {
|
|
|
8595
8728
|
classifyEmptyStatus,
|
|
8596
8729
|
filterPaidVisibility,
|
|
8597
8730
|
getBounties,
|
|
8731
|
+
printBounty,
|
|
8598
8732
|
rankBounties,
|
|
8599
8733
|
run
|
|
8600
8734
|
};
|
package/dist/bin/jpi-claim.js
CHANGED
|
@@ -2026,7 +2026,7 @@ function match(fp, jobs, limit = 5, now = Date.now(), opts = {}) {
|
|
|
2026
2026
|
reason: buildReason(details)
|
|
2027
2027
|
};
|
|
2028
2028
|
});
|
|
2029
|
-
return scored.filter((r) => r !== null && r.score >= MIN_SCORE).sort((a, b) => {
|
|
2029
|
+
return scored.filter((r) => r !== null && r.score >= (opts.minScore ?? MIN_SCORE)).sort((a, b) => {
|
|
2030
2030
|
const byScore = b.score - a.score;
|
|
2031
2031
|
if (Math.abs(byScore) > TIEBREAK_EPS) return byScore;
|
|
2032
2032
|
const byAcceptance = (b.acceptance?.count ?? 0) - (a.acceptance?.count ?? 0);
|
|
@@ -868,7 +868,7 @@ function match(fp, jobs, limit = 5, now = Date.now(), opts = {}) {
|
|
|
868
868
|
reason: buildReason(details)
|
|
869
869
|
};
|
|
870
870
|
});
|
|
871
|
-
return scored.filter((r) => r !== null && r.score >= MIN_SCORE).sort((a, b) => {
|
|
871
|
+
return scored.filter((r) => r !== null && r.score >= (opts.minScore ?? MIN_SCORE)).sort((a, b) => {
|
|
872
872
|
const byScore = b.score - a.score;
|
|
873
873
|
if (Math.abs(byScore) > TIEBREAK_EPS) return byScore;
|
|
874
874
|
const byAcceptance = (b.acceptance?.count ?? 0) - (a.acceptance?.count ?? 0);
|
|
@@ -1704,11 +1704,134 @@ var init_profile = __esm({
|
|
|
1704
1704
|
}
|
|
1705
1705
|
});
|
|
1706
1706
|
|
|
1707
|
-
//
|
|
1708
|
-
|
|
1709
|
-
|
|
1707
|
+
// src/claims.ts
|
|
1708
|
+
var claims_exports = {};
|
|
1709
|
+
__export(claims_exports, {
|
|
1710
|
+
PUSHED_CLAIM_FIELDS: () => PUSHED_CLAIM_FIELDS,
|
|
1711
|
+
acceptedPRRate: () => acceptedPRRate,
|
|
1712
|
+
findClaim: () => findClaim,
|
|
1713
|
+
listClaims: () => listClaims,
|
|
1714
|
+
readClaims: () => readClaims,
|
|
1715
|
+
recordClaim: () => recordClaim,
|
|
1716
|
+
removeClaim: () => removeClaim,
|
|
1717
|
+
toPushedClaim: () => toPushedClaim,
|
|
1718
|
+
updateClaim: () => updateClaim
|
|
1719
|
+
});
|
|
1720
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, renameSync as renameSync2, existsSync as existsSync3 } from "fs";
|
|
1710
1721
|
import { join as join5 } from "path";
|
|
1711
1722
|
import { homedir as homedir4 } from "os";
|
|
1723
|
+
function toPushedClaim(claim) {
|
|
1724
|
+
return {
|
|
1725
|
+
kind: claim.kind,
|
|
1726
|
+
repoFullName: claim.repoFullName,
|
|
1727
|
+
state: claim.state,
|
|
1728
|
+
prUrl: claim.prUrl,
|
|
1729
|
+
merged: claim.state === "merged",
|
|
1730
|
+
claimedAt: claim.claimedAt,
|
|
1731
|
+
updatedAt: claim.updatedAt
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
function nowISO() {
|
|
1735
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1736
|
+
}
|
|
1737
|
+
function normalizeClaim(c) {
|
|
1738
|
+
return { ...c, kind: c.kind ?? "bounty", policy: c.policy ?? null };
|
|
1739
|
+
}
|
|
1740
|
+
function readClaims() {
|
|
1741
|
+
try {
|
|
1742
|
+
if (!existsSync3(CLAIMS_FILE)) return [];
|
|
1743
|
+
const data = JSON.parse(readFileSync5(CLAIMS_FILE, "utf8"));
|
|
1744
|
+
const claims = Array.isArray(data?.claims) ? data.claims : [];
|
|
1745
|
+
return claims.map(normalizeClaim);
|
|
1746
|
+
} catch {
|
|
1747
|
+
return [];
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
function writeClaims(claims) {
|
|
1751
|
+
mkdirSync4(TERMINALHIRE_DIR4, { recursive: true });
|
|
1752
|
+
const tmp = `${CLAIMS_FILE}.tmp`;
|
|
1753
|
+
const payload = { claims };
|
|
1754
|
+
writeFileSync4(tmp, JSON.stringify(payload, null, 2), "utf8");
|
|
1755
|
+
renameSync2(tmp, CLAIMS_FILE);
|
|
1756
|
+
}
|
|
1757
|
+
function findClaim(id) {
|
|
1758
|
+
return readClaims().find((c) => c.id === id) ?? null;
|
|
1759
|
+
}
|
|
1760
|
+
function listClaims(opts = {}) {
|
|
1761
|
+
const claims = readClaims();
|
|
1762
|
+
if (!opts.active) return claims;
|
|
1763
|
+
return claims.filter((c) => !TERMINAL_STATES.has(c.state));
|
|
1764
|
+
}
|
|
1765
|
+
function recordClaim(rec) {
|
|
1766
|
+
const claims = readClaims();
|
|
1767
|
+
if (claims.some((c) => c.id === rec.id)) {
|
|
1768
|
+
throw new Error(
|
|
1769
|
+
`claim already exists for '${rec.id}' \u2014 run 'terminalhire claim status ${rec.id}' or 'terminalhire claim release ${rec.id}'`
|
|
1770
|
+
);
|
|
1771
|
+
}
|
|
1772
|
+
const ts = nowISO();
|
|
1773
|
+
const claim = {
|
|
1774
|
+
...rec,
|
|
1775
|
+
// Defensive default (mirrors normalizeClaim's `kind ?? 'bounty'` pattern):
|
|
1776
|
+
// a caller written before `policy` existed, or a plain-JS caller that skips
|
|
1777
|
+
// it, still produces a valid record instead of `policy: undefined`.
|
|
1778
|
+
policy: rec.policy ?? null,
|
|
1779
|
+
state: "claimed",
|
|
1780
|
+
worktreePath: null,
|
|
1781
|
+
branch: null,
|
|
1782
|
+
prUrl: null,
|
|
1783
|
+
review: null,
|
|
1784
|
+
claimedAt: ts,
|
|
1785
|
+
updatedAt: ts
|
|
1786
|
+
};
|
|
1787
|
+
claims.push(claim);
|
|
1788
|
+
writeClaims(claims);
|
|
1789
|
+
return claim;
|
|
1790
|
+
}
|
|
1791
|
+
function updateClaim(id, patch) {
|
|
1792
|
+
const claims = readClaims();
|
|
1793
|
+
const idx = claims.findIndex((c) => c.id === id);
|
|
1794
|
+
if (idx === -1) return null;
|
|
1795
|
+
claims[idx] = { ...claims[idx], ...patch, updatedAt: nowISO() };
|
|
1796
|
+
writeClaims(claims);
|
|
1797
|
+
return claims[idx];
|
|
1798
|
+
}
|
|
1799
|
+
function removeClaim(id) {
|
|
1800
|
+
const claims = readClaims();
|
|
1801
|
+
const next = claims.filter((c) => c.id !== id);
|
|
1802
|
+
if (next.length === claims.length) return false;
|
|
1803
|
+
writeClaims(next);
|
|
1804
|
+
return true;
|
|
1805
|
+
}
|
|
1806
|
+
function acceptedPRRate(claims = readClaims()) {
|
|
1807
|
+
const total = claims.length;
|
|
1808
|
+
const merged = claims.filter((c) => c.state === "merged").length;
|
|
1809
|
+
return { merged, total, rate: total === 0 ? 0 : merged / total };
|
|
1810
|
+
}
|
|
1811
|
+
var TERMINALHIRE_DIR4, CLAIMS_FILE, PUSHED_CLAIM_FIELDS, TERMINAL_STATES;
|
|
1812
|
+
var init_claims = __esm({
|
|
1813
|
+
"src/claims.ts"() {
|
|
1814
|
+
"use strict";
|
|
1815
|
+
TERMINALHIRE_DIR4 = process.env.TERMINALHIRE_DIR || join5(homedir4(), ".terminalhire");
|
|
1816
|
+
CLAIMS_FILE = join5(TERMINALHIRE_DIR4, "claims.json");
|
|
1817
|
+
PUSHED_CLAIM_FIELDS = [
|
|
1818
|
+
"kind",
|
|
1819
|
+
"repoFullName",
|
|
1820
|
+
"state",
|
|
1821
|
+
"prUrl",
|
|
1822
|
+
"merged",
|
|
1823
|
+
"claimedAt",
|
|
1824
|
+
"updatedAt"
|
|
1825
|
+
];
|
|
1826
|
+
TERMINAL_STATES = /* @__PURE__ */ new Set(["merged", "abandoned"]);
|
|
1827
|
+
}
|
|
1828
|
+
});
|
|
1829
|
+
|
|
1830
|
+
// bin/jpi-contribute.js
|
|
1831
|
+
init_src();
|
|
1832
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
1833
|
+
import { join as join6 } from "path";
|
|
1834
|
+
import { homedir as homedir5 } from "os";
|
|
1712
1835
|
import { createInterface } from "readline";
|
|
1713
1836
|
|
|
1714
1837
|
// bin/cache-store.js
|
|
@@ -1815,8 +1938,8 @@ function linkTitle(title, url) {
|
|
|
1815
1938
|
}
|
|
1816
1939
|
|
|
1817
1940
|
// bin/jpi-contribute.js
|
|
1818
|
-
var
|
|
1819
|
-
var INDEX_CACHE_FILE2 =
|
|
1941
|
+
var TERMINALHIRE_DIR5 = process.env.TERMINALHIRE_DIR || join6(homedir5(), ".terminalhire");
|
|
1942
|
+
var INDEX_CACHE_FILE2 = join6(TERMINALHIRE_DIR5, "index-cache.json");
|
|
1820
1943
|
var INDEX_TTL_MS = 15 * 60 * 1e3;
|
|
1821
1944
|
var API_URL = process.env["TERMINALHIRE_API_URL"] ?? process.env["JPI_API_URL"] ?? "https://terminalhire.com";
|
|
1822
1945
|
var HEADER = "Contribution opportunities \u2014 open issues where a merged PR actually counts toward your r\xE9sum\xE9.\nRepos \u226550\u2605 / \u226510 contributors \xB7 unassigned \xB7 merit-merge \xB7 matched locally to your stack.";
|
|
@@ -1824,7 +1947,7 @@ var OPT_IN_PROMPT = "Contribute is off. Turn it on to see open issues where a me
|
|
|
1824
1947
|
var EMPTY_STATE = "Nothing clears the bar right now. We only list issues where a merged PR actually counts toward\nyour r\xE9sum\xE9 \u2014 so the list stays honest. Try again after the next refresh.";
|
|
1825
1948
|
function readIndexCache() {
|
|
1826
1949
|
try {
|
|
1827
|
-
const entry = JSON.parse(
|
|
1950
|
+
const entry = JSON.parse(readFileSync6(INDEX_CACHE_FILE2, "utf8"));
|
|
1828
1951
|
if (Date.now() - entry.ts < INDEX_TTL_MS) return entry.index;
|
|
1829
1952
|
return null;
|
|
1830
1953
|
} catch {
|
|
@@ -1925,7 +2048,7 @@ function displayLanguage(job) {
|
|
|
1925
2048
|
const tag = (job.tags ?? []).find((t) => LANGUAGES.has(String(t).toLowerCase()));
|
|
1926
2049
|
return tag ?? "\u2014";
|
|
1927
2050
|
}
|
|
1928
|
-
function renderRow(i, result) {
|
|
2051
|
+
function renderRow(i, result, claimedIds = /* @__PURE__ */ new Set()) {
|
|
1929
2052
|
const job = result.job;
|
|
1930
2053
|
const c = job.contribution ?? {};
|
|
1931
2054
|
const repo = sanitizeText(c.repoFullName ?? job.company ?? "");
|
|
@@ -1934,9 +2057,11 @@ function renderRow(i, result) {
|
|
|
1934
2057
|
const lang = sanitizeText(displayLanguage(job));
|
|
1935
2058
|
const scorePct = `match ${Math.round((result.score ?? 0) * 100)}%`;
|
|
1936
2059
|
const ref = opportunityShortToken(job.id);
|
|
2060
|
+
const claimed = claimedIds.has(job.id);
|
|
2061
|
+
const badge = claimed ? " \xB7 \u25CF claimed by you" : "";
|
|
1937
2062
|
const line1 = `${i + 1}. ${linkTitle(job.title, c.issueUrl ?? job.url)} [${ref}]`;
|
|
1938
|
-
const line2 = ` ${repo} \xB7 ${num} \xB7 ${label} \xB7 ${lang} \xB7 ${scorePct}`;
|
|
1939
|
-
const line3 = ` \u2192 terminalhire claim ${ref}`;
|
|
2063
|
+
const line2 = ` ${repo} \xB7 ${num} \xB7 ${label} \xB7 ${lang} \xB7 ${scorePct}${badge}`;
|
|
2064
|
+
const line3 = claimed ? ` \u2192 claimed by you \u2014 terminalhire claim status ${job.id}` : ` \u2192 terminalhire claim ${ref}`;
|
|
1940
2065
|
return `${line1}
|
|
1941
2066
|
${line2}
|
|
1942
2067
|
${line3}`;
|
|
@@ -1967,9 +2092,15 @@ async function run(opts = {}) {
|
|
|
1967
2092
|
log(EMPTY_STATE);
|
|
1968
2093
|
return;
|
|
1969
2094
|
}
|
|
2095
|
+
let claimedIds = /* @__PURE__ */ new Set();
|
|
2096
|
+
try {
|
|
2097
|
+
const { listClaims: listClaims2 } = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
2098
|
+
claimedIds = new Set(listClaims2({ active: true }).map((c) => c.id));
|
|
2099
|
+
} catch {
|
|
2100
|
+
}
|
|
1970
2101
|
log(HEADER);
|
|
1971
2102
|
log("");
|
|
1972
|
-
for (let i = 0; i < results.length; i++) log(renderRow(i, results[i]));
|
|
2103
|
+
for (let i = 0; i < results.length; i++) log(renderRow(i, results[i], claimedIds));
|
|
1973
2104
|
} catch (err) {
|
|
1974
2105
|
console.error("terminalhire contribute error:", err?.message ?? err);
|
|
1975
2106
|
process.exit(1);
|
package/dist/bin/jpi-devs.js
CHANGED
|
@@ -2026,7 +2026,7 @@ function match(fp, jobs, limit = 5, now = Date.now(), opts = {}) {
|
|
|
2026
2026
|
reason: buildReason(details)
|
|
2027
2027
|
};
|
|
2028
2028
|
});
|
|
2029
|
-
return scored.filter((r) => r !== null && r.score >= MIN_SCORE).sort((a, b) => {
|
|
2029
|
+
return scored.filter((r) => r !== null && r.score >= (opts.minScore ?? MIN_SCORE)).sort((a, b) => {
|
|
2030
2030
|
const byScore = b.score - a.score;
|
|
2031
2031
|
if (Math.abs(byScore) > TIEBREAK_EPS) return byScore;
|
|
2032
2032
|
const byAcceptance = (b.acceptance?.count ?? 0) - (a.acceptance?.count ?? 0);
|
package/dist/bin/jpi-dispatch.js
CHANGED
|
@@ -2602,7 +2602,7 @@ function match(fp, jobs, limit = 5, now = Date.now(), opts = {}) {
|
|
|
2602
2602
|
reason: buildReason(details)
|
|
2603
2603
|
};
|
|
2604
2604
|
});
|
|
2605
|
-
return scored.filter((r) => r !== null && r.score >= MIN_SCORE).sort((a, b) => {
|
|
2605
|
+
return scored.filter((r) => r !== null && r.score >= (opts.minScore ?? MIN_SCORE)).sort((a, b) => {
|
|
2606
2606
|
const byScore = b.score - a.score;
|
|
2607
2607
|
if (Math.abs(byScore) > TIEBREAK_EPS) return byScore;
|
|
2608
2608
|
const byAcceptance = (b.acceptance?.count ?? 0) - (a.acceptance?.count ?? 0);
|
|
@@ -10558,22 +10558,146 @@ var init_jpi_project = __esm({
|
|
|
10558
10558
|
}
|
|
10559
10559
|
});
|
|
10560
10560
|
|
|
10561
|
+
// src/claims.ts
|
|
10562
|
+
var claims_exports = {};
|
|
10563
|
+
__export(claims_exports, {
|
|
10564
|
+
PUSHED_CLAIM_FIELDS: () => PUSHED_CLAIM_FIELDS,
|
|
10565
|
+
acceptedPRRate: () => acceptedPRRate,
|
|
10566
|
+
findClaim: () => findClaim,
|
|
10567
|
+
listClaims: () => listClaims,
|
|
10568
|
+
readClaims: () => readClaims,
|
|
10569
|
+
recordClaim: () => recordClaim,
|
|
10570
|
+
removeClaim: () => removeClaim,
|
|
10571
|
+
toPushedClaim: () => toPushedClaim,
|
|
10572
|
+
updateClaim: () => updateClaim
|
|
10573
|
+
});
|
|
10574
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9, renameSync as renameSync4, existsSync as existsSync8 } from "fs";
|
|
10575
|
+
import { join as join13 } from "path";
|
|
10576
|
+
import { homedir as homedir11 } from "os";
|
|
10577
|
+
function toPushedClaim(claim) {
|
|
10578
|
+
return {
|
|
10579
|
+
kind: claim.kind,
|
|
10580
|
+
repoFullName: claim.repoFullName,
|
|
10581
|
+
state: claim.state,
|
|
10582
|
+
prUrl: claim.prUrl,
|
|
10583
|
+
merged: claim.state === "merged",
|
|
10584
|
+
claimedAt: claim.claimedAt,
|
|
10585
|
+
updatedAt: claim.updatedAt
|
|
10586
|
+
};
|
|
10587
|
+
}
|
|
10588
|
+
function nowISO() {
|
|
10589
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
10590
|
+
}
|
|
10591
|
+
function normalizeClaim(c) {
|
|
10592
|
+
return { ...c, kind: c.kind ?? "bounty", policy: c.policy ?? null };
|
|
10593
|
+
}
|
|
10594
|
+
function readClaims() {
|
|
10595
|
+
try {
|
|
10596
|
+
if (!existsSync8(CLAIMS_FILE)) return [];
|
|
10597
|
+
const data = JSON.parse(readFileSync13(CLAIMS_FILE, "utf8"));
|
|
10598
|
+
const claims = Array.isArray(data?.claims) ? data.claims : [];
|
|
10599
|
+
return claims.map(normalizeClaim);
|
|
10600
|
+
} catch {
|
|
10601
|
+
return [];
|
|
10602
|
+
}
|
|
10603
|
+
}
|
|
10604
|
+
function writeClaims(claims) {
|
|
10605
|
+
mkdirSync9(TERMINALHIRE_DIR9, { recursive: true });
|
|
10606
|
+
const tmp = `${CLAIMS_FILE}.tmp`;
|
|
10607
|
+
const payload = { claims };
|
|
10608
|
+
writeFileSync9(tmp, JSON.stringify(payload, null, 2), "utf8");
|
|
10609
|
+
renameSync4(tmp, CLAIMS_FILE);
|
|
10610
|
+
}
|
|
10611
|
+
function findClaim(id) {
|
|
10612
|
+
return readClaims().find((c) => c.id === id) ?? null;
|
|
10613
|
+
}
|
|
10614
|
+
function listClaims(opts = {}) {
|
|
10615
|
+
const claims = readClaims();
|
|
10616
|
+
if (!opts.active) return claims;
|
|
10617
|
+
return claims.filter((c) => !TERMINAL_STATES.has(c.state));
|
|
10618
|
+
}
|
|
10619
|
+
function recordClaim(rec) {
|
|
10620
|
+
const claims = readClaims();
|
|
10621
|
+
if (claims.some((c) => c.id === rec.id)) {
|
|
10622
|
+
throw new Error(
|
|
10623
|
+
`claim already exists for '${rec.id}' \u2014 run 'terminalhire claim status ${rec.id}' or 'terminalhire claim release ${rec.id}'`
|
|
10624
|
+
);
|
|
10625
|
+
}
|
|
10626
|
+
const ts = nowISO();
|
|
10627
|
+
const claim = {
|
|
10628
|
+
...rec,
|
|
10629
|
+
// Defensive default (mirrors normalizeClaim's `kind ?? 'bounty'` pattern):
|
|
10630
|
+
// a caller written before `policy` existed, or a plain-JS caller that skips
|
|
10631
|
+
// it, still produces a valid record instead of `policy: undefined`.
|
|
10632
|
+
policy: rec.policy ?? null,
|
|
10633
|
+
state: "claimed",
|
|
10634
|
+
worktreePath: null,
|
|
10635
|
+
branch: null,
|
|
10636
|
+
prUrl: null,
|
|
10637
|
+
review: null,
|
|
10638
|
+
claimedAt: ts,
|
|
10639
|
+
updatedAt: ts
|
|
10640
|
+
};
|
|
10641
|
+
claims.push(claim);
|
|
10642
|
+
writeClaims(claims);
|
|
10643
|
+
return claim;
|
|
10644
|
+
}
|
|
10645
|
+
function updateClaim(id, patch) {
|
|
10646
|
+
const claims = readClaims();
|
|
10647
|
+
const idx = claims.findIndex((c) => c.id === id);
|
|
10648
|
+
if (idx === -1) return null;
|
|
10649
|
+
claims[idx] = { ...claims[idx], ...patch, updatedAt: nowISO() };
|
|
10650
|
+
writeClaims(claims);
|
|
10651
|
+
return claims[idx];
|
|
10652
|
+
}
|
|
10653
|
+
function removeClaim(id) {
|
|
10654
|
+
const claims = readClaims();
|
|
10655
|
+
const next = claims.filter((c) => c.id !== id);
|
|
10656
|
+
if (next.length === claims.length) return false;
|
|
10657
|
+
writeClaims(next);
|
|
10658
|
+
return true;
|
|
10659
|
+
}
|
|
10660
|
+
function acceptedPRRate(claims = readClaims()) {
|
|
10661
|
+
const total = claims.length;
|
|
10662
|
+
const merged = claims.filter((c) => c.state === "merged").length;
|
|
10663
|
+
return { merged, total, rate: total === 0 ? 0 : merged / total };
|
|
10664
|
+
}
|
|
10665
|
+
var TERMINALHIRE_DIR9, CLAIMS_FILE, PUSHED_CLAIM_FIELDS, TERMINAL_STATES;
|
|
10666
|
+
var init_claims = __esm({
|
|
10667
|
+
"src/claims.ts"() {
|
|
10668
|
+
"use strict";
|
|
10669
|
+
TERMINALHIRE_DIR9 = process.env.TERMINALHIRE_DIR || join13(homedir11(), ".terminalhire");
|
|
10670
|
+
CLAIMS_FILE = join13(TERMINALHIRE_DIR9, "claims.json");
|
|
10671
|
+
PUSHED_CLAIM_FIELDS = [
|
|
10672
|
+
"kind",
|
|
10673
|
+
"repoFullName",
|
|
10674
|
+
"state",
|
|
10675
|
+
"prUrl",
|
|
10676
|
+
"merged",
|
|
10677
|
+
"claimedAt",
|
|
10678
|
+
"updatedAt"
|
|
10679
|
+
];
|
|
10680
|
+
TERMINAL_STATES = /* @__PURE__ */ new Set(["merged", "abandoned"]);
|
|
10681
|
+
}
|
|
10682
|
+
});
|
|
10683
|
+
|
|
10561
10684
|
// bin/jpi-bounties.js
|
|
10562
10685
|
var jpi_bounties_exports = {};
|
|
10563
10686
|
__export(jpi_bounties_exports, {
|
|
10564
10687
|
classifyEmptyStatus: () => classifyEmptyStatus,
|
|
10565
10688
|
filterPaidVisibility: () => filterPaidVisibility,
|
|
10566
10689
|
getBounties: () => getBounties,
|
|
10690
|
+
printBounty: () => printBounty,
|
|
10567
10691
|
rankBounties: () => rankBounties,
|
|
10568
10692
|
run: () => run5
|
|
10569
10693
|
});
|
|
10570
|
-
import { readFileSync as
|
|
10571
|
-
import { join as
|
|
10572
|
-
import { homedir as
|
|
10694
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
10695
|
+
import { join as join14 } from "path";
|
|
10696
|
+
import { homedir as homedir12 } from "os";
|
|
10573
10697
|
import { createInterface as createInterface6 } from "readline";
|
|
10574
10698
|
function readIndexCache2() {
|
|
10575
10699
|
try {
|
|
10576
|
-
const entry = JSON.parse(
|
|
10700
|
+
const entry = JSON.parse(readFileSync14(INDEX_CACHE_FILE3, "utf8"));
|
|
10577
10701
|
if (Date.now() - entry.ts < INDEX_TTL_MS3) return entry.index;
|
|
10578
10702
|
return null;
|
|
10579
10703
|
} catch {
|
|
@@ -10604,22 +10728,26 @@ function prompt3(question) {
|
|
|
10604
10728
|
function formatAmount(b) {
|
|
10605
10729
|
return b.amountUSD != null ? "$" + b.amountUSD.toLocaleString() : "$\u2014";
|
|
10606
10730
|
}
|
|
10607
|
-
function printBounty(i, job, score, reason, matchedTags) {
|
|
10731
|
+
function printBounty(i, job, score, reason, matchedTags, claimedIds = /* @__PURE__ */ new Set()) {
|
|
10608
10732
|
const b = job.bounty ?? {};
|
|
10609
10733
|
const stars = b.repoStars != null ? ` \xB7 ${b.repoStars}\u2605` : "";
|
|
10610
10734
|
const effort = b.estimatedEffort ? ` \xB7 ${EFFORT_LABEL[b.estimatedEffort]}` : "";
|
|
10611
10735
|
const scoreStr = score > 0 ? ` \xB7 match ${Math.round(score * 100)}%` : "";
|
|
10612
10736
|
const prs = b.competingOpenPRs;
|
|
10613
10737
|
const contend = prs != null && prs > 0 ? ` \xB7 \u26A0 ${prs} PR${prs === 1 ? "" : "s"} in flight` : "";
|
|
10738
|
+
const claimed = claimedIds.has(job.id);
|
|
10739
|
+
const badge = claimed ? " \xB7 \u25CF claimed by you" : "";
|
|
10614
10740
|
const ref = opportunityShortToken(job.id);
|
|
10615
10741
|
console.log(`
|
|
10616
10742
|
${i + 1}. ${linkTitle(job.title, job.url)} [${ref}]`);
|
|
10617
|
-
console.log(` ${formatAmount(b)}${effort} \xB7 ${sanitizeText(b.repoFullName ?? job.company)}${stars}${scoreStr}${contend}`);
|
|
10743
|
+
console.log(` ${formatAmount(b)}${effort} \xB7 ${sanitizeText(b.repoFullName ?? job.company)}${stars}${scoreStr}${contend}${badge}`);
|
|
10618
10744
|
if (reason) console.log(` ${reason}`);
|
|
10619
10745
|
if (matchedTags && matchedTags.length) console.log(` Tags matched: ${matchedTags.slice(0, 5).join(", ")}`);
|
|
10620
10746
|
console.log(` id: ${job.id}`);
|
|
10621
10747
|
console.log(` Claim: ${sanitizeText(b.claimUrl ?? job.url)}`);
|
|
10622
|
-
console.log(
|
|
10748
|
+
console.log(
|
|
10749
|
+
claimed ? ` \u2192 claimed by you \u2014 terminalhire claim status ${job.id}` : ` \u2192 terminalhire claim ${ref}`
|
|
10750
|
+
);
|
|
10623
10751
|
}
|
|
10624
10752
|
function rankBounties(bounties, { rankMode = "winnability", scoreOf = () => 0 } = {}) {
|
|
10625
10753
|
const legacy = rankMode === "legacy";
|
|
@@ -10697,6 +10825,12 @@ async function run5() {
|
|
|
10697
10825
|
if (result.status !== "ok") return;
|
|
10698
10826
|
const { bounties, ranked, matchedCount } = result;
|
|
10699
10827
|
const shown = SHOW_ALL3 ? bounties : bounties.slice(0, LIMIT3);
|
|
10828
|
+
let claimedIds = /* @__PURE__ */ new Set();
|
|
10829
|
+
try {
|
|
10830
|
+
const { listClaims: listClaims2 } = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
10831
|
+
claimedIds = new Set(listClaims2({ active: true }).map((c) => c.id));
|
|
10832
|
+
} catch {
|
|
10833
|
+
}
|
|
10700
10834
|
console.log(
|
|
10701
10835
|
`
|
|
10702
10836
|
\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)
|
|
@@ -10704,7 +10838,7 @@ async function run5() {
|
|
|
10704
10838
|
);
|
|
10705
10839
|
for (let i = 0; i < shown.length; i++) {
|
|
10706
10840
|
const r = ranked.get(shown[i].id);
|
|
10707
|
-
printBounty(i, shown[i], r?.score ?? 0, r?.reason, r?.matchedTags);
|
|
10841
|
+
printBounty(i, shown[i], r?.score ?? 0, r?.reason, r?.matchedTags, claimedIds);
|
|
10708
10842
|
}
|
|
10709
10843
|
if (!SHOW_ALL3 && bounties.length > shown.length) {
|
|
10710
10844
|
console.log(`
|
|
@@ -10727,15 +10861,15 @@ Open this to claim/work the bounty (you go straight to the source \u2014 we neve
|
|
|
10727
10861
|
process.exit(1);
|
|
10728
10862
|
}
|
|
10729
10863
|
}
|
|
10730
|
-
var
|
|
10864
|
+
var TERMINALHIRE_DIR10, INDEX_CACHE_FILE3, INDEX_TTL_MS3, API_URL4, RANK_MODE, DEFAULT_LIMIT3, args4, limitArg3, LIMIT3, PRICED_ONLY, SHOW_ALL3, WINNABLE_ONLY, EFFORT_LABEL;
|
|
10731
10865
|
var init_jpi_bounties = __esm({
|
|
10732
10866
|
"bin/jpi-bounties.js"() {
|
|
10733
10867
|
"use strict";
|
|
10734
10868
|
init_src();
|
|
10735
10869
|
init_cache_store();
|
|
10736
10870
|
init_sanitize();
|
|
10737
|
-
|
|
10738
|
-
INDEX_CACHE_FILE3 =
|
|
10871
|
+
TERMINALHIRE_DIR10 = process.env.TERMINALHIRE_DIR || join14(homedir12(), ".terminalhire");
|
|
10872
|
+
INDEX_CACHE_FILE3 = join14(TERMINALHIRE_DIR10, "index-cache.json");
|
|
10739
10873
|
INDEX_TTL_MS3 = 15 * 60 * 1e3;
|
|
10740
10874
|
API_URL4 = process.env["TERMINALHIRE_API_URL"] ?? process.env["JPI_API_URL"] ?? "https://terminalhire.com";
|
|
10741
10875
|
RANK_MODE = process.env["TERMINALHIRE_BOUNTY_RANK"] ?? "winnability";
|
|
@@ -10757,13 +10891,13 @@ __export(jpi_contribute_exports, {
|
|
|
10757
10891
|
renderRow: () => renderRow,
|
|
10758
10892
|
run: () => run6
|
|
10759
10893
|
});
|
|
10760
|
-
import { readFileSync as
|
|
10761
|
-
import { join as
|
|
10762
|
-
import { homedir as
|
|
10894
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
10895
|
+
import { join as join15 } from "path";
|
|
10896
|
+
import { homedir as homedir13 } from "os";
|
|
10763
10897
|
import { createInterface as createInterface7 } from "readline";
|
|
10764
10898
|
function readIndexCache3() {
|
|
10765
10899
|
try {
|
|
10766
|
-
const entry = JSON.parse(
|
|
10900
|
+
const entry = JSON.parse(readFileSync15(INDEX_CACHE_FILE4, "utf8"));
|
|
10767
10901
|
if (Date.now() - entry.ts < INDEX_TTL_MS4) return entry.index;
|
|
10768
10902
|
return null;
|
|
10769
10903
|
} catch {
|
|
@@ -10822,7 +10956,7 @@ function displayLanguage(job) {
|
|
|
10822
10956
|
const tag = (job.tags ?? []).find((t) => LANGUAGES.has(String(t).toLowerCase()));
|
|
10823
10957
|
return tag ?? "\u2014";
|
|
10824
10958
|
}
|
|
10825
|
-
function renderRow(i, result) {
|
|
10959
|
+
function renderRow(i, result, claimedIds = /* @__PURE__ */ new Set()) {
|
|
10826
10960
|
const job = result.job;
|
|
10827
10961
|
const c = job.contribution ?? {};
|
|
10828
10962
|
const repo = sanitizeText(c.repoFullName ?? job.company ?? "");
|
|
@@ -10831,9 +10965,11 @@ function renderRow(i, result) {
|
|
|
10831
10965
|
const lang = sanitizeText(displayLanguage(job));
|
|
10832
10966
|
const scorePct = `match ${Math.round((result.score ?? 0) * 100)}%`;
|
|
10833
10967
|
const ref = opportunityShortToken(job.id);
|
|
10968
|
+
const claimed = claimedIds.has(job.id);
|
|
10969
|
+
const badge = claimed ? " \xB7 \u25CF claimed by you" : "";
|
|
10834
10970
|
const line1 = `${i + 1}. ${linkTitle(job.title, c.issueUrl ?? job.url)} [${ref}]`;
|
|
10835
|
-
const line2 = ` ${repo} \xB7 ${num} \xB7 ${label} \xB7 ${lang} \xB7 ${scorePct}`;
|
|
10836
|
-
const line3 = ` \u2192 terminalhire claim ${ref}`;
|
|
10971
|
+
const line2 = ` ${repo} \xB7 ${num} \xB7 ${label} \xB7 ${lang} \xB7 ${scorePct}${badge}`;
|
|
10972
|
+
const line3 = claimed ? ` \u2192 claimed by you \u2014 terminalhire claim status ${job.id}` : ` \u2192 terminalhire claim ${ref}`;
|
|
10837
10973
|
return `${line1}
|
|
10838
10974
|
${line2}
|
|
10839
10975
|
${line3}`;
|
|
@@ -10864,15 +11000,21 @@ async function run6(opts = {}) {
|
|
|
10864
11000
|
log(EMPTY_STATE);
|
|
10865
11001
|
return;
|
|
10866
11002
|
}
|
|
11003
|
+
let claimedIds = /* @__PURE__ */ new Set();
|
|
11004
|
+
try {
|
|
11005
|
+
const { listClaims: listClaims2 } = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
11006
|
+
claimedIds = new Set(listClaims2({ active: true }).map((c) => c.id));
|
|
11007
|
+
} catch {
|
|
11008
|
+
}
|
|
10867
11009
|
log(HEADER);
|
|
10868
11010
|
log("");
|
|
10869
|
-
for (let i = 0; i < results.length; i++) log(renderRow(i, results[i]));
|
|
11011
|
+
for (let i = 0; i < results.length; i++) log(renderRow(i, results[i], claimedIds));
|
|
10870
11012
|
} catch (err) {
|
|
10871
11013
|
console.error("terminalhire contribute error:", err?.message ?? err);
|
|
10872
11014
|
process.exit(1);
|
|
10873
11015
|
}
|
|
10874
11016
|
}
|
|
10875
|
-
var
|
|
11017
|
+
var TERMINALHIRE_DIR11, INDEX_CACHE_FILE4, INDEX_TTL_MS4, API_URL5, HEADER, OPT_IN_PROMPT, EMPTY_STATE, STRONG_THRESHOLD, LANGUAGES;
|
|
10876
11018
|
var init_jpi_contribute = __esm({
|
|
10877
11019
|
"bin/jpi-contribute.js"() {
|
|
10878
11020
|
"use strict";
|
|
@@ -10880,8 +11022,8 @@ var init_jpi_contribute = __esm({
|
|
|
10880
11022
|
init_cache_store();
|
|
10881
11023
|
init_config();
|
|
10882
11024
|
init_sanitize();
|
|
10883
|
-
|
|
10884
|
-
INDEX_CACHE_FILE4 =
|
|
11025
|
+
TERMINALHIRE_DIR11 = process.env.TERMINALHIRE_DIR || join15(homedir13(), ".terminalhire");
|
|
11026
|
+
INDEX_CACHE_FILE4 = join15(TERMINALHIRE_DIR11, "index-cache.json");
|
|
10885
11027
|
INDEX_TTL_MS4 = 15 * 60 * 1e3;
|
|
10886
11028
|
API_URL5 = process.env["TERMINALHIRE_API_URL"] ?? process.env["JPI_API_URL"] ?? "https://terminalhire.com";
|
|
10887
11029
|
HEADER = "Contribution opportunities \u2014 open issues where a merged PR actually counts toward your r\xE9sum\xE9.\nRepos \u226550\u2605 / \u226510 contributors \xB7 unassigned \xB7 merit-merge \xB7 matched locally to your stack.";
|
|
@@ -10932,129 +11074,6 @@ var init_jpi_contribute = __esm({
|
|
|
10932
11074
|
}
|
|
10933
11075
|
});
|
|
10934
11076
|
|
|
10935
|
-
// src/claims.ts
|
|
10936
|
-
var claims_exports = {};
|
|
10937
|
-
__export(claims_exports, {
|
|
10938
|
-
PUSHED_CLAIM_FIELDS: () => PUSHED_CLAIM_FIELDS,
|
|
10939
|
-
acceptedPRRate: () => acceptedPRRate,
|
|
10940
|
-
findClaim: () => findClaim,
|
|
10941
|
-
listClaims: () => listClaims,
|
|
10942
|
-
readClaims: () => readClaims,
|
|
10943
|
-
recordClaim: () => recordClaim,
|
|
10944
|
-
removeClaim: () => removeClaim,
|
|
10945
|
-
toPushedClaim: () => toPushedClaim,
|
|
10946
|
-
updateClaim: () => updateClaim
|
|
10947
|
-
});
|
|
10948
|
-
import { readFileSync as readFileSync15, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9, renameSync as renameSync4, existsSync as existsSync8 } from "fs";
|
|
10949
|
-
import { join as join15 } from "path";
|
|
10950
|
-
import { homedir as homedir13 } from "os";
|
|
10951
|
-
function toPushedClaim(claim) {
|
|
10952
|
-
return {
|
|
10953
|
-
kind: claim.kind,
|
|
10954
|
-
repoFullName: claim.repoFullName,
|
|
10955
|
-
state: claim.state,
|
|
10956
|
-
prUrl: claim.prUrl,
|
|
10957
|
-
merged: claim.state === "merged",
|
|
10958
|
-
claimedAt: claim.claimedAt,
|
|
10959
|
-
updatedAt: claim.updatedAt
|
|
10960
|
-
};
|
|
10961
|
-
}
|
|
10962
|
-
function nowISO() {
|
|
10963
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
10964
|
-
}
|
|
10965
|
-
function normalizeClaim(c) {
|
|
10966
|
-
return { ...c, kind: c.kind ?? "bounty", policy: c.policy ?? null };
|
|
10967
|
-
}
|
|
10968
|
-
function readClaims() {
|
|
10969
|
-
try {
|
|
10970
|
-
if (!existsSync8(CLAIMS_FILE)) return [];
|
|
10971
|
-
const data = JSON.parse(readFileSync15(CLAIMS_FILE, "utf8"));
|
|
10972
|
-
const claims = Array.isArray(data?.claims) ? data.claims : [];
|
|
10973
|
-
return claims.map(normalizeClaim);
|
|
10974
|
-
} catch {
|
|
10975
|
-
return [];
|
|
10976
|
-
}
|
|
10977
|
-
}
|
|
10978
|
-
function writeClaims(claims) {
|
|
10979
|
-
mkdirSync9(TERMINALHIRE_DIR11, { recursive: true });
|
|
10980
|
-
const tmp = `${CLAIMS_FILE}.tmp`;
|
|
10981
|
-
const payload = { claims };
|
|
10982
|
-
writeFileSync9(tmp, JSON.stringify(payload, null, 2), "utf8");
|
|
10983
|
-
renameSync4(tmp, CLAIMS_FILE);
|
|
10984
|
-
}
|
|
10985
|
-
function findClaim(id) {
|
|
10986
|
-
return readClaims().find((c) => c.id === id) ?? null;
|
|
10987
|
-
}
|
|
10988
|
-
function listClaims(opts = {}) {
|
|
10989
|
-
const claims = readClaims();
|
|
10990
|
-
if (!opts.active) return claims;
|
|
10991
|
-
return claims.filter((c) => !TERMINAL_STATES.has(c.state));
|
|
10992
|
-
}
|
|
10993
|
-
function recordClaim(rec) {
|
|
10994
|
-
const claims = readClaims();
|
|
10995
|
-
if (claims.some((c) => c.id === rec.id)) {
|
|
10996
|
-
throw new Error(
|
|
10997
|
-
`claim already exists for '${rec.id}' \u2014 run 'terminalhire claim status ${rec.id}' or 'terminalhire claim release ${rec.id}'`
|
|
10998
|
-
);
|
|
10999
|
-
}
|
|
11000
|
-
const ts = nowISO();
|
|
11001
|
-
const claim = {
|
|
11002
|
-
...rec,
|
|
11003
|
-
// Defensive default (mirrors normalizeClaim's `kind ?? 'bounty'` pattern):
|
|
11004
|
-
// a caller written before `policy` existed, or a plain-JS caller that skips
|
|
11005
|
-
// it, still produces a valid record instead of `policy: undefined`.
|
|
11006
|
-
policy: rec.policy ?? null,
|
|
11007
|
-
state: "claimed",
|
|
11008
|
-
worktreePath: null,
|
|
11009
|
-
branch: null,
|
|
11010
|
-
prUrl: null,
|
|
11011
|
-
review: null,
|
|
11012
|
-
claimedAt: ts,
|
|
11013
|
-
updatedAt: ts
|
|
11014
|
-
};
|
|
11015
|
-
claims.push(claim);
|
|
11016
|
-
writeClaims(claims);
|
|
11017
|
-
return claim;
|
|
11018
|
-
}
|
|
11019
|
-
function updateClaim(id, patch) {
|
|
11020
|
-
const claims = readClaims();
|
|
11021
|
-
const idx = claims.findIndex((c) => c.id === id);
|
|
11022
|
-
if (idx === -1) return null;
|
|
11023
|
-
claims[idx] = { ...claims[idx], ...patch, updatedAt: nowISO() };
|
|
11024
|
-
writeClaims(claims);
|
|
11025
|
-
return claims[idx];
|
|
11026
|
-
}
|
|
11027
|
-
function removeClaim(id) {
|
|
11028
|
-
const claims = readClaims();
|
|
11029
|
-
const next = claims.filter((c) => c.id !== id);
|
|
11030
|
-
if (next.length === claims.length) return false;
|
|
11031
|
-
writeClaims(next);
|
|
11032
|
-
return true;
|
|
11033
|
-
}
|
|
11034
|
-
function acceptedPRRate(claims = readClaims()) {
|
|
11035
|
-
const total = claims.length;
|
|
11036
|
-
const merged = claims.filter((c) => c.state === "merged").length;
|
|
11037
|
-
return { merged, total, rate: total === 0 ? 0 : merged / total };
|
|
11038
|
-
}
|
|
11039
|
-
var TERMINALHIRE_DIR11, CLAIMS_FILE, PUSHED_CLAIM_FIELDS, TERMINAL_STATES;
|
|
11040
|
-
var init_claims = __esm({
|
|
11041
|
-
"src/claims.ts"() {
|
|
11042
|
-
"use strict";
|
|
11043
|
-
TERMINALHIRE_DIR11 = process.env.TERMINALHIRE_DIR || join15(homedir13(), ".terminalhire");
|
|
11044
|
-
CLAIMS_FILE = join15(TERMINALHIRE_DIR11, "claims.json");
|
|
11045
|
-
PUSHED_CLAIM_FIELDS = [
|
|
11046
|
-
"kind",
|
|
11047
|
-
"repoFullName",
|
|
11048
|
-
"state",
|
|
11049
|
-
"prUrl",
|
|
11050
|
-
"merged",
|
|
11051
|
-
"claimedAt",
|
|
11052
|
-
"updatedAt"
|
|
11053
|
-
];
|
|
11054
|
-
TERMINAL_STATES = /* @__PURE__ */ new Set(["merged", "abandoned"]);
|
|
11055
|
-
}
|
|
11056
|
-
});
|
|
11057
|
-
|
|
11058
11077
|
// bin/claim-push-bg.js
|
|
11059
11078
|
var claim_push_bg_exports = {};
|
|
11060
11079
|
__export(claim_push_bg_exports, {
|
package/dist/bin/jpi-hub.js
CHANGED
|
@@ -409,6 +409,43 @@ var init_tui_core = __esm({
|
|
|
409
409
|
}
|
|
410
410
|
});
|
|
411
411
|
|
|
412
|
+
// src/claims.ts
|
|
413
|
+
import { readFileSync, writeFileSync, mkdirSync, renameSync, existsSync } from "fs";
|
|
414
|
+
import { join } from "path";
|
|
415
|
+
import { homedir } from "os";
|
|
416
|
+
function normalizeClaim(c) {
|
|
417
|
+
return { ...c, kind: c.kind ?? "bounty", policy: c.policy ?? null };
|
|
418
|
+
}
|
|
419
|
+
function readClaims() {
|
|
420
|
+
try {
|
|
421
|
+
if (!existsSync(CLAIMS_FILE)) return [];
|
|
422
|
+
const data = JSON.parse(readFileSync(CLAIMS_FILE, "utf8"));
|
|
423
|
+
const claims = Array.isArray(data?.claims) ? data.claims : [];
|
|
424
|
+
return claims.map(normalizeClaim);
|
|
425
|
+
} catch {
|
|
426
|
+
return [];
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
function listClaims(opts = {}) {
|
|
430
|
+
const claims = readClaims();
|
|
431
|
+
if (!opts.active) return claims;
|
|
432
|
+
return claims.filter((c) => !TERMINAL_STATES.has(c.state));
|
|
433
|
+
}
|
|
434
|
+
function acceptedPRRate(claims = readClaims()) {
|
|
435
|
+
const total = claims.length;
|
|
436
|
+
const merged = claims.filter((c) => c.state === "merged").length;
|
|
437
|
+
return { merged, total, rate: total === 0 ? 0 : merged / total };
|
|
438
|
+
}
|
|
439
|
+
var TERMINALHIRE_DIR, CLAIMS_FILE, TERMINAL_STATES;
|
|
440
|
+
var init_claims = __esm({
|
|
441
|
+
"src/claims.ts"() {
|
|
442
|
+
"use strict";
|
|
443
|
+
TERMINALHIRE_DIR = process.env.TERMINALHIRE_DIR || join(homedir(), ".terminalhire");
|
|
444
|
+
CLAIMS_FILE = join(TERMINALHIRE_DIR, "claims.json");
|
|
445
|
+
TERMINAL_STATES = /* @__PURE__ */ new Set(["merged", "abandoned"]);
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
412
449
|
// ../../packages/core/src/types.ts
|
|
413
450
|
function isBounty(job) {
|
|
414
451
|
return job.source === "bounty" && job.bounty != null;
|
|
@@ -2397,7 +2434,7 @@ function match(fp, jobs, limit = 5, now = Date.now(), opts = {}) {
|
|
|
2397
2434
|
reason: buildReason(details)
|
|
2398
2435
|
};
|
|
2399
2436
|
});
|
|
2400
|
-
return scored.filter((r) => r !== null && r.score >= MIN_SCORE).sort((a, b) => {
|
|
2437
|
+
return scored.filter((r) => r !== null && r.score >= (opts.minScore ?? MIN_SCORE)).sort((a, b) => {
|
|
2401
2438
|
const byScore = b.score - a.score;
|
|
2402
2439
|
if (Math.abs(byScore) > TIEBREAK_EPS) return byScore;
|
|
2403
2440
|
const byAcceptance = (b.acceptance?.count ?? 0) - (a.acceptance?.count ?? 0);
|
|
@@ -9418,39 +9455,7 @@ var init_intro2 = __esm({
|
|
|
9418
9455
|
|
|
9419
9456
|
// bin/jpi-hub.js
|
|
9420
9457
|
init_tui_core();
|
|
9421
|
-
|
|
9422
|
-
// src/claims.ts
|
|
9423
|
-
import { readFileSync, writeFileSync, mkdirSync, renameSync, existsSync } from "fs";
|
|
9424
|
-
import { join } from "path";
|
|
9425
|
-
import { homedir } from "os";
|
|
9426
|
-
var TERMINALHIRE_DIR = process.env.TERMINALHIRE_DIR || join(homedir(), ".terminalhire");
|
|
9427
|
-
var CLAIMS_FILE = join(TERMINALHIRE_DIR, "claims.json");
|
|
9428
|
-
var TERMINAL_STATES = /* @__PURE__ */ new Set(["merged", "abandoned"]);
|
|
9429
|
-
function normalizeClaim(c) {
|
|
9430
|
-
return { ...c, kind: c.kind ?? "bounty", policy: c.policy ?? null };
|
|
9431
|
-
}
|
|
9432
|
-
function readClaims() {
|
|
9433
|
-
try {
|
|
9434
|
-
if (!existsSync(CLAIMS_FILE)) return [];
|
|
9435
|
-
const data = JSON.parse(readFileSync(CLAIMS_FILE, "utf8"));
|
|
9436
|
-
const claims = Array.isArray(data?.claims) ? data.claims : [];
|
|
9437
|
-
return claims.map(normalizeClaim);
|
|
9438
|
-
} catch {
|
|
9439
|
-
return [];
|
|
9440
|
-
}
|
|
9441
|
-
}
|
|
9442
|
-
function listClaims(opts = {}) {
|
|
9443
|
-
const claims = readClaims();
|
|
9444
|
-
if (!opts.active) return claims;
|
|
9445
|
-
return claims.filter((c) => !TERMINAL_STATES.has(c.state));
|
|
9446
|
-
}
|
|
9447
|
-
function acceptedPRRate(claims = readClaims()) {
|
|
9448
|
-
const total = claims.length;
|
|
9449
|
-
const merged = claims.filter((c) => c.state === "merged").length;
|
|
9450
|
-
return { merged, total, rate: total === 0 ? 0 : merged / total };
|
|
9451
|
-
}
|
|
9452
|
-
|
|
9453
|
-
// bin/jpi-hub.js
|
|
9458
|
+
init_claims();
|
|
9454
9459
|
init_profile();
|
|
9455
9460
|
init_config();
|
|
9456
9461
|
init_jpi_chat_read();
|
package/dist/bin/jpi-init.js
CHANGED
|
@@ -1997,7 +1997,7 @@ function match(fp, jobs, limit = 5, now = Date.now(), opts = {}) {
|
|
|
1997
1997
|
reason: buildReason(details)
|
|
1998
1998
|
};
|
|
1999
1999
|
});
|
|
2000
|
-
return scored.filter((r) => r !== null && r.score >= MIN_SCORE).sort((a, b) => {
|
|
2000
|
+
return scored.filter((r) => r !== null && r.score >= (opts.minScore ?? MIN_SCORE)).sort((a, b) => {
|
|
2001
2001
|
const byScore = b.score - a.score;
|
|
2002
2002
|
if (Math.abs(byScore) > TIEBREAK_EPS) return byScore;
|
|
2003
2003
|
const byAcceptance = (b.acceptance?.count ?? 0) - (a.acceptance?.count ?? 0);
|
package/dist/bin/jpi-jobs.js
CHANGED
|
@@ -2026,7 +2026,7 @@ function match(fp, jobs, limit = 5, now = Date.now(), opts = {}) {
|
|
|
2026
2026
|
reason: buildReason(details)
|
|
2027
2027
|
};
|
|
2028
2028
|
});
|
|
2029
|
-
return scored.filter((r) => r !== null && r.score >= MIN_SCORE).sort((a, b) => {
|
|
2029
|
+
return scored.filter((r) => r !== null && r.score >= (opts.minScore ?? MIN_SCORE)).sort((a, b) => {
|
|
2030
2030
|
const byScore = b.score - a.score;
|
|
2031
2031
|
if (Math.abs(byScore) > TIEBREAK_EPS) return byScore;
|
|
2032
2032
|
const byAcceptance = (b.acceptance?.count ?? 0) - (a.acceptance?.count ?? 0);
|
package/dist/bin/jpi-login.js
CHANGED
|
@@ -2331,7 +2331,7 @@ function match(fp, jobs, limit = 5, now = Date.now(), opts = {}) {
|
|
|
2331
2331
|
reason: buildReason(details)
|
|
2332
2332
|
};
|
|
2333
2333
|
});
|
|
2334
|
-
return scored.filter((r) => r !== null && r.score >= MIN_SCORE).sort((a, b) => {
|
|
2334
|
+
return scored.filter((r) => r !== null && r.score >= (opts.minScore ?? MIN_SCORE)).sort((a, b) => {
|
|
2335
2335
|
const byScore = b.score - a.score;
|
|
2336
2336
|
if (Math.abs(byScore) > TIEBREAK_EPS) return byScore;
|
|
2337
2337
|
const byAcceptance = (b.acceptance?.count ?? 0) - (a.acceptance?.count ?? 0);
|
package/dist/bin/jpi-project.js
CHANGED
|
@@ -1997,7 +1997,7 @@ function match(fp, jobs, limit = 5, now = Date.now(), opts = {}) {
|
|
|
1997
1997
|
reason: buildReason(details)
|
|
1998
1998
|
};
|
|
1999
1999
|
});
|
|
2000
|
-
return scored.filter((r) => r !== null && r.score >= MIN_SCORE).sort((a, b) => {
|
|
2000
|
+
return scored.filter((r) => r !== null && r.score >= (opts.minScore ?? MIN_SCORE)).sort((a, b) => {
|
|
2001
2001
|
const byScore = b.score - a.score;
|
|
2002
2002
|
if (Math.abs(byScore) > TIEBREAK_EPS) return byScore;
|
|
2003
2003
|
const byAcceptance = (b.acceptance?.count ?? 0) - (a.acceptance?.count ?? 0);
|
package/dist/bin/jpi-refresh.js
CHANGED
|
@@ -2116,7 +2116,7 @@ function match(fp, jobs, limit = 5, now = Date.now(), opts = {}) {
|
|
|
2116
2116
|
reason: buildReason(details)
|
|
2117
2117
|
};
|
|
2118
2118
|
});
|
|
2119
|
-
return scored.filter((r) => r !== null && r.score >= MIN_SCORE).sort((a, b) => {
|
|
2119
|
+
return scored.filter((r) => r !== null && r.score >= (opts.minScore ?? MIN_SCORE)).sort((a, b) => {
|
|
2120
2120
|
const byScore = b.score - a.score;
|
|
2121
2121
|
if (Math.abs(byScore) > TIEBREAK_EPS) return byScore;
|
|
2122
2122
|
const byAcceptance = (b.acceptance?.count ?? 0) - (a.acceptance?.count ?? 0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "terminalhire",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.0",
|
|
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",
|