terminalhire 0.27.0 → 0.29.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/claim-push-bg.js +41 -0
- package/dist/bin/jpi-claim.js +135 -4
- package/dist/bin/jpi-dispatch.js +158 -6
- package/dist/bin/jpi-refresh.js +227 -168
- package/dist/bin/jpi-spinner.js +7 -0
- package/dist/bin/spinner.js +9 -0
- package/package.json +1 -1
|
@@ -285,6 +285,7 @@ function decrypt(blob, key) {
|
|
|
285
285
|
var TERMINALHIRE_DIR3 = process.env.TERMINALHIRE_DIR || join3(homedir3(), ".terminalhire");
|
|
286
286
|
var CLAIM_PUSH_AUTO_MARKER = join3(TERMINALHIRE_DIR3, "claim-push-auto.json");
|
|
287
287
|
var CLAIM_PUSH_TOKEN_FILE = join3(TERMINALHIRE_DIR3, "claim-push-token.enc");
|
|
288
|
+
var CLAIM_PUSH_MANUAL_MARKER = join3(TERMINALHIRE_DIR3, "claim-push.json");
|
|
288
289
|
var CLAIM_SYNC_BASE = "https://terminalhire.com";
|
|
289
290
|
var AUTO_CONSENT_VERSION = 2;
|
|
290
291
|
var AUTO_PUSH_THROTTLE_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -352,6 +353,43 @@ function backgroundPushGate(params) {
|
|
|
352
353
|
}
|
|
353
354
|
return { push: true, reason: "ok" };
|
|
354
355
|
}
|
|
356
|
+
function unpushedNudgeGate(params) {
|
|
357
|
+
const {
|
|
358
|
+
autoMarkerExists,
|
|
359
|
+
tokenFileExists,
|
|
360
|
+
manualMarkerExists,
|
|
361
|
+
lastSnapshotHash,
|
|
362
|
+
currentHash,
|
|
363
|
+
claimCount
|
|
364
|
+
} = params;
|
|
365
|
+
if (autoMarkerExists && tokenFileExists) return false;
|
|
366
|
+
if (!manualMarkerExists) return false;
|
|
367
|
+
if (!(claimCount > 0)) return false;
|
|
368
|
+
return lastSnapshotHash !== currentHash;
|
|
369
|
+
}
|
|
370
|
+
async function shouldNudgeUnpushed() {
|
|
371
|
+
try {
|
|
372
|
+
const { listClaims: listClaims2, toPushedClaim: toPushedClaim2 } = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
373
|
+
const pushed = listClaims2().map((c) => toPushedClaim2(c));
|
|
374
|
+
const currentHash = computeSnapshotHash(pushed);
|
|
375
|
+
let manual = null;
|
|
376
|
+
try {
|
|
377
|
+
manual = existsSync3(CLAIM_PUSH_MANUAL_MARKER) ? JSON.parse(readFileSync3(CLAIM_PUSH_MANUAL_MARKER, "utf8")) : null;
|
|
378
|
+
} catch {
|
|
379
|
+
manual = null;
|
|
380
|
+
}
|
|
381
|
+
return unpushedNudgeGate({
|
|
382
|
+
autoMarkerExists: existsSync3(CLAIM_PUSH_AUTO_MARKER),
|
|
383
|
+
tokenFileExists: existsSync3(CLAIM_PUSH_TOKEN_FILE),
|
|
384
|
+
manualMarkerExists: !!manual,
|
|
385
|
+
lastSnapshotHash: manual?.lastSnapshotHash ?? null,
|
|
386
|
+
currentHash,
|
|
387
|
+
claimCount: pushed.length
|
|
388
|
+
});
|
|
389
|
+
} catch {
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
355
393
|
async function runBackgroundClaimPush({ now = Date.now() } = {}) {
|
|
356
394
|
try {
|
|
357
395
|
if (!existsSync3(CLAIM_PUSH_AUTO_MARKER) || !existsSync3(CLAIM_PUSH_TOKEN_FILE)) return;
|
|
@@ -396,6 +434,7 @@ export {
|
|
|
396
434
|
AUTO_CONSENT_VERSION,
|
|
397
435
|
AUTO_PUSH_THROTTLE_MS,
|
|
398
436
|
CLAIM_PUSH_AUTO_MARKER,
|
|
437
|
+
CLAIM_PUSH_MANUAL_MARKER,
|
|
399
438
|
CLAIM_PUSH_TOKEN_FILE,
|
|
400
439
|
backgroundPushGate,
|
|
401
440
|
clearAutoMarker,
|
|
@@ -404,6 +443,8 @@ export {
|
|
|
404
443
|
readAutoMarker,
|
|
405
444
|
readPushTokenEnc,
|
|
406
445
|
runBackgroundClaimPush,
|
|
446
|
+
shouldNudgeUnpushed,
|
|
447
|
+
unpushedNudgeGate,
|
|
407
448
|
writeAutoMarker,
|
|
408
449
|
writePushTokenEnc
|
|
409
450
|
};
|
package/dist/bin/jpi-claim.js
CHANGED
|
@@ -8616,6 +8616,7 @@ function encrypt(plaintext, key) {
|
|
|
8616
8616
|
var TERMINALHIRE_DIR3 = process.env.TERMINALHIRE_DIR || join4(homedir3(), ".terminalhire");
|
|
8617
8617
|
var CLAIM_PUSH_AUTO_MARKER = join4(TERMINALHIRE_DIR3, "claim-push-auto.json");
|
|
8618
8618
|
var CLAIM_PUSH_TOKEN_FILE = join4(TERMINALHIRE_DIR3, "claim-push-token.enc");
|
|
8619
|
+
var CLAIM_PUSH_MANUAL_MARKER = join4(TERMINALHIRE_DIR3, "claim-push.json");
|
|
8619
8620
|
var AUTO_CONSENT_VERSION = 2;
|
|
8620
8621
|
var AUTO_PUSH_THROTTLE_MS = 24 * 60 * 60 * 1e3;
|
|
8621
8622
|
async function writePushTokenEnc(rawToken) {
|
|
@@ -8643,6 +8644,43 @@ function clearAutoMarker() {
|
|
|
8643
8644
|
function computeSnapshotHash(pushed) {
|
|
8644
8645
|
return createHash3("sha256").update(JSON.stringify(pushed)).digest("hex");
|
|
8645
8646
|
}
|
|
8647
|
+
function unpushedNudgeGate(params) {
|
|
8648
|
+
const {
|
|
8649
|
+
autoMarkerExists,
|
|
8650
|
+
tokenFileExists,
|
|
8651
|
+
manualMarkerExists,
|
|
8652
|
+
lastSnapshotHash,
|
|
8653
|
+
currentHash,
|
|
8654
|
+
claimCount
|
|
8655
|
+
} = params;
|
|
8656
|
+
if (autoMarkerExists && tokenFileExists) return false;
|
|
8657
|
+
if (!manualMarkerExists) return false;
|
|
8658
|
+
if (!(claimCount > 0)) return false;
|
|
8659
|
+
return lastSnapshotHash !== currentHash;
|
|
8660
|
+
}
|
|
8661
|
+
async function shouldNudgeUnpushed() {
|
|
8662
|
+
try {
|
|
8663
|
+
const { listClaims: listClaims2, toPushedClaim: toPushedClaim2 } = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
8664
|
+
const pushed = listClaims2().map((c) => toPushedClaim2(c));
|
|
8665
|
+
const currentHash = computeSnapshotHash(pushed);
|
|
8666
|
+
let manual = null;
|
|
8667
|
+
try {
|
|
8668
|
+
manual = existsSync3(CLAIM_PUSH_MANUAL_MARKER) ? JSON.parse(readFileSync4(CLAIM_PUSH_MANUAL_MARKER, "utf8")) : null;
|
|
8669
|
+
} catch {
|
|
8670
|
+
manual = null;
|
|
8671
|
+
}
|
|
8672
|
+
return unpushedNudgeGate({
|
|
8673
|
+
autoMarkerExists: existsSync3(CLAIM_PUSH_AUTO_MARKER),
|
|
8674
|
+
tokenFileExists: existsSync3(CLAIM_PUSH_TOKEN_FILE),
|
|
8675
|
+
manualMarkerExists: !!manual,
|
|
8676
|
+
lastSnapshotHash: manual?.lastSnapshotHash ?? null,
|
|
8677
|
+
currentHash,
|
|
8678
|
+
claimCount: pushed.length
|
|
8679
|
+
});
|
|
8680
|
+
} catch {
|
|
8681
|
+
return false;
|
|
8682
|
+
}
|
|
8683
|
+
}
|
|
8646
8684
|
|
|
8647
8685
|
// bin/jpi-claim.js
|
|
8648
8686
|
var TERMINALHIRE_DIR6 = process.env.TERMINALHIRE_DIR || join7(homedir6(), ".terminalhire");
|
|
@@ -8882,6 +8920,10 @@ ${p.body ?? ""}`)) {
|
|
|
8882
8920
|
}
|
|
8883
8921
|
return matched;
|
|
8884
8922
|
}
|
|
8923
|
+
function selectCompetingPrs(prs, selfLogin) {
|
|
8924
|
+
if (!Array.isArray(prs)) return [];
|
|
8925
|
+
return prs.filter((pr) => pr && pr.author !== selfLogin);
|
|
8926
|
+
}
|
|
8885
8927
|
async function listOpenPRsReferencingIssue(repoFullName, issueNumber) {
|
|
8886
8928
|
try {
|
|
8887
8929
|
const res = await fetch(`${GH_API2}/repos/${repoFullName}/pulls?state=open&per_page=100`, {
|
|
@@ -8947,6 +8989,67 @@ async function fetchIssue(repoFullName, issueNumber) {
|
|
|
8947
8989
|
return null;
|
|
8948
8990
|
}
|
|
8949
8991
|
}
|
|
8992
|
+
var ASSIGNMENT_MARKER = "<!-- terminalhire:assignment-request -->";
|
|
8993
|
+
var TAKE_BOT_REPOS = /* @__PURE__ */ new Set(["paradedb/paradedb"]);
|
|
8994
|
+
function buildAssignmentComment(repoFullName) {
|
|
8995
|
+
const usesTakeBot = TAKE_BOT_REPOS.has(repoFullName.toLowerCase());
|
|
8996
|
+
const body = usesTakeBot ? "/take" : `I'd like to work on this \u2014 could I be assigned? Thanks!
|
|
8997
|
+
|
|
8998
|
+
${ASSIGNMENT_MARKER}`;
|
|
8999
|
+
return { usesTakeBot, body };
|
|
9000
|
+
}
|
|
9001
|
+
async function hasPriorAssignmentRequest(repoFullName, issueNumber, login) {
|
|
9002
|
+
try {
|
|
9003
|
+
const res = await fetch(
|
|
9004
|
+
`${GH_API2}/repos/${repoFullName}/issues/${issueNumber}/comments?per_page=100&sort=created&direction=desc`,
|
|
9005
|
+
{ headers: GH_HEADERS2, signal: AbortSignal.timeout(1e4) }
|
|
9006
|
+
);
|
|
9007
|
+
if (!res.ok) return false;
|
|
9008
|
+
const comments = await res.json();
|
|
9009
|
+
if (!Array.isArray(comments)) return false;
|
|
9010
|
+
return comments.some(
|
|
9011
|
+
(c) => c && c.user && c.user.login === login && typeof c.body === "string" && c.body.includes(ASSIGNMENT_MARKER)
|
|
9012
|
+
);
|
|
9013
|
+
} catch {
|
|
9014
|
+
return false;
|
|
9015
|
+
}
|
|
9016
|
+
}
|
|
9017
|
+
async function requestIssueAssignment(claim, flags = {}, ghUser) {
|
|
9018
|
+
if (flags["no-assign"]) {
|
|
9019
|
+
console.log(" (--no-assign \u2014 skipping the assignment request; request it manually before working)");
|
|
9020
|
+
return;
|
|
9021
|
+
}
|
|
9022
|
+
const parsed = parseGitHubUrl(claim.issueUrl);
|
|
9023
|
+
if (!parsed) return;
|
|
9024
|
+
const { repoFullName, number } = parsed;
|
|
9025
|
+
let login = ghUser;
|
|
9026
|
+
if (!login) {
|
|
9027
|
+
try {
|
|
9028
|
+
login = await sh("gh", ["api", "user", "-q", ".login"]);
|
|
9029
|
+
} catch {
|
|
9030
|
+
console.log(" (assignment not requested: 'gh' not authenticated \u2014 comment on the issue manually before working)");
|
|
9031
|
+
return;
|
|
9032
|
+
}
|
|
9033
|
+
}
|
|
9034
|
+
const issue = await fetchIssue(repoFullName, number);
|
|
9035
|
+
if (issue && issue.assignees.includes(login)) {
|
|
9036
|
+
console.log(` \u2713 Already assigned to @${login} on ${repoFullName}#${number}.`);
|
|
9037
|
+
return;
|
|
9038
|
+
}
|
|
9039
|
+
const { usesTakeBot, body } = buildAssignmentComment(repoFullName);
|
|
9040
|
+
if (!usesTakeBot && await hasPriorAssignmentRequest(repoFullName, number, login)) {
|
|
9041
|
+
console.log(` \u2713 Assignment already requested on ${repoFullName}#${number}.`);
|
|
9042
|
+
return;
|
|
9043
|
+
}
|
|
9044
|
+
try {
|
|
9045
|
+
await sh("gh", ["issue", "comment", String(number), "--repo", repoFullName, "--body", body]);
|
|
9046
|
+
console.log(
|
|
9047
|
+
usesTakeBot ? ` \u2713 Requested assignment via /take on ${repoFullName}#${number}.` : ` \u2713 Requested assignment on ${repoFullName}#${number}.`
|
|
9048
|
+
);
|
|
9049
|
+
} catch (err) {
|
|
9050
|
+
console.log(` (could not post the assignment request: ${err.stderr || err.message || err} \u2014 do it manually before working)`);
|
|
9051
|
+
}
|
|
9052
|
+
}
|
|
8950
9053
|
async function pollPR(prUrl) {
|
|
8951
9054
|
const p = parseGitHubUrl(prUrl);
|
|
8952
9055
|
if (!p) return null;
|
|
@@ -9237,6 +9340,12 @@ ${list.length} ${active ? "active " : ""}claim${list.length === 1 ? "" : "s"}:
|
|
|
9237
9340
|
console.log(` id: ${c.id}${pr}`);
|
|
9238
9341
|
}
|
|
9239
9342
|
printMetric(claims.acceptedPRRate());
|
|
9343
|
+
try {
|
|
9344
|
+
if (await shouldNudgeUnpushed()) {
|
|
9345
|
+
console.log("\n \u26A0 new claims not yet on your dashboard \u2014 run: terminalhire claim --push --keep-updated");
|
|
9346
|
+
}
|
|
9347
|
+
} catch {
|
|
9348
|
+
}
|
|
9240
9349
|
}
|
|
9241
9350
|
async function cmdStatus(id) {
|
|
9242
9351
|
const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
@@ -9436,7 +9545,7 @@ When it's done: terminalhire claim submit ${id}`);
|
|
|
9436
9545
|
}
|
|
9437
9546
|
}
|
|
9438
9547
|
if (flags.here) {
|
|
9439
|
-
await cmdStartHere(claims, claim);
|
|
9548
|
+
await cmdStartHere(claims, claim, flags);
|
|
9440
9549
|
return;
|
|
9441
9550
|
}
|
|
9442
9551
|
let ghUser;
|
|
@@ -9498,6 +9607,7 @@ terminalhire claim: not started \u2014 starting forks ${claim.repoFullName} to y
|
|
|
9498
9607
|
await sh("git", ["-C", destDir, "checkout", "-b", branch]);
|
|
9499
9608
|
const toplevel = await sh("git", ["-C", destDir, "rev-parse", "--show-toplevel"]);
|
|
9500
9609
|
claims.updateClaim(id, { worktreePath: toplevel, branch, state: "working" });
|
|
9610
|
+
await requestIssueAssignment(claim, flags, ghUser);
|
|
9501
9611
|
console.log(`
|
|
9502
9612
|
\u2713 Started: ${claim.title}`);
|
|
9503
9613
|
console.log(` fork: ${forkFullName}`);
|
|
@@ -9507,7 +9617,7 @@ terminalhire claim: not started \u2014 starting forks ${claim.repoFullName} to y
|
|
|
9507
9617
|
console.log("\n When the work is done (the only step that pushes + opens the PR):");
|
|
9508
9618
|
console.log(` terminalhire claim submit ${id}`);
|
|
9509
9619
|
}
|
|
9510
|
-
async function cmdStartHere(claims, claim) {
|
|
9620
|
+
async function cmdStartHere(claims, claim, flags = {}) {
|
|
9511
9621
|
let toplevel;
|
|
9512
9622
|
try {
|
|
9513
9623
|
toplevel = await sh("git", ["-C", process.cwd(), "rev-parse", "--show-toplevel"]);
|
|
@@ -9537,6 +9647,7 @@ async function cmdStartHere(claims, claim) {
|
|
|
9537
9647
|
const branch = startBranchFor(claim.repoFullName, issueNumber);
|
|
9538
9648
|
await sh("git", ["-C", toplevel, "checkout", "-b", branch]);
|
|
9539
9649
|
claims.updateClaim(claim.id, { worktreePath: toplevel, branch, state: "working" });
|
|
9650
|
+
await requestIssueAssignment(claim, flags);
|
|
9540
9651
|
console.log(`
|
|
9541
9652
|
\u2713 Started here: ${claim.title}`);
|
|
9542
9653
|
console.log(` worktree: ${toplevel}`);
|
|
@@ -9768,10 +9879,22 @@ async function cmdSubmit(id, flags = {}) {
|
|
|
9768
9879
|
const issueNo = (parseGitHubUrl(claim.issueUrl) || {}).number;
|
|
9769
9880
|
const contention = issueNo ? await listOpenPRsReferencingIssue(claim.repoFullName, issueNo) : null;
|
|
9770
9881
|
if (contention && contention.total > 0) {
|
|
9771
|
-
console.log(` \u26A0 contention: ${contention.total} open PR(s) already reference this issue:`);
|
|
9772
9882
|
const nowMs = Date.now();
|
|
9883
|
+
console.log(` \u26A0 contention: ${contention.total} open PR(s) already reference this issue:`);
|
|
9773
9884
|
for (const pr of contention.prs) console.log(fmtContentionPr(pr, nowMs, false));
|
|
9774
9885
|
console.log(CONTENTION_HINT);
|
|
9886
|
+
const competing = selectCompetingPrs(contention.prs, ghUser);
|
|
9887
|
+
if (competing.length > 0 && !flags["force-competing"]) {
|
|
9888
|
+
console.error(
|
|
9889
|
+
`
|
|
9890
|
+
terminalhire claim: refusing to submit \u2014 ${competing.length} open PR(s) by someone else already address ${claim.repoFullName}#${issueNo}:`
|
|
9891
|
+
);
|
|
9892
|
+
for (const pr of competing) console.error(fmtContentionPr(pr, nowMs, false));
|
|
9893
|
+
console.error(
|
|
9894
|
+
"\n Opening a competing PR reads as low-effort duplication and burns maintainer goodwill.\n Stand down, or add value on the existing PR instead (a review, a test, a comment).\n If you are certain yours should still go up, re-run with --force-competing."
|
|
9895
|
+
);
|
|
9896
|
+
process.exit(1);
|
|
9897
|
+
}
|
|
9775
9898
|
}
|
|
9776
9899
|
}
|
|
9777
9900
|
let ok;
|
|
@@ -10039,7 +10162,13 @@ async function cmdPush({ keepUpdated = false } = {}) {
|
|
|
10039
10162
|
pushToken = body?.pushToken || null;
|
|
10040
10163
|
} catch {
|
|
10041
10164
|
}
|
|
10042
|
-
writeClaimPushMarker({
|
|
10165
|
+
writeClaimPushMarker({
|
|
10166
|
+
consentedAt,
|
|
10167
|
+
login,
|
|
10168
|
+
deleteToken,
|
|
10169
|
+
lastPushedAt: consentedAt,
|
|
10170
|
+
lastSnapshotHash: computeSnapshotHash(pushed)
|
|
10171
|
+
});
|
|
10043
10172
|
if (autoConsent && pushToken) {
|
|
10044
10173
|
try {
|
|
10045
10174
|
await writePushTokenEnc(pushToken);
|
|
@@ -10187,6 +10316,7 @@ export {
|
|
|
10187
10316
|
AI_DISCLOSURE_NOTE,
|
|
10188
10317
|
CLAIM_CONSENT_VERSION,
|
|
10189
10318
|
backgroundEnableFailed,
|
|
10319
|
+
buildAssignmentComment,
|
|
10190
10320
|
buildSubmitBody,
|
|
10191
10321
|
cmdRecord,
|
|
10192
10322
|
countOpenPRsReferencingIssue,
|
|
@@ -10207,6 +10337,7 @@ export {
|
|
|
10207
10337
|
resolveSubmitWorktree,
|
|
10208
10338
|
revokeFailureAction,
|
|
10209
10339
|
run,
|
|
10340
|
+
selectCompetingPrs,
|
|
10210
10341
|
selectPushRemote,
|
|
10211
10342
|
startBranchFor,
|
|
10212
10343
|
workDirFor
|
package/dist/bin/jpi-dispatch.js
CHANGED
|
@@ -10811,6 +10811,7 @@ __export(claim_push_bg_exports, {
|
|
|
10811
10811
|
AUTO_CONSENT_VERSION: () => AUTO_CONSENT_VERSION,
|
|
10812
10812
|
AUTO_PUSH_THROTTLE_MS: () => AUTO_PUSH_THROTTLE_MS,
|
|
10813
10813
|
CLAIM_PUSH_AUTO_MARKER: () => CLAIM_PUSH_AUTO_MARKER,
|
|
10814
|
+
CLAIM_PUSH_MANUAL_MARKER: () => CLAIM_PUSH_MANUAL_MARKER,
|
|
10814
10815
|
CLAIM_PUSH_TOKEN_FILE: () => CLAIM_PUSH_TOKEN_FILE,
|
|
10815
10816
|
backgroundPushGate: () => backgroundPushGate,
|
|
10816
10817
|
clearAutoMarker: () => clearAutoMarker,
|
|
@@ -10819,6 +10820,8 @@ __export(claim_push_bg_exports, {
|
|
|
10819
10820
|
readAutoMarker: () => readAutoMarker,
|
|
10820
10821
|
readPushTokenEnc: () => readPushTokenEnc,
|
|
10821
10822
|
runBackgroundClaimPush: () => runBackgroundClaimPush,
|
|
10823
|
+
shouldNudgeUnpushed: () => shouldNudgeUnpushed,
|
|
10824
|
+
unpushedNudgeGate: () => unpushedNudgeGate,
|
|
10822
10825
|
writeAutoMarker: () => writeAutoMarker,
|
|
10823
10826
|
writePushTokenEnc: () => writePushTokenEnc
|
|
10824
10827
|
});
|
|
@@ -10890,6 +10893,43 @@ function backgroundPushGate(params) {
|
|
|
10890
10893
|
}
|
|
10891
10894
|
return { push: true, reason: "ok" };
|
|
10892
10895
|
}
|
|
10896
|
+
function unpushedNudgeGate(params) {
|
|
10897
|
+
const {
|
|
10898
|
+
autoMarkerExists,
|
|
10899
|
+
tokenFileExists,
|
|
10900
|
+
manualMarkerExists,
|
|
10901
|
+
lastSnapshotHash,
|
|
10902
|
+
currentHash,
|
|
10903
|
+
claimCount
|
|
10904
|
+
} = params;
|
|
10905
|
+
if (autoMarkerExists && tokenFileExists) return false;
|
|
10906
|
+
if (!manualMarkerExists) return false;
|
|
10907
|
+
if (!(claimCount > 0)) return false;
|
|
10908
|
+
return lastSnapshotHash !== currentHash;
|
|
10909
|
+
}
|
|
10910
|
+
async function shouldNudgeUnpushed() {
|
|
10911
|
+
try {
|
|
10912
|
+
const { listClaims: listClaims2, toPushedClaim: toPushedClaim2 } = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
10913
|
+
const pushed = listClaims2().map((c) => toPushedClaim2(c));
|
|
10914
|
+
const currentHash = computeSnapshotHash(pushed);
|
|
10915
|
+
let manual = null;
|
|
10916
|
+
try {
|
|
10917
|
+
manual = existsSync9(CLAIM_PUSH_MANUAL_MARKER) ? JSON.parse(readFileSync16(CLAIM_PUSH_MANUAL_MARKER, "utf8")) : null;
|
|
10918
|
+
} catch {
|
|
10919
|
+
manual = null;
|
|
10920
|
+
}
|
|
10921
|
+
return unpushedNudgeGate({
|
|
10922
|
+
autoMarkerExists: existsSync9(CLAIM_PUSH_AUTO_MARKER),
|
|
10923
|
+
tokenFileExists: existsSync9(CLAIM_PUSH_TOKEN_FILE),
|
|
10924
|
+
manualMarkerExists: !!manual,
|
|
10925
|
+
lastSnapshotHash: manual?.lastSnapshotHash ?? null,
|
|
10926
|
+
currentHash,
|
|
10927
|
+
claimCount: pushed.length
|
|
10928
|
+
});
|
|
10929
|
+
} catch {
|
|
10930
|
+
return false;
|
|
10931
|
+
}
|
|
10932
|
+
}
|
|
10893
10933
|
async function runBackgroundClaimPush({ now = Date.now() } = {}) {
|
|
10894
10934
|
try {
|
|
10895
10935
|
if (!existsSync9(CLAIM_PUSH_AUTO_MARKER) || !existsSync9(CLAIM_PUSH_TOKEN_FILE)) return;
|
|
@@ -10930,7 +10970,7 @@ async function runBackgroundClaimPush({ now = Date.now() } = {}) {
|
|
|
10930
10970
|
} catch {
|
|
10931
10971
|
}
|
|
10932
10972
|
}
|
|
10933
|
-
var TERMINALHIRE_DIR12, CLAIM_PUSH_AUTO_MARKER, CLAIM_PUSH_TOKEN_FILE, CLAIM_SYNC_BASE, AUTO_CONSENT_VERSION, AUTO_PUSH_THROTTLE_MS;
|
|
10973
|
+
var TERMINALHIRE_DIR12, CLAIM_PUSH_AUTO_MARKER, CLAIM_PUSH_TOKEN_FILE, CLAIM_PUSH_MANUAL_MARKER, CLAIM_SYNC_BASE, AUTO_CONSENT_VERSION, AUTO_PUSH_THROTTLE_MS;
|
|
10934
10974
|
var init_claim_push_bg = __esm({
|
|
10935
10975
|
"bin/claim-push-bg.js"() {
|
|
10936
10976
|
"use strict";
|
|
@@ -10938,6 +10978,7 @@ var init_claim_push_bg = __esm({
|
|
|
10938
10978
|
TERMINALHIRE_DIR12 = process.env.TERMINALHIRE_DIR || join16(homedir14(), ".terminalhire");
|
|
10939
10979
|
CLAIM_PUSH_AUTO_MARKER = join16(TERMINALHIRE_DIR12, "claim-push-auto.json");
|
|
10940
10980
|
CLAIM_PUSH_TOKEN_FILE = join16(TERMINALHIRE_DIR12, "claim-push-token.enc");
|
|
10981
|
+
CLAIM_PUSH_MANUAL_MARKER = join16(TERMINALHIRE_DIR12, "claim-push.json");
|
|
10941
10982
|
CLAIM_SYNC_BASE = "https://terminalhire.com";
|
|
10942
10983
|
AUTO_CONSENT_VERSION = 2;
|
|
10943
10984
|
AUTO_PUSH_THROTTLE_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -11035,6 +11076,7 @@ __export(jpi_claim_exports, {
|
|
|
11035
11076
|
AI_DISCLOSURE_NOTE: () => AI_DISCLOSURE_NOTE,
|
|
11036
11077
|
CLAIM_CONSENT_VERSION: () => CLAIM_CONSENT_VERSION,
|
|
11037
11078
|
backgroundEnableFailed: () => backgroundEnableFailed,
|
|
11079
|
+
buildAssignmentComment: () => buildAssignmentComment,
|
|
11038
11080
|
buildSubmitBody: () => buildSubmitBody,
|
|
11039
11081
|
cmdRecord: () => cmdRecord,
|
|
11040
11082
|
countOpenPRsReferencingIssue: () => countOpenPRsReferencingIssue,
|
|
@@ -11055,6 +11097,7 @@ __export(jpi_claim_exports, {
|
|
|
11055
11097
|
resolveSubmitWorktree: () => resolveSubmitWorktree,
|
|
11056
11098
|
revokeFailureAction: () => revokeFailureAction,
|
|
11057
11099
|
run: () => run7,
|
|
11100
|
+
selectCompetingPrs: () => selectCompetingPrs,
|
|
11058
11101
|
selectPushRemote: () => selectPushRemote,
|
|
11059
11102
|
startBranchFor: () => startBranchFor,
|
|
11060
11103
|
workDirFor: () => workDirFor
|
|
@@ -11288,6 +11331,10 @@ ${p.body ?? ""}`)) {
|
|
|
11288
11331
|
}
|
|
11289
11332
|
return matched;
|
|
11290
11333
|
}
|
|
11334
|
+
function selectCompetingPrs(prs, selfLogin) {
|
|
11335
|
+
if (!Array.isArray(prs)) return [];
|
|
11336
|
+
return prs.filter((pr) => pr && pr.author !== selfLogin);
|
|
11337
|
+
}
|
|
11291
11338
|
async function listOpenPRsReferencingIssue(repoFullName, issueNumber) {
|
|
11292
11339
|
try {
|
|
11293
11340
|
const res = await fetch(`${GH_API2}/repos/${repoFullName}/pulls?state=open&per_page=100`, {
|
|
@@ -11353,6 +11400,65 @@ async function fetchIssue(repoFullName, issueNumber) {
|
|
|
11353
11400
|
return null;
|
|
11354
11401
|
}
|
|
11355
11402
|
}
|
|
11403
|
+
function buildAssignmentComment(repoFullName) {
|
|
11404
|
+
const usesTakeBot = TAKE_BOT_REPOS.has(repoFullName.toLowerCase());
|
|
11405
|
+
const body = usesTakeBot ? "/take" : `I'd like to work on this \u2014 could I be assigned? Thanks!
|
|
11406
|
+
|
|
11407
|
+
${ASSIGNMENT_MARKER}`;
|
|
11408
|
+
return { usesTakeBot, body };
|
|
11409
|
+
}
|
|
11410
|
+
async function hasPriorAssignmentRequest(repoFullName, issueNumber, login) {
|
|
11411
|
+
try {
|
|
11412
|
+
const res = await fetch(
|
|
11413
|
+
`${GH_API2}/repos/${repoFullName}/issues/${issueNumber}/comments?per_page=100&sort=created&direction=desc`,
|
|
11414
|
+
{ headers: GH_HEADERS2, signal: AbortSignal.timeout(1e4) }
|
|
11415
|
+
);
|
|
11416
|
+
if (!res.ok) return false;
|
|
11417
|
+
const comments = await res.json();
|
|
11418
|
+
if (!Array.isArray(comments)) return false;
|
|
11419
|
+
return comments.some(
|
|
11420
|
+
(c) => c && c.user && c.user.login === login && typeof c.body === "string" && c.body.includes(ASSIGNMENT_MARKER)
|
|
11421
|
+
);
|
|
11422
|
+
} catch {
|
|
11423
|
+
return false;
|
|
11424
|
+
}
|
|
11425
|
+
}
|
|
11426
|
+
async function requestIssueAssignment(claim, flags = {}, ghUser) {
|
|
11427
|
+
if (flags["no-assign"]) {
|
|
11428
|
+
console.log(" (--no-assign \u2014 skipping the assignment request; request it manually before working)");
|
|
11429
|
+
return;
|
|
11430
|
+
}
|
|
11431
|
+
const parsed = parseGitHubUrl(claim.issueUrl);
|
|
11432
|
+
if (!parsed) return;
|
|
11433
|
+
const { repoFullName, number: number3 } = parsed;
|
|
11434
|
+
let login = ghUser;
|
|
11435
|
+
if (!login) {
|
|
11436
|
+
try {
|
|
11437
|
+
login = await sh("gh", ["api", "user", "-q", ".login"]);
|
|
11438
|
+
} catch {
|
|
11439
|
+
console.log(" (assignment not requested: 'gh' not authenticated \u2014 comment on the issue manually before working)");
|
|
11440
|
+
return;
|
|
11441
|
+
}
|
|
11442
|
+
}
|
|
11443
|
+
const issue2 = await fetchIssue(repoFullName, number3);
|
|
11444
|
+
if (issue2 && issue2.assignees.includes(login)) {
|
|
11445
|
+
console.log(` \u2713 Already assigned to @${login} on ${repoFullName}#${number3}.`);
|
|
11446
|
+
return;
|
|
11447
|
+
}
|
|
11448
|
+
const { usesTakeBot, body } = buildAssignmentComment(repoFullName);
|
|
11449
|
+
if (!usesTakeBot && await hasPriorAssignmentRequest(repoFullName, number3, login)) {
|
|
11450
|
+
console.log(` \u2713 Assignment already requested on ${repoFullName}#${number3}.`);
|
|
11451
|
+
return;
|
|
11452
|
+
}
|
|
11453
|
+
try {
|
|
11454
|
+
await sh("gh", ["issue", "comment", String(number3), "--repo", repoFullName, "--body", body]);
|
|
11455
|
+
console.log(
|
|
11456
|
+
usesTakeBot ? ` \u2713 Requested assignment via /take on ${repoFullName}#${number3}.` : ` \u2713 Requested assignment on ${repoFullName}#${number3}.`
|
|
11457
|
+
);
|
|
11458
|
+
} catch (err) {
|
|
11459
|
+
console.log(` (could not post the assignment request: ${err.stderr || err.message || err} \u2014 do it manually before working)`);
|
|
11460
|
+
}
|
|
11461
|
+
}
|
|
11356
11462
|
async function pollPR(prUrl) {
|
|
11357
11463
|
const p = parseGitHubUrl(prUrl);
|
|
11358
11464
|
if (!p) return null;
|
|
@@ -11643,6 +11749,12 @@ ${list.length} ${active ? "active " : ""}claim${list.length === 1 ? "" : "s"}:
|
|
|
11643
11749
|
console.log(` id: ${c.id}${pr}`);
|
|
11644
11750
|
}
|
|
11645
11751
|
printMetric(claims.acceptedPRRate());
|
|
11752
|
+
try {
|
|
11753
|
+
if (await shouldNudgeUnpushed()) {
|
|
11754
|
+
console.log("\n \u26A0 new claims not yet on your dashboard \u2014 run: terminalhire claim --push --keep-updated");
|
|
11755
|
+
}
|
|
11756
|
+
} catch {
|
|
11757
|
+
}
|
|
11646
11758
|
}
|
|
11647
11759
|
async function cmdStatus(id) {
|
|
11648
11760
|
const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
@@ -11842,7 +11954,7 @@ When it's done: terminalhire claim submit ${id}`);
|
|
|
11842
11954
|
}
|
|
11843
11955
|
}
|
|
11844
11956
|
if (flags.here) {
|
|
11845
|
-
await cmdStartHere(claims, claim);
|
|
11957
|
+
await cmdStartHere(claims, claim, flags);
|
|
11846
11958
|
return;
|
|
11847
11959
|
}
|
|
11848
11960
|
let ghUser;
|
|
@@ -11904,6 +12016,7 @@ terminalhire claim: not started \u2014 starting forks ${claim.repoFullName} to y
|
|
|
11904
12016
|
await sh("git", ["-C", destDir, "checkout", "-b", branch]);
|
|
11905
12017
|
const toplevel = await sh("git", ["-C", destDir, "rev-parse", "--show-toplevel"]);
|
|
11906
12018
|
claims.updateClaim(id, { worktreePath: toplevel, branch, state: "working" });
|
|
12019
|
+
await requestIssueAssignment(claim, flags, ghUser);
|
|
11907
12020
|
console.log(`
|
|
11908
12021
|
\u2713 Started: ${claim.title}`);
|
|
11909
12022
|
console.log(` fork: ${forkFullName}`);
|
|
@@ -11913,7 +12026,7 @@ terminalhire claim: not started \u2014 starting forks ${claim.repoFullName} to y
|
|
|
11913
12026
|
console.log("\n When the work is done (the only step that pushes + opens the PR):");
|
|
11914
12027
|
console.log(` terminalhire claim submit ${id}`);
|
|
11915
12028
|
}
|
|
11916
|
-
async function cmdStartHere(claims, claim) {
|
|
12029
|
+
async function cmdStartHere(claims, claim, flags = {}) {
|
|
11917
12030
|
let toplevel;
|
|
11918
12031
|
try {
|
|
11919
12032
|
toplevel = await sh("git", ["-C", process.cwd(), "rev-parse", "--show-toplevel"]);
|
|
@@ -11943,6 +12056,7 @@ async function cmdStartHere(claims, claim) {
|
|
|
11943
12056
|
const branch = startBranchFor(claim.repoFullName, issueNumber);
|
|
11944
12057
|
await sh("git", ["-C", toplevel, "checkout", "-b", branch]);
|
|
11945
12058
|
claims.updateClaim(claim.id, { worktreePath: toplevel, branch, state: "working" });
|
|
12059
|
+
await requestIssueAssignment(claim, flags);
|
|
11946
12060
|
console.log(`
|
|
11947
12061
|
\u2713 Started here: ${claim.title}`);
|
|
11948
12062
|
console.log(` worktree: ${toplevel}`);
|
|
@@ -12174,10 +12288,22 @@ async function cmdSubmit(id, flags = {}) {
|
|
|
12174
12288
|
const issueNo = (parseGitHubUrl(claim.issueUrl) || {}).number;
|
|
12175
12289
|
const contention = issueNo ? await listOpenPRsReferencingIssue(claim.repoFullName, issueNo) : null;
|
|
12176
12290
|
if (contention && contention.total > 0) {
|
|
12177
|
-
console.log(` \u26A0 contention: ${contention.total} open PR(s) already reference this issue:`);
|
|
12178
12291
|
const nowMs = Date.now();
|
|
12292
|
+
console.log(` \u26A0 contention: ${contention.total} open PR(s) already reference this issue:`);
|
|
12179
12293
|
for (const pr of contention.prs) console.log(fmtContentionPr(pr, nowMs, false));
|
|
12180
12294
|
console.log(CONTENTION_HINT);
|
|
12295
|
+
const competing = selectCompetingPrs(contention.prs, ghUser);
|
|
12296
|
+
if (competing.length > 0 && !flags["force-competing"]) {
|
|
12297
|
+
console.error(
|
|
12298
|
+
`
|
|
12299
|
+
terminalhire claim: refusing to submit \u2014 ${competing.length} open PR(s) by someone else already address ${claim.repoFullName}#${issueNo}:`
|
|
12300
|
+
);
|
|
12301
|
+
for (const pr of competing) console.error(fmtContentionPr(pr, nowMs, false));
|
|
12302
|
+
console.error(
|
|
12303
|
+
"\n Opening a competing PR reads as low-effort duplication and burns maintainer goodwill.\n Stand down, or add value on the existing PR instead (a review, a test, a comment).\n If you are certain yours should still go up, re-run with --force-competing."
|
|
12304
|
+
);
|
|
12305
|
+
process.exit(1);
|
|
12306
|
+
}
|
|
12181
12307
|
}
|
|
12182
12308
|
}
|
|
12183
12309
|
let ok;
|
|
@@ -12445,7 +12571,13 @@ async function cmdPush({ keepUpdated = false } = {}) {
|
|
|
12445
12571
|
pushToken = body?.pushToken || null;
|
|
12446
12572
|
} catch {
|
|
12447
12573
|
}
|
|
12448
|
-
writeClaimPushMarker({
|
|
12574
|
+
writeClaimPushMarker({
|
|
12575
|
+
consentedAt,
|
|
12576
|
+
login,
|
|
12577
|
+
deleteToken,
|
|
12578
|
+
lastPushedAt: consentedAt,
|
|
12579
|
+
lastSnapshotHash: computeSnapshotHash(pushed)
|
|
12580
|
+
});
|
|
12449
12581
|
if (autoConsent && pushToken) {
|
|
12450
12582
|
try {
|
|
12451
12583
|
await writePushTokenEnc(pushToken);
|
|
@@ -12589,7 +12721,7 @@ async function run7() {
|
|
|
12589
12721
|
process.exit(1);
|
|
12590
12722
|
}
|
|
12591
12723
|
}
|
|
12592
|
-
var TERMINALHIRE_DIR13, INDEX_CACHE_FILE5, CLAIM_PUSH_MARKER, API_URL6, CLAIM_SYNC_BASE2, CLAIM_CONSENT_VERSION, CLAIM_POLL_INTERVAL_MS, CLAIM_POLL_TIMEOUT_MS, GH_API2, GH_HEADERS2, CONTENTION_HINT, AI_DISCLOSURE_NOTE, pExecFile, VALUE_FLAGS;
|
|
12724
|
+
var TERMINALHIRE_DIR13, INDEX_CACHE_FILE5, CLAIM_PUSH_MARKER, API_URL6, CLAIM_SYNC_BASE2, CLAIM_CONSENT_VERSION, CLAIM_POLL_INTERVAL_MS, CLAIM_POLL_TIMEOUT_MS, GH_API2, GH_HEADERS2, CONTENTION_HINT, AI_DISCLOSURE_NOTE, pExecFile, VALUE_FLAGS, ASSIGNMENT_MARKER, TAKE_BOT_REPOS;
|
|
12593
12725
|
var init_jpi_claim = __esm({
|
|
12594
12726
|
"bin/jpi-claim.js"() {
|
|
12595
12727
|
"use strict";
|
|
@@ -12611,6 +12743,8 @@ var init_jpi_claim = __esm({
|
|
|
12611
12743
|
AI_DISCLOSURE_NOTE = "---\nThis contribution was developed with AI assistance via [terminalhire](https://terminalhire.com). The author has reviewed the change and takes responsibility for its content.";
|
|
12612
12744
|
pExecFile = promisify(execFile);
|
|
12613
12745
|
VALUE_FLAGS = /* @__PURE__ */ new Set(["worktree", "branch", "body-file", "title"]);
|
|
12746
|
+
ASSIGNMENT_MARKER = "<!-- terminalhire:assignment-request -->";
|
|
12747
|
+
TAKE_BOT_REPOS = /* @__PURE__ */ new Set(["paradedb/paradedb"]);
|
|
12614
12748
|
}
|
|
12615
12749
|
});
|
|
12616
12750
|
|
|
@@ -41110,6 +41244,9 @@ function buildContributeNudgeLine(contributeNudge) {
|
|
|
41110
41244
|
function buildSessionStaleLine(sessionStale) {
|
|
41111
41245
|
return sessionStale === true ? "\u26A0 terminalhire: linked session expired \u2014 run: terminalhire login" : null;
|
|
41112
41246
|
}
|
|
41247
|
+
function buildUnpushedClaimsLine(unpushedClaims) {
|
|
41248
|
+
return unpushedClaims === true ? "\u26A0 new claims not yet on your dashboard \u2014 run: terminalhire claim --push --keep-updated" : null;
|
|
41249
|
+
}
|
|
41113
41250
|
function buildSpinnerPool(topMatches, max = 6, opts = {}) {
|
|
41114
41251
|
const {
|
|
41115
41252
|
sessionTags,
|
|
@@ -41118,16 +41255,19 @@ function buildSpinnerPool(topMatches, max = 6, opts = {}) {
|
|
|
41118
41255
|
incomingPending,
|
|
41119
41256
|
sessionStale,
|
|
41120
41257
|
contributeNudge,
|
|
41258
|
+
unpushedClaims,
|
|
41121
41259
|
seenHistory
|
|
41122
41260
|
} = opts;
|
|
41123
41261
|
const staleLine = buildSessionStaleLine(sessionStale);
|
|
41124
41262
|
const withStale = (pool2) => staleLine ? [staleLine, ...pool2] : pool2;
|
|
41125
41263
|
const introLine = buildIncomingIntroLine(incomingPending);
|
|
41126
41264
|
const contributeLine = buildContributeNudgeLine(contributeNudge);
|
|
41265
|
+
const unpushedLine = buildUnpushedClaimsLine(unpushedClaims);
|
|
41127
41266
|
const ranked = filterFreshMatches(rankBySessionTags(topMatches, sessionTags), seenHistory);
|
|
41128
41267
|
if (!Array.isArray(ranked) || ranked.length === 0) {
|
|
41129
41268
|
if (introLine) return withStale([introLine]);
|
|
41130
41269
|
if (contributeLine) return withStale([contributeLine]);
|
|
41270
|
+
if (unpushedLine) return withStale([unpushedLine]);
|
|
41131
41271
|
const peerLine = buildPeerLine(topPeers);
|
|
41132
41272
|
return withStale(peerLine ? [peerLine] : []);
|
|
41133
41273
|
}
|
|
@@ -41137,6 +41277,7 @@ function buildSpinnerPool(topMatches, max = 6, opts = {}) {
|
|
|
41137
41277
|
const pool = [...headers.slice(0, cap), ctaVerb()];
|
|
41138
41278
|
if (introLine) pool.push(introLine);
|
|
41139
41279
|
if (contributeLine) pool.push(contributeLine);
|
|
41280
|
+
if (unpushedLine) pool.push(unpushedLine);
|
|
41140
41281
|
return withStale(pool);
|
|
41141
41282
|
}
|
|
41142
41283
|
var init_spinner_verbs = __esm({
|
|
@@ -41276,6 +41417,7 @@ function renderRefreshSurface(topMatches, sc, opts = {}) {
|
|
|
41276
41417
|
incomingPending: opts.incomingPending,
|
|
41277
41418
|
sessionStale: opts.sessionStale,
|
|
41278
41419
|
contributeNudge: opts.contributeNudge,
|
|
41420
|
+
unpushedClaims: opts.unpushedClaims,
|
|
41279
41421
|
seenHistory
|
|
41280
41422
|
});
|
|
41281
41423
|
if (verbs.length > 0) applySpinnerVerbs(verbs, sc.mode);
|
|
@@ -41315,6 +41457,7 @@ __export(spinner_exports, {
|
|
|
41315
41457
|
buildSpinnerPool: () => buildSpinnerPool,
|
|
41316
41458
|
buildTips: () => buildTips,
|
|
41317
41459
|
buildTipsDetailed: () => buildTipsDetailed,
|
|
41460
|
+
buildUnpushedClaimsLine: () => buildUnpushedClaimsLine,
|
|
41318
41461
|
clearSpinnerTips: () => clearSpinnerTips,
|
|
41319
41462
|
clearSpinnerVerbs: () => clearSpinnerVerbs,
|
|
41320
41463
|
ctaVerb: () => ctaVerb,
|
|
@@ -41339,6 +41482,7 @@ var init_spinner = __esm({
|
|
|
41339
41482
|
init_spinner_verbs();
|
|
41340
41483
|
init_spinner_verbs();
|
|
41341
41484
|
init_spinner_verbs();
|
|
41485
|
+
init_spinner_verbs();
|
|
41342
41486
|
init_spinner_select();
|
|
41343
41487
|
init_spinner_select();
|
|
41344
41488
|
init_spinner_select();
|
|
@@ -42501,6 +42645,12 @@ async function run22() {
|
|
|
42501
42645
|
topMatches = filterFreshMatches2(topMatches, seenHistory);
|
|
42502
42646
|
} catch {
|
|
42503
42647
|
}
|
|
42648
|
+
let unpushedClaims = false;
|
|
42649
|
+
try {
|
|
42650
|
+
const { shouldNudgeUnpushed: shouldNudgeUnpushed2 } = await Promise.resolve().then(() => (init_claim_push_bg(), claim_push_bg_exports));
|
|
42651
|
+
unpushedClaims = await shouldNudgeUnpushed2();
|
|
42652
|
+
} catch {
|
|
42653
|
+
}
|
|
42504
42654
|
const cacheEntry = {
|
|
42505
42655
|
index,
|
|
42506
42656
|
matchCount,
|
|
@@ -42509,6 +42659,7 @@ async function run22() {
|
|
|
42509
42659
|
incomingPending,
|
|
42510
42660
|
unreadChat,
|
|
42511
42661
|
sessionStale,
|
|
42662
|
+
unpushedClaims,
|
|
42512
42663
|
// In-process-only despite being persisted: the render pass receives it as
|
|
42513
42664
|
// a function argument below. A future cache READER must not gate on this
|
|
42514
42665
|
// field without re-checking isContributeEnabled/isContributePrompted at
|
|
@@ -42547,6 +42698,7 @@ async function run22() {
|
|
|
42547
42698
|
incomingPending,
|
|
42548
42699
|
sessionStale,
|
|
42549
42700
|
contributeNudge,
|
|
42701
|
+
unpushedClaims,
|
|
42550
42702
|
baseUrl: API_URL8,
|
|
42551
42703
|
seenHistory,
|
|
42552
42704
|
widen,
|
package/dist/bin/jpi-refresh.js
CHANGED
|
@@ -9045,6 +9045,9 @@ function buildContributeNudgeLine(contributeNudge) {
|
|
|
9045
9045
|
function buildSessionStaleLine(sessionStale) {
|
|
9046
9046
|
return sessionStale === true ? "\u26A0 terminalhire: linked session expired \u2014 run: terminalhire login" : null;
|
|
9047
9047
|
}
|
|
9048
|
+
function buildUnpushedClaimsLine(unpushedClaims) {
|
|
9049
|
+
return unpushedClaims === true ? "\u26A0 new claims not yet on your dashboard \u2014 run: terminalhire claim --push --keep-updated" : null;
|
|
9050
|
+
}
|
|
9048
9051
|
function buildSpinnerPool(topMatches, max = 6, opts = {}) {
|
|
9049
9052
|
const {
|
|
9050
9053
|
sessionTags,
|
|
@@ -9053,16 +9056,19 @@ function buildSpinnerPool(topMatches, max = 6, opts = {}) {
|
|
|
9053
9056
|
incomingPending,
|
|
9054
9057
|
sessionStale,
|
|
9055
9058
|
contributeNudge,
|
|
9059
|
+
unpushedClaims,
|
|
9056
9060
|
seenHistory
|
|
9057
9061
|
} = opts;
|
|
9058
9062
|
const staleLine = buildSessionStaleLine(sessionStale);
|
|
9059
9063
|
const withStale = (pool2) => staleLine ? [staleLine, ...pool2] : pool2;
|
|
9060
9064
|
const introLine = buildIncomingIntroLine(incomingPending);
|
|
9061
9065
|
const contributeLine = buildContributeNudgeLine(contributeNudge);
|
|
9066
|
+
const unpushedLine = buildUnpushedClaimsLine(unpushedClaims);
|
|
9062
9067
|
const ranked = filterFreshMatches(rankBySessionTags(topMatches, sessionTags), seenHistory);
|
|
9063
9068
|
if (!Array.isArray(ranked) || ranked.length === 0) {
|
|
9064
9069
|
if (introLine) return withStale([introLine]);
|
|
9065
9070
|
if (contributeLine) return withStale([contributeLine]);
|
|
9071
|
+
if (unpushedLine) return withStale([unpushedLine]);
|
|
9066
9072
|
const peerLine = buildPeerLine(topPeers);
|
|
9067
9073
|
return withStale(peerLine ? [peerLine] : []);
|
|
9068
9074
|
}
|
|
@@ -9072,6 +9078,7 @@ function buildSpinnerPool(topMatches, max = 6, opts = {}) {
|
|
|
9072
9078
|
const pool = [...headers.slice(0, cap), ctaVerb()];
|
|
9073
9079
|
if (introLine) pool.push(introLine);
|
|
9074
9080
|
if (contributeLine) pool.push(contributeLine);
|
|
9081
|
+
if (unpushedLine) pool.push(unpushedLine);
|
|
9075
9082
|
return withStale(pool);
|
|
9076
9083
|
}
|
|
9077
9084
|
var init_spinner_verbs = __esm({
|
|
@@ -9211,6 +9218,7 @@ function renderRefreshSurface(topMatches, sc, opts = {}) {
|
|
|
9211
9218
|
incomingPending: opts.incomingPending,
|
|
9212
9219
|
sessionStale: opts.sessionStale,
|
|
9213
9220
|
contributeNudge: opts.contributeNudge,
|
|
9221
|
+
unpushedClaims: opts.unpushedClaims,
|
|
9214
9222
|
seenHistory
|
|
9215
9223
|
});
|
|
9216
9224
|
if (verbs.length > 0) applySpinnerVerbs(verbs, sc.mode);
|
|
@@ -9250,6 +9258,7 @@ __export(spinner_exports, {
|
|
|
9250
9258
|
buildSpinnerPool: () => buildSpinnerPool,
|
|
9251
9259
|
buildTips: () => buildTips,
|
|
9252
9260
|
buildTipsDetailed: () => buildTipsDetailed,
|
|
9261
|
+
buildUnpushedClaimsLine: () => buildUnpushedClaimsLine,
|
|
9253
9262
|
clearSpinnerTips: () => clearSpinnerTips,
|
|
9254
9263
|
clearSpinnerVerbs: () => clearSpinnerVerbs,
|
|
9255
9264
|
ctaVerb: () => ctaVerb,
|
|
@@ -9274,6 +9283,7 @@ var init_spinner = __esm({
|
|
|
9274
9283
|
init_spinner_verbs();
|
|
9275
9284
|
init_spinner_verbs();
|
|
9276
9285
|
init_spinner_verbs();
|
|
9286
|
+
init_spinner_verbs();
|
|
9277
9287
|
init_spinner_select();
|
|
9278
9288
|
init_spinner_select();
|
|
9279
9289
|
init_spinner_select();
|
|
@@ -9288,137 +9298,6 @@ var init_spinner = __esm({
|
|
|
9288
9298
|
}
|
|
9289
9299
|
});
|
|
9290
9300
|
|
|
9291
|
-
// bin/version-nudge.js
|
|
9292
|
-
var version_nudge_exports = {};
|
|
9293
|
-
__export(version_nudge_exports, {
|
|
9294
|
-
buildStaleNudge: () => buildStaleNudge,
|
|
9295
|
-
cachedStaleNudge: () => cachedStaleNudge,
|
|
9296
|
-
compareVersions: () => compareVersions,
|
|
9297
|
-
emitInteractiveNudge: () => emitInteractiveNudge,
|
|
9298
|
-
parseVersion: () => parseVersion,
|
|
9299
|
-
readLatestVersionFromCache: () => readLatestVersionFromCache,
|
|
9300
|
-
readLocalVersion: () => readLocalVersion,
|
|
9301
|
-
recordNag: () => recordNag,
|
|
9302
|
-
shouldNag: () => shouldNag
|
|
9303
|
-
});
|
|
9304
|
-
import { readFileSync as readFileSync11, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9, existsSync as existsSync6 } from "fs";
|
|
9305
|
-
import { join as join12 } from "path";
|
|
9306
|
-
import { homedir as homedir9 } from "os";
|
|
9307
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9308
|
-
function stateDir() {
|
|
9309
|
-
return process.env.TERMINALHIRE_DIR || join12(homedir9(), ".terminalhire");
|
|
9310
|
-
}
|
|
9311
|
-
function indexCacheFile() {
|
|
9312
|
-
return join12(stateDir(), "index-cache.json");
|
|
9313
|
-
}
|
|
9314
|
-
function nudgeStateFile() {
|
|
9315
|
-
return join12(stateDir(), "version-nudge.json");
|
|
9316
|
-
}
|
|
9317
|
-
function parseVersion(v) {
|
|
9318
|
-
if (typeof v !== "string") return null;
|
|
9319
|
-
const m = v.trim().replace(/^v/, "").match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
9320
|
-
if (!m) return null;
|
|
9321
|
-
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
9322
|
-
}
|
|
9323
|
-
function compareVersions(a, b) {
|
|
9324
|
-
const pa = parseVersion(a);
|
|
9325
|
-
const pb = parseVersion(b);
|
|
9326
|
-
if (!pa || !pb) return null;
|
|
9327
|
-
for (let i = 0; i < 3; i++) {
|
|
9328
|
-
if (pa[i] < pb[i]) return -1;
|
|
9329
|
-
if (pa[i] > pb[i]) return 1;
|
|
9330
|
-
}
|
|
9331
|
-
return 0;
|
|
9332
|
-
}
|
|
9333
|
-
function buildStaleNudge(local, latest) {
|
|
9334
|
-
if (compareVersions(local, latest) !== -1) return null;
|
|
9335
|
-
return `your terminalhire CLI is behind (${local} \u2192 ${latest}) \u2014 npm i -g terminalhire@latest`;
|
|
9336
|
-
}
|
|
9337
|
-
function readLocalVersion() {
|
|
9338
|
-
try {
|
|
9339
|
-
const candidates = [
|
|
9340
|
-
join12(__dirname, "..", "..", "package.json"),
|
|
9341
|
-
join12(__dirname, "..", "package.json")
|
|
9342
|
-
];
|
|
9343
|
-
for (const p of candidates) {
|
|
9344
|
-
if (existsSync6(p)) {
|
|
9345
|
-
const pkg = JSON.parse(readFileSync11(p, "utf8"));
|
|
9346
|
-
if (pkg.version) return pkg.version;
|
|
9347
|
-
}
|
|
9348
|
-
}
|
|
9349
|
-
} catch {
|
|
9350
|
-
}
|
|
9351
|
-
return null;
|
|
9352
|
-
}
|
|
9353
|
-
function readLatestVersionFromCache() {
|
|
9354
|
-
try {
|
|
9355
|
-
const cache = JSON.parse(readFileSync11(indexCacheFile(), "utf8"));
|
|
9356
|
-
const v = cache?.index?.cliVersion;
|
|
9357
|
-
return typeof v === "string" ? v : null;
|
|
9358
|
-
} catch {
|
|
9359
|
-
return null;
|
|
9360
|
-
}
|
|
9361
|
-
}
|
|
9362
|
-
function cachedStaleNudge(localVersion) {
|
|
9363
|
-
const local = localVersion ?? readLocalVersion();
|
|
9364
|
-
if (!local) return null;
|
|
9365
|
-
return buildStaleNudge(local, readLatestVersionFromCache());
|
|
9366
|
-
}
|
|
9367
|
-
function shouldNag(now = Date.now()) {
|
|
9368
|
-
try {
|
|
9369
|
-
const state = JSON.parse(readFileSync11(nudgeStateFile(), "utf8"));
|
|
9370
|
-
const last = state?.lastNaggedAt;
|
|
9371
|
-
if (typeof last !== "number" || !Number.isFinite(last)) return true;
|
|
9372
|
-
return now - last >= NAG_INTERVAL_MS;
|
|
9373
|
-
} catch {
|
|
9374
|
-
return true;
|
|
9375
|
-
}
|
|
9376
|
-
}
|
|
9377
|
-
function recordNag(now = Date.now()) {
|
|
9378
|
-
try {
|
|
9379
|
-
mkdirSync9(stateDir(), { recursive: true });
|
|
9380
|
-
writeFileSync9(nudgeStateFile(), JSON.stringify({ lastNaggedAt: now }) + "\n", "utf8");
|
|
9381
|
-
} catch {
|
|
9382
|
-
}
|
|
9383
|
-
}
|
|
9384
|
-
function emitInteractiveNudge({ now = Date.now(), stream = process.stderr, localVersion } = {}) {
|
|
9385
|
-
try {
|
|
9386
|
-
const nudge = cachedStaleNudge(localVersion);
|
|
9387
|
-
if (!nudge) return false;
|
|
9388
|
-
if (!shouldNag(now)) return false;
|
|
9389
|
-
stream.write(`${nudge}
|
|
9390
|
-
`);
|
|
9391
|
-
recordNag(now);
|
|
9392
|
-
return true;
|
|
9393
|
-
} catch {
|
|
9394
|
-
return false;
|
|
9395
|
-
}
|
|
9396
|
-
}
|
|
9397
|
-
var __dirname, NAG_INTERVAL_MS;
|
|
9398
|
-
var init_version_nudge = __esm({
|
|
9399
|
-
"bin/version-nudge.js"() {
|
|
9400
|
-
"use strict";
|
|
9401
|
-
__dirname = fileURLToPath2(new URL(".", import.meta.url));
|
|
9402
|
-
NAG_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
9403
|
-
}
|
|
9404
|
-
});
|
|
9405
|
-
|
|
9406
|
-
// bin/beta-nudge.js
|
|
9407
|
-
var beta_nudge_exports = {};
|
|
9408
|
-
__export(beta_nudge_exports, {
|
|
9409
|
-
buildBetaNudge: () => buildBetaNudge
|
|
9410
|
-
});
|
|
9411
|
-
function buildBetaNudge(betaOpen, betaOptIn) {
|
|
9412
|
-
if (betaOpen !== true) return null;
|
|
9413
|
-
if (betaOptIn === true) return null;
|
|
9414
|
-
return "\u{1F9EA} Beta's open \u2014 run `terminalhire beta` to join as a Founding Contributor";
|
|
9415
|
-
}
|
|
9416
|
-
var init_beta_nudge = __esm({
|
|
9417
|
-
"bin/beta-nudge.js"() {
|
|
9418
|
-
"use strict";
|
|
9419
|
-
}
|
|
9420
|
-
});
|
|
9421
|
-
|
|
9422
9301
|
// src/github-auth.ts
|
|
9423
9302
|
import {
|
|
9424
9303
|
createCipheriv as createCipheriv2,
|
|
@@ -9426,14 +9305,14 @@ import {
|
|
|
9426
9305
|
randomBytes as randomBytes4
|
|
9427
9306
|
} from "crypto";
|
|
9428
9307
|
import {
|
|
9429
|
-
readFileSync as
|
|
9430
|
-
writeFileSync as
|
|
9431
|
-
mkdirSync as
|
|
9432
|
-
existsSync as
|
|
9308
|
+
readFileSync as readFileSync11,
|
|
9309
|
+
writeFileSync as writeFileSync9,
|
|
9310
|
+
mkdirSync as mkdirSync9,
|
|
9311
|
+
existsSync as existsSync6,
|
|
9433
9312
|
rmSync as rmSync2
|
|
9434
9313
|
} from "fs";
|
|
9435
|
-
import { join as
|
|
9436
|
-
import { homedir as
|
|
9314
|
+
import { join as join12 } from "path";
|
|
9315
|
+
import { homedir as homedir9 } from "os";
|
|
9437
9316
|
async function loadKey2() {
|
|
9438
9317
|
try {
|
|
9439
9318
|
const kt = await Promise.resolve().then(() => __toESM(require_keytar2(), 1));
|
|
@@ -9444,12 +9323,12 @@ async function loadKey2() {
|
|
|
9444
9323
|
return key2;
|
|
9445
9324
|
} catch {
|
|
9446
9325
|
}
|
|
9447
|
-
|
|
9448
|
-
if (
|
|
9449
|
-
return Buffer.from(
|
|
9326
|
+
mkdirSync9(TERMINALHIRE_DIR6, { recursive: true });
|
|
9327
|
+
if (existsSync6(KEY_FILE2)) {
|
|
9328
|
+
return Buffer.from(readFileSync11(KEY_FILE2, "utf8").trim(), "hex");
|
|
9450
9329
|
}
|
|
9451
9330
|
const key = randomBytes4(KEY_BYTES2);
|
|
9452
|
-
|
|
9331
|
+
writeFileSync9(KEY_FILE2, key.toString("hex"), { mode: 384, encoding: "utf8" });
|
|
9453
9332
|
return key;
|
|
9454
9333
|
}
|
|
9455
9334
|
function encrypt2(plaintext, key) {
|
|
@@ -9472,9 +9351,9 @@ var TERMINALHIRE_DIR6, TOKEN_FILE, KEY_FILE2, ALGO2, KEY_BYTES2, IV_BYTES2;
|
|
|
9472
9351
|
var init_github_auth = __esm({
|
|
9473
9352
|
"src/github-auth.ts"() {
|
|
9474
9353
|
"use strict";
|
|
9475
|
-
TERMINALHIRE_DIR6 =
|
|
9476
|
-
TOKEN_FILE =
|
|
9477
|
-
KEY_FILE2 =
|
|
9354
|
+
TERMINALHIRE_DIR6 = join12(homedir9(), ".terminalhire");
|
|
9355
|
+
TOKEN_FILE = join12(TERMINALHIRE_DIR6, "github-token.enc");
|
|
9356
|
+
KEY_FILE2 = join12(TERMINALHIRE_DIR6, "key");
|
|
9478
9357
|
ALGO2 = "aes-256-gcm";
|
|
9479
9358
|
KEY_BYTES2 = 32;
|
|
9480
9359
|
IV_BYTES2 = 12;
|
|
@@ -9494,9 +9373,9 @@ __export(claims_exports, {
|
|
|
9494
9373
|
toPushedClaim: () => toPushedClaim,
|
|
9495
9374
|
updateClaim: () => updateClaim
|
|
9496
9375
|
});
|
|
9497
|
-
import { readFileSync as
|
|
9498
|
-
import { join as
|
|
9499
|
-
import { homedir as
|
|
9376
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync10, mkdirSync as mkdirSync10, renameSync as renameSync6, existsSync as existsSync7 } from "fs";
|
|
9377
|
+
import { join as join13 } from "path";
|
|
9378
|
+
import { homedir as homedir10 } from "os";
|
|
9500
9379
|
function toPushedClaim(claim) {
|
|
9501
9380
|
return {
|
|
9502
9381
|
kind: claim.kind,
|
|
@@ -9516,8 +9395,8 @@ function normalizeClaim(c) {
|
|
|
9516
9395
|
}
|
|
9517
9396
|
function readClaims() {
|
|
9518
9397
|
try {
|
|
9519
|
-
if (!
|
|
9520
|
-
const data = JSON.parse(
|
|
9398
|
+
if (!existsSync7(CLAIMS_FILE)) return [];
|
|
9399
|
+
const data = JSON.parse(readFileSync12(CLAIMS_FILE, "utf8"));
|
|
9521
9400
|
const claims = Array.isArray(data?.claims) ? data.claims : [];
|
|
9522
9401
|
return claims.map(normalizeClaim);
|
|
9523
9402
|
} catch {
|
|
@@ -9525,10 +9404,10 @@ function readClaims() {
|
|
|
9525
9404
|
}
|
|
9526
9405
|
}
|
|
9527
9406
|
function writeClaims(claims) {
|
|
9528
|
-
|
|
9407
|
+
mkdirSync10(TERMINALHIRE_DIR7, { recursive: true });
|
|
9529
9408
|
const tmp = `${CLAIMS_FILE}.tmp`;
|
|
9530
9409
|
const payload = { claims };
|
|
9531
|
-
|
|
9410
|
+
writeFileSync10(tmp, JSON.stringify(payload, null, 2), "utf8");
|
|
9532
9411
|
renameSync6(tmp, CLAIMS_FILE);
|
|
9533
9412
|
}
|
|
9534
9413
|
function findClaim(id) {
|
|
@@ -9589,8 +9468,8 @@ var TERMINALHIRE_DIR7, CLAIMS_FILE, PUSHED_CLAIM_FIELDS, TERMINAL_STATES;
|
|
|
9589
9468
|
var init_claims = __esm({
|
|
9590
9469
|
"src/claims.ts"() {
|
|
9591
9470
|
"use strict";
|
|
9592
|
-
TERMINALHIRE_DIR7 = process.env.TERMINALHIRE_DIR ||
|
|
9593
|
-
CLAIMS_FILE =
|
|
9471
|
+
TERMINALHIRE_DIR7 = process.env.TERMINALHIRE_DIR || join13(homedir10(), ".terminalhire");
|
|
9472
|
+
CLAIMS_FILE = join13(TERMINALHIRE_DIR7, "claims.json");
|
|
9594
9473
|
PUSHED_CLAIM_FIELDS = [
|
|
9595
9474
|
"kind",
|
|
9596
9475
|
"repoFullName",
|
|
@@ -9610,6 +9489,7 @@ __export(claim_push_bg_exports, {
|
|
|
9610
9489
|
AUTO_CONSENT_VERSION: () => AUTO_CONSENT_VERSION,
|
|
9611
9490
|
AUTO_PUSH_THROTTLE_MS: () => AUTO_PUSH_THROTTLE_MS,
|
|
9612
9491
|
CLAIM_PUSH_AUTO_MARKER: () => CLAIM_PUSH_AUTO_MARKER,
|
|
9492
|
+
CLAIM_PUSH_MANUAL_MARKER: () => CLAIM_PUSH_MANUAL_MARKER,
|
|
9613
9493
|
CLAIM_PUSH_TOKEN_FILE: () => CLAIM_PUSH_TOKEN_FILE,
|
|
9614
9494
|
backgroundPushGate: () => backgroundPushGate,
|
|
9615
9495
|
clearAutoMarker: () => clearAutoMarker,
|
|
@@ -9618,24 +9498,26 @@ __export(claim_push_bg_exports, {
|
|
|
9618
9498
|
readAutoMarker: () => readAutoMarker,
|
|
9619
9499
|
readPushTokenEnc: () => readPushTokenEnc,
|
|
9620
9500
|
runBackgroundClaimPush: () => runBackgroundClaimPush,
|
|
9501
|
+
shouldNudgeUnpushed: () => shouldNudgeUnpushed,
|
|
9502
|
+
unpushedNudgeGate: () => unpushedNudgeGate,
|
|
9621
9503
|
writeAutoMarker: () => writeAutoMarker,
|
|
9622
9504
|
writePushTokenEnc: () => writePushTokenEnc
|
|
9623
9505
|
});
|
|
9624
9506
|
import { createHash as createHash3 } from "crypto";
|
|
9625
|
-
import { readFileSync as
|
|
9626
|
-
import { join as
|
|
9627
|
-
import { homedir as
|
|
9507
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync11, mkdirSync as mkdirSync11, existsSync as existsSync8, rmSync as rmSync3 } from "fs";
|
|
9508
|
+
import { join as join14 } from "path";
|
|
9509
|
+
import { homedir as homedir11 } from "os";
|
|
9628
9510
|
async function writePushTokenEnc(rawToken) {
|
|
9629
|
-
|
|
9511
|
+
mkdirSync11(TERMINALHIRE_DIR8, { recursive: true });
|
|
9630
9512
|
const key = await loadKey2();
|
|
9631
9513
|
const blob = encrypt2(rawToken, key);
|
|
9632
|
-
|
|
9514
|
+
writeFileSync11(CLAIM_PUSH_TOKEN_FILE, JSON.stringify(blob, null, 2), { encoding: "utf8" });
|
|
9633
9515
|
}
|
|
9634
9516
|
async function readPushTokenEnc() {
|
|
9635
|
-
if (!
|
|
9517
|
+
if (!existsSync8(CLAIM_PUSH_TOKEN_FILE)) return void 0;
|
|
9636
9518
|
try {
|
|
9637
9519
|
const key = await loadKey2();
|
|
9638
|
-
const blob = JSON.parse(
|
|
9520
|
+
const blob = JSON.parse(readFileSync13(CLAIM_PUSH_TOKEN_FILE, "utf8"));
|
|
9639
9521
|
return decrypt2(blob, key);
|
|
9640
9522
|
} catch {
|
|
9641
9523
|
return void 0;
|
|
@@ -9649,14 +9531,14 @@ function clearPushTokenEnc() {
|
|
|
9649
9531
|
}
|
|
9650
9532
|
function readAutoMarker() {
|
|
9651
9533
|
try {
|
|
9652
|
-
return
|
|
9534
|
+
return existsSync8(CLAIM_PUSH_AUTO_MARKER) ? JSON.parse(readFileSync13(CLAIM_PUSH_AUTO_MARKER, "utf8")) : null;
|
|
9653
9535
|
} catch {
|
|
9654
9536
|
return null;
|
|
9655
9537
|
}
|
|
9656
9538
|
}
|
|
9657
9539
|
function writeAutoMarker(marker) {
|
|
9658
|
-
|
|
9659
|
-
|
|
9540
|
+
mkdirSync11(TERMINALHIRE_DIR8, { recursive: true });
|
|
9541
|
+
writeFileSync11(CLAIM_PUSH_AUTO_MARKER, JSON.stringify(marker, null, 2) + "\n", "utf8");
|
|
9660
9542
|
}
|
|
9661
9543
|
function clearAutoMarker() {
|
|
9662
9544
|
try {
|
|
@@ -9689,9 +9571,46 @@ function backgroundPushGate(params) {
|
|
|
9689
9571
|
}
|
|
9690
9572
|
return { push: true, reason: "ok" };
|
|
9691
9573
|
}
|
|
9574
|
+
function unpushedNudgeGate(params) {
|
|
9575
|
+
const {
|
|
9576
|
+
autoMarkerExists,
|
|
9577
|
+
tokenFileExists,
|
|
9578
|
+
manualMarkerExists,
|
|
9579
|
+
lastSnapshotHash,
|
|
9580
|
+
currentHash,
|
|
9581
|
+
claimCount
|
|
9582
|
+
} = params;
|
|
9583
|
+
if (autoMarkerExists && tokenFileExists) return false;
|
|
9584
|
+
if (!manualMarkerExists) return false;
|
|
9585
|
+
if (!(claimCount > 0)) return false;
|
|
9586
|
+
return lastSnapshotHash !== currentHash;
|
|
9587
|
+
}
|
|
9588
|
+
async function shouldNudgeUnpushed() {
|
|
9589
|
+
try {
|
|
9590
|
+
const { listClaims: listClaims2, toPushedClaim: toPushedClaim2 } = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
9591
|
+
const pushed = listClaims2().map((c) => toPushedClaim2(c));
|
|
9592
|
+
const currentHash = computeSnapshotHash(pushed);
|
|
9593
|
+
let manual = null;
|
|
9594
|
+
try {
|
|
9595
|
+
manual = existsSync8(CLAIM_PUSH_MANUAL_MARKER) ? JSON.parse(readFileSync13(CLAIM_PUSH_MANUAL_MARKER, "utf8")) : null;
|
|
9596
|
+
} catch {
|
|
9597
|
+
manual = null;
|
|
9598
|
+
}
|
|
9599
|
+
return unpushedNudgeGate({
|
|
9600
|
+
autoMarkerExists: existsSync8(CLAIM_PUSH_AUTO_MARKER),
|
|
9601
|
+
tokenFileExists: existsSync8(CLAIM_PUSH_TOKEN_FILE),
|
|
9602
|
+
manualMarkerExists: !!manual,
|
|
9603
|
+
lastSnapshotHash: manual?.lastSnapshotHash ?? null,
|
|
9604
|
+
currentHash,
|
|
9605
|
+
claimCount: pushed.length
|
|
9606
|
+
});
|
|
9607
|
+
} catch {
|
|
9608
|
+
return false;
|
|
9609
|
+
}
|
|
9610
|
+
}
|
|
9692
9611
|
async function runBackgroundClaimPush({ now = Date.now() } = {}) {
|
|
9693
9612
|
try {
|
|
9694
|
-
if (!
|
|
9613
|
+
if (!existsSync8(CLAIM_PUSH_AUTO_MARKER) || !existsSync8(CLAIM_PUSH_TOKEN_FILE)) return;
|
|
9695
9614
|
const marker = readAutoMarker();
|
|
9696
9615
|
if (!marker || !marker.autoConsentedAt) return;
|
|
9697
9616
|
const { listClaims: listClaims2, toPushedClaim: toPushedClaim2, PUSHED_CLAIM_FIELDS: PUSHED_CLAIM_FIELDS2 } = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
@@ -9729,20 +9648,152 @@ async function runBackgroundClaimPush({ now = Date.now() } = {}) {
|
|
|
9729
9648
|
} catch {
|
|
9730
9649
|
}
|
|
9731
9650
|
}
|
|
9732
|
-
var TERMINALHIRE_DIR8, CLAIM_PUSH_AUTO_MARKER, CLAIM_PUSH_TOKEN_FILE, CLAIM_SYNC_BASE, AUTO_CONSENT_VERSION, AUTO_PUSH_THROTTLE_MS;
|
|
9651
|
+
var TERMINALHIRE_DIR8, CLAIM_PUSH_AUTO_MARKER, CLAIM_PUSH_TOKEN_FILE, CLAIM_PUSH_MANUAL_MARKER, CLAIM_SYNC_BASE, AUTO_CONSENT_VERSION, AUTO_PUSH_THROTTLE_MS;
|
|
9733
9652
|
var init_claim_push_bg = __esm({
|
|
9734
9653
|
"bin/claim-push-bg.js"() {
|
|
9735
9654
|
"use strict";
|
|
9736
9655
|
init_github_auth();
|
|
9737
|
-
TERMINALHIRE_DIR8 = process.env.TERMINALHIRE_DIR ||
|
|
9738
|
-
CLAIM_PUSH_AUTO_MARKER =
|
|
9739
|
-
CLAIM_PUSH_TOKEN_FILE =
|
|
9656
|
+
TERMINALHIRE_DIR8 = process.env.TERMINALHIRE_DIR || join14(homedir11(), ".terminalhire");
|
|
9657
|
+
CLAIM_PUSH_AUTO_MARKER = join14(TERMINALHIRE_DIR8, "claim-push-auto.json");
|
|
9658
|
+
CLAIM_PUSH_TOKEN_FILE = join14(TERMINALHIRE_DIR8, "claim-push-token.enc");
|
|
9659
|
+
CLAIM_PUSH_MANUAL_MARKER = join14(TERMINALHIRE_DIR8, "claim-push.json");
|
|
9740
9660
|
CLAIM_SYNC_BASE = "https://terminalhire.com";
|
|
9741
9661
|
AUTO_CONSENT_VERSION = 2;
|
|
9742
9662
|
AUTO_PUSH_THROTTLE_MS = 24 * 60 * 60 * 1e3;
|
|
9743
9663
|
}
|
|
9744
9664
|
});
|
|
9745
9665
|
|
|
9666
|
+
// bin/version-nudge.js
|
|
9667
|
+
var version_nudge_exports = {};
|
|
9668
|
+
__export(version_nudge_exports, {
|
|
9669
|
+
buildStaleNudge: () => buildStaleNudge,
|
|
9670
|
+
cachedStaleNudge: () => cachedStaleNudge,
|
|
9671
|
+
compareVersions: () => compareVersions,
|
|
9672
|
+
emitInteractiveNudge: () => emitInteractiveNudge,
|
|
9673
|
+
parseVersion: () => parseVersion,
|
|
9674
|
+
readLatestVersionFromCache: () => readLatestVersionFromCache,
|
|
9675
|
+
readLocalVersion: () => readLocalVersion,
|
|
9676
|
+
recordNag: () => recordNag,
|
|
9677
|
+
shouldNag: () => shouldNag
|
|
9678
|
+
});
|
|
9679
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync12, mkdirSync as mkdirSync12, existsSync as existsSync9 } from "fs";
|
|
9680
|
+
import { join as join15 } from "path";
|
|
9681
|
+
import { homedir as homedir12 } from "os";
|
|
9682
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9683
|
+
function stateDir() {
|
|
9684
|
+
return process.env.TERMINALHIRE_DIR || join15(homedir12(), ".terminalhire");
|
|
9685
|
+
}
|
|
9686
|
+
function indexCacheFile() {
|
|
9687
|
+
return join15(stateDir(), "index-cache.json");
|
|
9688
|
+
}
|
|
9689
|
+
function nudgeStateFile() {
|
|
9690
|
+
return join15(stateDir(), "version-nudge.json");
|
|
9691
|
+
}
|
|
9692
|
+
function parseVersion(v) {
|
|
9693
|
+
if (typeof v !== "string") return null;
|
|
9694
|
+
const m = v.trim().replace(/^v/, "").match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
9695
|
+
if (!m) return null;
|
|
9696
|
+
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
9697
|
+
}
|
|
9698
|
+
function compareVersions(a, b) {
|
|
9699
|
+
const pa = parseVersion(a);
|
|
9700
|
+
const pb = parseVersion(b);
|
|
9701
|
+
if (!pa || !pb) return null;
|
|
9702
|
+
for (let i = 0; i < 3; i++) {
|
|
9703
|
+
if (pa[i] < pb[i]) return -1;
|
|
9704
|
+
if (pa[i] > pb[i]) return 1;
|
|
9705
|
+
}
|
|
9706
|
+
return 0;
|
|
9707
|
+
}
|
|
9708
|
+
function buildStaleNudge(local, latest) {
|
|
9709
|
+
if (compareVersions(local, latest) !== -1) return null;
|
|
9710
|
+
return `your terminalhire CLI is behind (${local} \u2192 ${latest}) \u2014 npm i -g terminalhire@latest`;
|
|
9711
|
+
}
|
|
9712
|
+
function readLocalVersion() {
|
|
9713
|
+
try {
|
|
9714
|
+
const candidates = [
|
|
9715
|
+
join15(__dirname, "..", "..", "package.json"),
|
|
9716
|
+
join15(__dirname, "..", "package.json")
|
|
9717
|
+
];
|
|
9718
|
+
for (const p of candidates) {
|
|
9719
|
+
if (existsSync9(p)) {
|
|
9720
|
+
const pkg = JSON.parse(readFileSync14(p, "utf8"));
|
|
9721
|
+
if (pkg.version) return pkg.version;
|
|
9722
|
+
}
|
|
9723
|
+
}
|
|
9724
|
+
} catch {
|
|
9725
|
+
}
|
|
9726
|
+
return null;
|
|
9727
|
+
}
|
|
9728
|
+
function readLatestVersionFromCache() {
|
|
9729
|
+
try {
|
|
9730
|
+
const cache = JSON.parse(readFileSync14(indexCacheFile(), "utf8"));
|
|
9731
|
+
const v = cache?.index?.cliVersion;
|
|
9732
|
+
return typeof v === "string" ? v : null;
|
|
9733
|
+
} catch {
|
|
9734
|
+
return null;
|
|
9735
|
+
}
|
|
9736
|
+
}
|
|
9737
|
+
function cachedStaleNudge(localVersion) {
|
|
9738
|
+
const local = localVersion ?? readLocalVersion();
|
|
9739
|
+
if (!local) return null;
|
|
9740
|
+
return buildStaleNudge(local, readLatestVersionFromCache());
|
|
9741
|
+
}
|
|
9742
|
+
function shouldNag(now = Date.now()) {
|
|
9743
|
+
try {
|
|
9744
|
+
const state = JSON.parse(readFileSync14(nudgeStateFile(), "utf8"));
|
|
9745
|
+
const last = state?.lastNaggedAt;
|
|
9746
|
+
if (typeof last !== "number" || !Number.isFinite(last)) return true;
|
|
9747
|
+
return now - last >= NAG_INTERVAL_MS;
|
|
9748
|
+
} catch {
|
|
9749
|
+
return true;
|
|
9750
|
+
}
|
|
9751
|
+
}
|
|
9752
|
+
function recordNag(now = Date.now()) {
|
|
9753
|
+
try {
|
|
9754
|
+
mkdirSync12(stateDir(), { recursive: true });
|
|
9755
|
+
writeFileSync12(nudgeStateFile(), JSON.stringify({ lastNaggedAt: now }) + "\n", "utf8");
|
|
9756
|
+
} catch {
|
|
9757
|
+
}
|
|
9758
|
+
}
|
|
9759
|
+
function emitInteractiveNudge({ now = Date.now(), stream = process.stderr, localVersion } = {}) {
|
|
9760
|
+
try {
|
|
9761
|
+
const nudge = cachedStaleNudge(localVersion);
|
|
9762
|
+
if (!nudge) return false;
|
|
9763
|
+
if (!shouldNag(now)) return false;
|
|
9764
|
+
stream.write(`${nudge}
|
|
9765
|
+
`);
|
|
9766
|
+
recordNag(now);
|
|
9767
|
+
return true;
|
|
9768
|
+
} catch {
|
|
9769
|
+
return false;
|
|
9770
|
+
}
|
|
9771
|
+
}
|
|
9772
|
+
var __dirname, NAG_INTERVAL_MS;
|
|
9773
|
+
var init_version_nudge = __esm({
|
|
9774
|
+
"bin/version-nudge.js"() {
|
|
9775
|
+
"use strict";
|
|
9776
|
+
__dirname = fileURLToPath2(new URL(".", import.meta.url));
|
|
9777
|
+
NAG_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
9778
|
+
}
|
|
9779
|
+
});
|
|
9780
|
+
|
|
9781
|
+
// bin/beta-nudge.js
|
|
9782
|
+
var beta_nudge_exports = {};
|
|
9783
|
+
__export(beta_nudge_exports, {
|
|
9784
|
+
buildBetaNudge: () => buildBetaNudge
|
|
9785
|
+
});
|
|
9786
|
+
function buildBetaNudge(betaOpen, betaOptIn) {
|
|
9787
|
+
if (betaOpen !== true) return null;
|
|
9788
|
+
if (betaOptIn === true) return null;
|
|
9789
|
+
return "\u{1F9EA} Beta's open \u2014 run `terminalhire beta` to join as a Founding Contributor";
|
|
9790
|
+
}
|
|
9791
|
+
var init_beta_nudge = __esm({
|
|
9792
|
+
"bin/beta-nudge.js"() {
|
|
9793
|
+
"use strict";
|
|
9794
|
+
}
|
|
9795
|
+
});
|
|
9796
|
+
|
|
9746
9797
|
// bin/jpi-refresh.js
|
|
9747
9798
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
9748
9799
|
|
|
@@ -10218,6 +10269,12 @@ async function run() {
|
|
|
10218
10269
|
topMatches = filterFreshMatches2(topMatches, seenHistory);
|
|
10219
10270
|
} catch {
|
|
10220
10271
|
}
|
|
10272
|
+
let unpushedClaims = false;
|
|
10273
|
+
try {
|
|
10274
|
+
const { shouldNudgeUnpushed: shouldNudgeUnpushed2 } = await Promise.resolve().then(() => (init_claim_push_bg(), claim_push_bg_exports));
|
|
10275
|
+
unpushedClaims = await shouldNudgeUnpushed2();
|
|
10276
|
+
} catch {
|
|
10277
|
+
}
|
|
10221
10278
|
const cacheEntry = {
|
|
10222
10279
|
index,
|
|
10223
10280
|
matchCount,
|
|
@@ -10226,6 +10283,7 @@ async function run() {
|
|
|
10226
10283
|
incomingPending,
|
|
10227
10284
|
unreadChat,
|
|
10228
10285
|
sessionStale,
|
|
10286
|
+
unpushedClaims,
|
|
10229
10287
|
// In-process-only despite being persisted: the render pass receives it as
|
|
10230
10288
|
// a function argument below. A future cache READER must not gate on this
|
|
10231
10289
|
// field without re-checking isContributeEnabled/isContributePrompted at
|
|
@@ -10264,6 +10322,7 @@ async function run() {
|
|
|
10264
10322
|
incomingPending,
|
|
10265
10323
|
sessionStale,
|
|
10266
10324
|
contributeNudge,
|
|
10325
|
+
unpushedClaims,
|
|
10267
10326
|
baseUrl: API_URL2,
|
|
10268
10327
|
seenHistory,
|
|
10269
10328
|
widen,
|
package/dist/bin/jpi-spinner.js
CHANGED
|
@@ -249,6 +249,9 @@ function buildContributeNudgeLine(contributeNudge) {
|
|
|
249
249
|
function buildSessionStaleLine(sessionStale) {
|
|
250
250
|
return sessionStale === true ? "\u26A0 terminalhire: linked session expired \u2014 run: terminalhire login" : null;
|
|
251
251
|
}
|
|
252
|
+
function buildUnpushedClaimsLine(unpushedClaims) {
|
|
253
|
+
return unpushedClaims === true ? "\u26A0 new claims not yet on your dashboard \u2014 run: terminalhire claim --push --keep-updated" : null;
|
|
254
|
+
}
|
|
252
255
|
function buildSpinnerPool(topMatches, max = 6, opts = {}) {
|
|
253
256
|
const {
|
|
254
257
|
sessionTags,
|
|
@@ -257,16 +260,19 @@ function buildSpinnerPool(topMatches, max = 6, opts = {}) {
|
|
|
257
260
|
incomingPending,
|
|
258
261
|
sessionStale,
|
|
259
262
|
contributeNudge,
|
|
263
|
+
unpushedClaims,
|
|
260
264
|
seenHistory
|
|
261
265
|
} = opts;
|
|
262
266
|
const staleLine = buildSessionStaleLine(sessionStale);
|
|
263
267
|
const withStale = (pool2) => staleLine ? [staleLine, ...pool2] : pool2;
|
|
264
268
|
const introLine = buildIncomingIntroLine(incomingPending);
|
|
265
269
|
const contributeLine = buildContributeNudgeLine(contributeNudge);
|
|
270
|
+
const unpushedLine = buildUnpushedClaimsLine(unpushedClaims);
|
|
266
271
|
const ranked = filterFreshMatches(rankBySessionTags(topMatches, sessionTags), seenHistory);
|
|
267
272
|
if (!Array.isArray(ranked) || ranked.length === 0) {
|
|
268
273
|
if (introLine) return withStale([introLine]);
|
|
269
274
|
if (contributeLine) return withStale([contributeLine]);
|
|
275
|
+
if (unpushedLine) return withStale([unpushedLine]);
|
|
270
276
|
const peerLine = buildPeerLine(topPeers);
|
|
271
277
|
return withStale(peerLine ? [peerLine] : []);
|
|
272
278
|
}
|
|
@@ -276,6 +282,7 @@ function buildSpinnerPool(topMatches, max = 6, opts = {}) {
|
|
|
276
282
|
const pool = [...headers.slice(0, cap), ctaVerb()];
|
|
277
283
|
if (introLine) pool.push(introLine);
|
|
278
284
|
if (contributeLine) pool.push(contributeLine);
|
|
285
|
+
if (unpushedLine) pool.push(unpushedLine);
|
|
279
286
|
return withStale(pool);
|
|
280
287
|
}
|
|
281
288
|
|
package/dist/bin/spinner.js
CHANGED
|
@@ -346,6 +346,9 @@ function buildContributeNudgeLine(contributeNudge) {
|
|
|
346
346
|
function buildSessionStaleLine(sessionStale) {
|
|
347
347
|
return sessionStale === true ? "\u26A0 terminalhire: linked session expired \u2014 run: terminalhire login" : null;
|
|
348
348
|
}
|
|
349
|
+
function buildUnpushedClaimsLine(unpushedClaims) {
|
|
350
|
+
return unpushedClaims === true ? "\u26A0 new claims not yet on your dashboard \u2014 run: terminalhire claim --push --keep-updated" : null;
|
|
351
|
+
}
|
|
349
352
|
function buildSpinnerPool(topMatches, max = 6, opts = {}) {
|
|
350
353
|
const {
|
|
351
354
|
sessionTags,
|
|
@@ -354,16 +357,19 @@ function buildSpinnerPool(topMatches, max = 6, opts = {}) {
|
|
|
354
357
|
incomingPending,
|
|
355
358
|
sessionStale,
|
|
356
359
|
contributeNudge,
|
|
360
|
+
unpushedClaims,
|
|
357
361
|
seenHistory
|
|
358
362
|
} = opts;
|
|
359
363
|
const staleLine = buildSessionStaleLine(sessionStale);
|
|
360
364
|
const withStale = (pool2) => staleLine ? [staleLine, ...pool2] : pool2;
|
|
361
365
|
const introLine = buildIncomingIntroLine(incomingPending);
|
|
362
366
|
const contributeLine = buildContributeNudgeLine(contributeNudge);
|
|
367
|
+
const unpushedLine = buildUnpushedClaimsLine(unpushedClaims);
|
|
363
368
|
const ranked = filterFreshMatches(rankBySessionTags(topMatches, sessionTags), seenHistory);
|
|
364
369
|
if (!Array.isArray(ranked) || ranked.length === 0) {
|
|
365
370
|
if (introLine) return withStale([introLine]);
|
|
366
371
|
if (contributeLine) return withStale([contributeLine]);
|
|
372
|
+
if (unpushedLine) return withStale([unpushedLine]);
|
|
367
373
|
const peerLine = buildPeerLine(topPeers);
|
|
368
374
|
return withStale(peerLine ? [peerLine] : []);
|
|
369
375
|
}
|
|
@@ -373,6 +379,7 @@ function buildSpinnerPool(topMatches, max = 6, opts = {}) {
|
|
|
373
379
|
const pool = [...headers.slice(0, cap), ctaVerb()];
|
|
374
380
|
if (introLine) pool.push(introLine);
|
|
375
381
|
if (contributeLine) pool.push(contributeLine);
|
|
382
|
+
if (unpushedLine) pool.push(unpushedLine);
|
|
376
383
|
return withStale(pool);
|
|
377
384
|
}
|
|
378
385
|
|
|
@@ -1057,6 +1064,7 @@ function renderRefreshSurface(topMatches, sc, opts = {}) {
|
|
|
1057
1064
|
incomingPending: opts.incomingPending,
|
|
1058
1065
|
sessionStale: opts.sessionStale,
|
|
1059
1066
|
contributeNudge: opts.contributeNudge,
|
|
1067
|
+
unpushedClaims: opts.unpushedClaims,
|
|
1060
1068
|
seenHistory
|
|
1061
1069
|
});
|
|
1062
1070
|
if (verbs.length > 0) applySpinnerVerbs(verbs, sc.mode);
|
|
@@ -1083,6 +1091,7 @@ export {
|
|
|
1083
1091
|
buildSpinnerPool,
|
|
1084
1092
|
buildTips,
|
|
1085
1093
|
buildTipsDetailed,
|
|
1094
|
+
buildUnpushedClaimsLine,
|
|
1086
1095
|
clearSpinnerTips,
|
|
1087
1096
|
clearSpinnerVerbs,
|
|
1088
1097
|
ctaVerb,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "terminalhire",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.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",
|