terminalhire 0.42.2 → 0.42.4
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 +84 -1
- package/dist/bin/jpi-claim.js +150 -30
- package/dist/bin/jpi-config.js +1 -1
- package/dist/bin/jpi-dispatch.js +548 -114
- package/dist/bin/jpi-hub.js +1 -0
- package/dist/bin/jpi-jobs.js +49 -21
- package/dist/bin/jpi-mcp.js +153 -33
- package/dist/bin/jpi-post.js +251 -33
- package/dist/bin/jpi-protocol.js +114 -30
- package/dist/bin/jpi-refresh.js +148 -38
- package/dist/bin/jpi-spinner.js +4 -0
- package/dist/bin/jpi-update.js +114 -30
- package/dist/bin/spinner-render.js +45 -21
- package/dist/bin/spinner.js +45 -21
- package/dist/src/posting-drafts.js +234 -20
- package/dist/src/protocol.js +113 -29
- package/package.json +3 -3
|
@@ -514,7 +514,7 @@ var CLAIM_PUSH_AUTO_MARKER = join5(TERMINALHIRE_DIR4, "claim-push-auto.json");
|
|
|
514
514
|
var CLAIM_PUSH_TOKEN_FILE = join5(TERMINALHIRE_DIR4, "claim-push-token.enc");
|
|
515
515
|
var CLAIM_PUSH_MANUAL_MARKER = join5(TERMINALHIRE_DIR4, "claim-push.json");
|
|
516
516
|
var CLAIM_SYNC_BASE = "https://terminalhire.com";
|
|
517
|
-
var AUTO_CONSENT_VERSION =
|
|
517
|
+
var AUTO_CONSENT_VERSION = 3;
|
|
518
518
|
var AUTO_PUSH_THROTTLE_MS = 24 * 60 * 60 * 1e3;
|
|
519
519
|
async function writePushTokenEnc(rawToken) {
|
|
520
520
|
ensureStateDirForSecret(TERMINALHIRE_DIR4);
|
|
@@ -617,6 +617,82 @@ async function shouldNudgeUnpushed() {
|
|
|
617
617
|
return false;
|
|
618
618
|
}
|
|
619
619
|
}
|
|
620
|
+
var CLAIM_HEARTBEAT_FILE = join5(TERMINALHIRE_DIR4, "claim-heartbeat.json");
|
|
621
|
+
var HEARTBEAT_MIN_INTERVAL_MS = 3e4;
|
|
622
|
+
var HEARTBEAT_MIN_CONSENT_VERSION = 3;
|
|
623
|
+
function readHeartbeatState() {
|
|
624
|
+
try {
|
|
625
|
+
return existsSync5(CLAIM_HEARTBEAT_FILE) ? JSON.parse(readFileSync4(CLAIM_HEARTBEAT_FILE, "utf8")) : {};
|
|
626
|
+
} catch {
|
|
627
|
+
return {};
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
function recordHeartbeatBeat(claimId, at) {
|
|
631
|
+
try {
|
|
632
|
+
ensureStateDir(TERMINALHIRE_DIR4);
|
|
633
|
+
const state = readHeartbeatState();
|
|
634
|
+
state[claimId] = at;
|
|
635
|
+
writeFileSync4(CLAIM_HEARTBEAT_FILE, JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
636
|
+
} catch {
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
function heartbeatGate(params) {
|
|
640
|
+
const {
|
|
641
|
+
autoMarkerExists,
|
|
642
|
+
tokenFileExists,
|
|
643
|
+
consentVersion,
|
|
644
|
+
bountyId,
|
|
645
|
+
claimId,
|
|
646
|
+
lastBeatAt,
|
|
647
|
+
now = Date.now(),
|
|
648
|
+
minIntervalMs = HEARTBEAT_MIN_INTERVAL_MS
|
|
649
|
+
} = params;
|
|
650
|
+
if (typeof bountyId !== "string" || bountyId.trim() === "") {
|
|
651
|
+
return { beat: false, reason: "no-server-ids" };
|
|
652
|
+
}
|
|
653
|
+
if (typeof claimId !== "string" || claimId.trim() === "") {
|
|
654
|
+
return { beat: false, reason: "no-server-ids" };
|
|
655
|
+
}
|
|
656
|
+
if (!autoMarkerExists || !tokenFileExists) {
|
|
657
|
+
return { beat: false, reason: "not-opted-in" };
|
|
658
|
+
}
|
|
659
|
+
if (!(Number.isInteger(consentVersion) && consentVersion >= HEARTBEAT_MIN_CONSENT_VERSION)) {
|
|
660
|
+
return { beat: false, reason: "consent-predates-presence" };
|
|
661
|
+
}
|
|
662
|
+
const last = lastBeatAt ? Date.parse(lastBeatAt) : NaN;
|
|
663
|
+
if (!Number.isNaN(last) && now - last < minIntervalMs) {
|
|
664
|
+
return { beat: false, reason: "throttled" };
|
|
665
|
+
}
|
|
666
|
+
return { beat: true, reason: "ok" };
|
|
667
|
+
}
|
|
668
|
+
async function postClaimHeartbeat({ bountyId, claimId, now = Date.now() } = {}) {
|
|
669
|
+
try {
|
|
670
|
+
const marker = readAutoMarker();
|
|
671
|
+
const gate = heartbeatGate({
|
|
672
|
+
autoMarkerExists: Boolean(marker && marker.autoConsentedAt),
|
|
673
|
+
tokenFileExists: existsSync5(CLAIM_PUSH_TOKEN_FILE),
|
|
674
|
+
consentVersion: marker?.version,
|
|
675
|
+
bountyId,
|
|
676
|
+
claimId,
|
|
677
|
+
lastBeatAt: readHeartbeatState()[claimId] ?? null,
|
|
678
|
+
now
|
|
679
|
+
});
|
|
680
|
+
if (!gate.beat) return { beat: false, reason: gate.reason };
|
|
681
|
+
const token = await readPushTokenEnc();
|
|
682
|
+
if (!token) return { beat: false, reason: "unreadable-token" };
|
|
683
|
+
const res = await fetch(`${CLAIM_SYNC_BASE}/api/claim/heartbeat`, {
|
|
684
|
+
method: "POST",
|
|
685
|
+
headers: { "Content-Type": "application/json" },
|
|
686
|
+
body: JSON.stringify({ bountyId, claimId, pushToken: token }),
|
|
687
|
+
signal: AbortSignal.timeout(5e3)
|
|
688
|
+
});
|
|
689
|
+
if (!res.ok) return { beat: false, reason: `server-${res.status}` };
|
|
690
|
+
recordHeartbeatBeat(claimId, new Date(now).toISOString());
|
|
691
|
+
return { beat: true, reason: "ok" };
|
|
692
|
+
} catch {
|
|
693
|
+
return { beat: false, reason: "failed" };
|
|
694
|
+
}
|
|
695
|
+
}
|
|
620
696
|
async function runBackgroundClaimPush({ now = Date.now() } = {}) {
|
|
621
697
|
try {
|
|
622
698
|
if (!existsSync5(CLAIM_PUSH_AUTO_MARKER) || !existsSync5(CLAIM_PUSH_TOKEN_FILE)) {
|
|
@@ -666,15 +742,22 @@ async function runBackgroundClaimPush({ now = Date.now() } = {}) {
|
|
|
666
742
|
export {
|
|
667
743
|
AUTO_CONSENT_VERSION,
|
|
668
744
|
AUTO_PUSH_THROTTLE_MS,
|
|
745
|
+
CLAIM_HEARTBEAT_FILE,
|
|
669
746
|
CLAIM_PUSH_AUTO_MARKER,
|
|
670
747
|
CLAIM_PUSH_MANUAL_MARKER,
|
|
671
748
|
CLAIM_PUSH_TOKEN_FILE,
|
|
749
|
+
HEARTBEAT_MIN_CONSENT_VERSION,
|
|
750
|
+
HEARTBEAT_MIN_INTERVAL_MS,
|
|
672
751
|
backgroundPushGate,
|
|
673
752
|
clearAutoMarker,
|
|
674
753
|
clearPushTokenEnc,
|
|
675
754
|
computeSnapshotHash,
|
|
755
|
+
heartbeatGate,
|
|
756
|
+
postClaimHeartbeat,
|
|
676
757
|
readAutoMarker,
|
|
758
|
+
readHeartbeatState,
|
|
677
759
|
readPushTokenEnc,
|
|
760
|
+
recordHeartbeatBeat,
|
|
678
761
|
runBackgroundClaimPush,
|
|
679
762
|
shouldNudgeUnpushed,
|
|
680
763
|
unpushedNudgeGate,
|
package/dist/bin/jpi-claim.js
CHANGED
|
@@ -11198,7 +11198,7 @@ __export(founder_note_sync_exports, {
|
|
|
11198
11198
|
async function fetchFounderNotes(pushToken, fetchImpl = fetch) {
|
|
11199
11199
|
if (typeof pushToken !== "string" || pushToken.length === 0) return null;
|
|
11200
11200
|
try {
|
|
11201
|
-
const res = await fetchImpl(`${
|
|
11201
|
+
const res = await fetchImpl(`${CLAIM_SYNC_BASE3}/api/claim/notes`, {
|
|
11202
11202
|
method: "POST",
|
|
11203
11203
|
headers: { "Content-Type": "application/json" },
|
|
11204
11204
|
body: JSON.stringify({ pushToken }),
|
|
@@ -11254,11 +11254,11 @@ function formatNote(note) {
|
|
|
11254
11254
|
return ` ${note.at.slice(0, 16).replace("T", " ")} [${label}] ${note.claimId}
|
|
11255
11255
|
${note.note.split("\n").join("\n ")}`;
|
|
11256
11256
|
}
|
|
11257
|
-
var
|
|
11257
|
+
var CLAIM_SYNC_BASE3;
|
|
11258
11258
|
var init_founder_note_sync = __esm({
|
|
11259
11259
|
"bin/founder-note-sync.js"() {
|
|
11260
11260
|
"use strict";
|
|
11261
|
-
|
|
11261
|
+
CLAIM_SYNC_BASE3 = "https://terminalhire.com";
|
|
11262
11262
|
}
|
|
11263
11263
|
});
|
|
11264
11264
|
|
|
@@ -26238,7 +26238,8 @@ var TERMINALHIRE_DIR5 = process.env.TERMINALHIRE_DIR || join7(homedir5(), ".term
|
|
|
26238
26238
|
var CLAIM_PUSH_AUTO_MARKER = join7(TERMINALHIRE_DIR5, "claim-push-auto.json");
|
|
26239
26239
|
var CLAIM_PUSH_TOKEN_FILE = join7(TERMINALHIRE_DIR5, "claim-push-token.enc");
|
|
26240
26240
|
var CLAIM_PUSH_MANUAL_MARKER = join7(TERMINALHIRE_DIR5, "claim-push.json");
|
|
26241
|
-
var
|
|
26241
|
+
var CLAIM_SYNC_BASE = "https://terminalhire.com";
|
|
26242
|
+
var AUTO_CONSENT_VERSION = 3;
|
|
26242
26243
|
var AUTO_PUSH_THROTTLE_MS = 24 * 60 * 60 * 1e3;
|
|
26243
26244
|
async function writePushTokenEnc(rawToken) {
|
|
26244
26245
|
ensureStateDirForSecret(TERMINALHIRE_DIR5);
|
|
@@ -26319,9 +26320,85 @@ async function shouldNudgeUnpushed() {
|
|
|
26319
26320
|
return false;
|
|
26320
26321
|
}
|
|
26321
26322
|
}
|
|
26323
|
+
var CLAIM_HEARTBEAT_FILE = join7(TERMINALHIRE_DIR5, "claim-heartbeat.json");
|
|
26324
|
+
var HEARTBEAT_MIN_INTERVAL_MS = 3e4;
|
|
26325
|
+
var HEARTBEAT_MIN_CONSENT_VERSION = 3;
|
|
26326
|
+
function readHeartbeatState() {
|
|
26327
|
+
try {
|
|
26328
|
+
return existsSync5(CLAIM_HEARTBEAT_FILE) ? JSON.parse(readFileSync6(CLAIM_HEARTBEAT_FILE, "utf8")) : {};
|
|
26329
|
+
} catch {
|
|
26330
|
+
return {};
|
|
26331
|
+
}
|
|
26332
|
+
}
|
|
26333
|
+
function recordHeartbeatBeat(claimId, at) {
|
|
26334
|
+
try {
|
|
26335
|
+
ensureStateDir(TERMINALHIRE_DIR5);
|
|
26336
|
+
const state = readHeartbeatState();
|
|
26337
|
+
state[claimId] = at;
|
|
26338
|
+
writeFileSync5(CLAIM_HEARTBEAT_FILE, JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
26339
|
+
} catch {
|
|
26340
|
+
}
|
|
26341
|
+
}
|
|
26342
|
+
function heartbeatGate(params) {
|
|
26343
|
+
const {
|
|
26344
|
+
autoMarkerExists,
|
|
26345
|
+
tokenFileExists,
|
|
26346
|
+
consentVersion,
|
|
26347
|
+
bountyId,
|
|
26348
|
+
claimId,
|
|
26349
|
+
lastBeatAt,
|
|
26350
|
+
now = Date.now(),
|
|
26351
|
+
minIntervalMs = HEARTBEAT_MIN_INTERVAL_MS
|
|
26352
|
+
} = params;
|
|
26353
|
+
if (typeof bountyId !== "string" || bountyId.trim() === "") {
|
|
26354
|
+
return { beat: false, reason: "no-server-ids" };
|
|
26355
|
+
}
|
|
26356
|
+
if (typeof claimId !== "string" || claimId.trim() === "") {
|
|
26357
|
+
return { beat: false, reason: "no-server-ids" };
|
|
26358
|
+
}
|
|
26359
|
+
if (!autoMarkerExists || !tokenFileExists) {
|
|
26360
|
+
return { beat: false, reason: "not-opted-in" };
|
|
26361
|
+
}
|
|
26362
|
+
if (!(Number.isInteger(consentVersion) && consentVersion >= HEARTBEAT_MIN_CONSENT_VERSION)) {
|
|
26363
|
+
return { beat: false, reason: "consent-predates-presence" };
|
|
26364
|
+
}
|
|
26365
|
+
const last = lastBeatAt ? Date.parse(lastBeatAt) : NaN;
|
|
26366
|
+
if (!Number.isNaN(last) && now - last < minIntervalMs) {
|
|
26367
|
+
return { beat: false, reason: "throttled" };
|
|
26368
|
+
}
|
|
26369
|
+
return { beat: true, reason: "ok" };
|
|
26370
|
+
}
|
|
26371
|
+
async function postClaimHeartbeat({ bountyId, claimId, now = Date.now() } = {}) {
|
|
26372
|
+
try {
|
|
26373
|
+
const marker = readAutoMarker();
|
|
26374
|
+
const gate = heartbeatGate({
|
|
26375
|
+
autoMarkerExists: Boolean(marker && marker.autoConsentedAt),
|
|
26376
|
+
tokenFileExists: existsSync5(CLAIM_PUSH_TOKEN_FILE),
|
|
26377
|
+
consentVersion: marker?.version,
|
|
26378
|
+
bountyId,
|
|
26379
|
+
claimId,
|
|
26380
|
+
lastBeatAt: readHeartbeatState()[claimId] ?? null,
|
|
26381
|
+
now
|
|
26382
|
+
});
|
|
26383
|
+
if (!gate.beat) return { beat: false, reason: gate.reason };
|
|
26384
|
+
const token = await readPushTokenEnc();
|
|
26385
|
+
if (!token) return { beat: false, reason: "unreadable-token" };
|
|
26386
|
+
const res = await fetch(`${CLAIM_SYNC_BASE}/api/claim/heartbeat`, {
|
|
26387
|
+
method: "POST",
|
|
26388
|
+
headers: { "Content-Type": "application/json" },
|
|
26389
|
+
body: JSON.stringify({ bountyId, claimId, pushToken: token }),
|
|
26390
|
+
signal: AbortSignal.timeout(5e3)
|
|
26391
|
+
});
|
|
26392
|
+
if (!res.ok) return { beat: false, reason: `server-${res.status}` };
|
|
26393
|
+
recordHeartbeatBeat(claimId, new Date(now).toISOString());
|
|
26394
|
+
return { beat: true, reason: "ok" };
|
|
26395
|
+
} catch {
|
|
26396
|
+
return { beat: false, reason: "failed" };
|
|
26397
|
+
}
|
|
26398
|
+
}
|
|
26322
26399
|
|
|
26323
26400
|
// bin/founder-verdict-sync.js
|
|
26324
|
-
var
|
|
26401
|
+
var CLAIM_SYNC_BASE2 = "https://terminalhire.com";
|
|
26325
26402
|
var TERMINAL = /* @__PURE__ */ new Set(["merged", "abandoned"]);
|
|
26326
26403
|
function verdictState(verdict) {
|
|
26327
26404
|
return verdict === "rejected" ? "abandoned" : "merged";
|
|
@@ -26329,7 +26406,7 @@ function verdictState(verdict) {
|
|
|
26329
26406
|
async function fetchFounderVerdicts(pushToken, fetchImpl = fetch) {
|
|
26330
26407
|
if (typeof pushToken !== "string" || pushToken.length === 0) return null;
|
|
26331
26408
|
try {
|
|
26332
|
-
const res = await fetchImpl(`${
|
|
26409
|
+
const res = await fetchImpl(`${CLAIM_SYNC_BASE2}/api/claim/verdicts`, {
|
|
26333
26410
|
method: "POST",
|
|
26334
26411
|
headers: { "Content-Type": "application/json" },
|
|
26335
26412
|
body: JSON.stringify({ pushToken }),
|
|
@@ -26447,7 +26524,7 @@ function markClaimNudged(id) {
|
|
|
26447
26524
|
}
|
|
26448
26525
|
}
|
|
26449
26526
|
var API_URL = process.env["TERMINALHIRE_API_URL"] ?? process.env["JPI_API_URL"] ?? "https://terminalhire.com";
|
|
26450
|
-
var
|
|
26527
|
+
var CLAIM_SYNC_BASE4 = "https://terminalhire.com";
|
|
26451
26528
|
var CLAIM_CONSENT_VERSION = 1;
|
|
26452
26529
|
var CLAIM_POLL_INTERVAL_MS = 2e3;
|
|
26453
26530
|
var CLAIM_POLL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
@@ -27289,7 +27366,7 @@ async function mintRegistrationProof() {
|
|
|
27289
27366
|
console.log(" GitHub identity has to be verified once in the browser.");
|
|
27290
27367
|
let begin;
|
|
27291
27368
|
try {
|
|
27292
|
-
const r = await fetch(`${
|
|
27369
|
+
const r = await fetch(`${CLAIM_SYNC_BASE4}/api/claim-sync/begin`, {
|
|
27293
27370
|
method: "POST",
|
|
27294
27371
|
headers: { "Content-Type": "application/json" },
|
|
27295
27372
|
body: JSON.stringify({ hostname: osHostname() }),
|
|
@@ -27322,7 +27399,7 @@ async function mintRegistrationProof() {
|
|
|
27322
27399
|
let statusRes;
|
|
27323
27400
|
try {
|
|
27324
27401
|
statusRes = await fetch(
|
|
27325
|
-
`${
|
|
27402
|
+
`${CLAIM_SYNC_BASE4}/api/claim-sync/status?challenge=${encodeURIComponent(challenge)}`,
|
|
27326
27403
|
{ signal: AbortSignal.timeout(1e4) }
|
|
27327
27404
|
);
|
|
27328
27405
|
} catch {
|
|
@@ -27374,6 +27451,13 @@ var PUSH_TOKEN_REFUSAL = Object.freeze({
|
|
|
27374
27451
|
INVALID: "invalid-push-token",
|
|
27375
27452
|
/** The credential is LIVE, just not a registration credential. NEVER clear it. */
|
|
27376
27453
|
INSUFFICIENT: "insufficient-push-token",
|
|
27454
|
+
/**
|
|
27455
|
+
* TERM-499. Live background token, but minted under a consent card that did not
|
|
27456
|
+
* disclose claim registration (or presence). NEVER clear it — the daily push it
|
|
27457
|
+
* was enrolled for still works. Fall back to a one-time browser proof for this
|
|
27458
|
+
* registration, and tell the developer how to re-enrol for the updated card.
|
|
27459
|
+
*/
|
|
27460
|
+
CONSENT_PREDATES_REGISTER: "consent-predates-register",
|
|
27377
27461
|
/**
|
|
27378
27462
|
* TERM-325. Minted before tokens were bound to a GitHub account id, so it can no
|
|
27379
27463
|
* longer prove who holds it. Unrevoked and real, and it fails closed at every scope
|
|
@@ -27391,6 +27475,7 @@ async function registerFounderClaim(b) {
|
|
|
27391
27475
|
const postingId = b.bountyId.replace(/^bounty:founder:/, "");
|
|
27392
27476
|
let clearedLocalCredential = false;
|
|
27393
27477
|
let refusedForPurpose = false;
|
|
27478
|
+
let preserveBackgroundToken = false;
|
|
27394
27479
|
const refuse = (reason) => {
|
|
27395
27480
|
console.error(
|
|
27396
27481
|
`
|
|
@@ -27449,7 +27534,7 @@ terminalhire claim: refusing to record \u2014 ${reason}
|
|
|
27449
27534
|
};
|
|
27450
27535
|
if (!auth) await acquireProofAuth();
|
|
27451
27536
|
console.log("\n Registering this claim with terminalhire (founder posting)...");
|
|
27452
|
-
const sendRegistration = async (includeExpectation) => fetch(`${
|
|
27537
|
+
const sendRegistration = async (includeExpectation) => fetch(`${CLAIM_SYNC_BASE4}/api/claim/register`, {
|
|
27453
27538
|
method: "POST",
|
|
27454
27539
|
headers: { "Content-Type": "application/json" },
|
|
27455
27540
|
body: JSON.stringify({
|
|
@@ -27476,9 +27561,9 @@ terminalhire claim: refusing to record \u2014 ${reason}
|
|
|
27476
27561
|
);
|
|
27477
27562
|
}
|
|
27478
27563
|
let refusalBody = await readRefusal(res);
|
|
27479
|
-
const pushTokenRefusal = storedPushToken && res.status === 403 && (refusalBody?.error === PUSH_TOKEN_REFUSAL.INVALID || refusalBody?.error === PUSH_TOKEN_REFUSAL.LEGACY || refusalBody?.error === PUSH_TOKEN_REFUSAL.INSUFFICIENT) ? refusalBody.error : null;
|
|
27564
|
+
const pushTokenRefusal = storedPushToken && res.status === 403 && (refusalBody?.error === PUSH_TOKEN_REFUSAL.INVALID || refusalBody?.error === PUSH_TOKEN_REFUSAL.LEGACY || refusalBody?.error === PUSH_TOKEN_REFUSAL.INSUFFICIENT || refusalBody?.error === PUSH_TOKEN_REFUSAL.CONSENT_PREDATES_REGISTER) ? refusalBody.error : null;
|
|
27480
27565
|
if (pushTokenRefusal) {
|
|
27481
|
-
if (pushTokenRefusal !== PUSH_TOKEN_REFUSAL.INSUFFICIENT) {
|
|
27566
|
+
if (pushTokenRefusal !== PUSH_TOKEN_REFUSAL.INSUFFICIENT && pushTokenRefusal !== PUSH_TOKEN_REFUSAL.CONSENT_PREDATES_REGISTER) {
|
|
27482
27567
|
if (pushTokenRefusal === PUSH_TOKEN_REFUSAL.LEGACY) {
|
|
27483
27568
|
console.log("\n The push token on this machine was issued before terminalhire tied");
|
|
27484
27569
|
console.log(" tokens to a GitHub account, so it can no longer prove who holds it.");
|
|
@@ -27504,8 +27589,17 @@ terminalhire claim: refusing to record \u2014 ${reason}
|
|
|
27504
27589
|
}
|
|
27505
27590
|
} else {
|
|
27506
27591
|
refusedForPurpose = true;
|
|
27507
|
-
|
|
27508
|
-
|
|
27592
|
+
if (pushTokenRefusal === PUSH_TOKEN_REFUSAL.CONSENT_PREDATES_REGISTER) {
|
|
27593
|
+
preserveBackgroundToken = true;
|
|
27594
|
+
console.log("\n Your keep-updated enrolment predates the card that discloses");
|
|
27595
|
+
console.log(" claim registration (and founder presence). Nothing was revoked.");
|
|
27596
|
+
console.log(" Registering THIS claim falls back to a one-time browser check.");
|
|
27597
|
+
console.log(" To enrol under the updated card:");
|
|
27598
|
+
console.log(" terminalhire claim --push --keep-updated");
|
|
27599
|
+
} else {
|
|
27600
|
+
console.log("\n Registering a claim takes a one-time browser check, which the");
|
|
27601
|
+
console.log(" credential stored on this machine is not for. Falling back to it now.");
|
|
27602
|
+
}
|
|
27509
27603
|
console.log(" It was not revoked or changed \u2014 this refusal was about which");
|
|
27510
27604
|
console.log(" action the credential is for, not whether it is still good.");
|
|
27511
27605
|
}
|
|
@@ -27545,7 +27639,7 @@ terminalhire claim: refusing to record \u2014 ${reason}
|
|
|
27545
27639
|
refuse("malformed registration response from the server.");
|
|
27546
27640
|
}
|
|
27547
27641
|
const mintedToken = typeof body.pushToken === "string" && body.pushToken.length > 0 ? body.pushToken : null;
|
|
27548
|
-
if (refusedForPurpose && mintedToken) {
|
|
27642
|
+
if (refusedForPurpose && mintedToken && !preserveBackgroundToken) {
|
|
27549
27643
|
try {
|
|
27550
27644
|
await writePushTokenEnc(mintedToken);
|
|
27551
27645
|
console.log("\n Your stored credential was refreshed \u2014 registering this claim");
|
|
@@ -27563,7 +27657,8 @@ terminalhire claim: refusing to record \u2014 ${reason}
|
|
|
27563
27657
|
// Existing-token auth deliberately gets no replacement from the server: reuse the
|
|
27564
27658
|
// encrypted value we just authenticated with. The one exception is the rotation
|
|
27565
27659
|
// above — there the stored value is the superseded one, so it must not win.
|
|
27566
|
-
|
|
27660
|
+
// Consent-predates also keeps the background token even if a read mint arrived.
|
|
27661
|
+
pushToken: refusedForPurpose && mintedToken && !preserveBackgroundToken ? mintedToken : storedPushToken ?? mintedToken
|
|
27567
27662
|
};
|
|
27568
27663
|
}
|
|
27569
27664
|
function readCredentialDisposition({
|
|
@@ -27600,6 +27695,13 @@ async function bootstrapFounderClaimEnrollment(claim, registration) {
|
|
|
27600
27695
|
}
|
|
27601
27696
|
return { stored: true, reason: "ok" };
|
|
27602
27697
|
}
|
|
27698
|
+
async function beatFounderPresence(claim) {
|
|
27699
|
+
if (!claim) return;
|
|
27700
|
+
await postClaimHeartbeat({
|
|
27701
|
+
bountyId: founderPostingIdOf(claim),
|
|
27702
|
+
claimId: claim.approval?.claimId ?? null
|
|
27703
|
+
});
|
|
27704
|
+
}
|
|
27603
27705
|
async function cmdRecord(arg, flags = {}) {
|
|
27604
27706
|
const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
27605
27707
|
if (!arg) {
|
|
@@ -27833,6 +27935,7 @@ terminalhire claim: refusing to record \u2014 read ${b.repoFullName}'s contribut
|
|
|
27833
27935
|
console.log("\n Founder postings are never forked or cloned \u2014 the work arrives as a");
|
|
27834
27936
|
console.log(" read-slice through terminalhire, and your patch goes back the same way.");
|
|
27835
27937
|
}
|
|
27938
|
+
await beatFounderPresence(claim);
|
|
27836
27939
|
return;
|
|
27837
27940
|
}
|
|
27838
27941
|
console.log(`
|
|
@@ -28029,7 +28132,7 @@ async function resolveIssueOutcome(c, res, claims) {
|
|
|
28029
28132
|
async function fetchFounderApprovals(pushToken, fetchImpl = fetch) {
|
|
28030
28133
|
if (typeof pushToken !== "string" || pushToken.length === 0) return null;
|
|
28031
28134
|
try {
|
|
28032
|
-
const res = await fetchImpl(`${
|
|
28135
|
+
const res = await fetchImpl(`${CLAIM_SYNC_BASE4}/api/claim/approvals`, {
|
|
28033
28136
|
method: "POST",
|
|
28034
28137
|
headers: { "Content-Type": "application/json" },
|
|
28035
28138
|
body: JSON.stringify({ pushToken }),
|
|
@@ -28269,6 +28372,7 @@ async function cmdUpdate(id, state, prUrl) {
|
|
|
28269
28372
|
process.exit(1);
|
|
28270
28373
|
}
|
|
28271
28374
|
console.log(`Updated ${id} \u2192 ${state}${prUrl ? ` (PR: ${prUrl})` : ""}`);
|
|
28375
|
+
await beatFounderPresence(updated);
|
|
28272
28376
|
}
|
|
28273
28377
|
async function cmdRelease(id, flags = {}) {
|
|
28274
28378
|
const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
@@ -28396,8 +28500,9 @@ async function cmdAttach(id, worktree, branch) {
|
|
|
28396
28500
|
console.error(`terminalhire claim: '${worktree}' is not a git work tree.`);
|
|
28397
28501
|
process.exit(1);
|
|
28398
28502
|
}
|
|
28399
|
-
claims.updateClaim(id, { worktreePath: toplevel, branch });
|
|
28503
|
+
const attached = claims.updateClaim(id, { worktreePath: toplevel, branch });
|
|
28400
28504
|
console.log(`Attached ${id}: worktree=${toplevel} branch=${branch}`);
|
|
28505
|
+
await beatFounderPresence(attached);
|
|
28401
28506
|
}
|
|
28402
28507
|
function workDirFor(repoFullName, issueNumber) {
|
|
28403
28508
|
const [owner, repo] = String(repoFullName).split("/");
|
|
@@ -28495,6 +28600,7 @@ async function cmdStart(id, flags = {}) {
|
|
|
28495
28600
|
if (claim.branch) console.log(` branch: ${claim.branch}`);
|
|
28496
28601
|
console.log(`
|
|
28497
28602
|
When it's done: terminalhire claim submit ${id}`);
|
|
28603
|
+
await beatFounderPresence(claim);
|
|
28498
28604
|
return;
|
|
28499
28605
|
}
|
|
28500
28606
|
}
|
|
@@ -28512,6 +28618,7 @@ ${sanitizeText(claim.title)}`);
|
|
|
28512
28618
|
);
|
|
28513
28619
|
}
|
|
28514
28620
|
printNextSteps([claim]);
|
|
28621
|
+
await beatFounderPresence(claim);
|
|
28515
28622
|
return;
|
|
28516
28623
|
}
|
|
28517
28624
|
if (flags.here) {
|
|
@@ -28888,7 +28995,7 @@ async function cmdSlice(id, flags = {}) {
|
|
|
28888
28995
|
const pushToken = await requireReadPushToken("fetching your granted slice");
|
|
28889
28996
|
let res;
|
|
28890
28997
|
try {
|
|
28891
|
-
res = await fetch(`${
|
|
28998
|
+
res = await fetch(`${CLAIM_SYNC_BASE4}/api/claim/slice`, {
|
|
28892
28999
|
method: "POST",
|
|
28893
29000
|
headers: { "Content-Type": "application/json" },
|
|
28894
29001
|
body: JSON.stringify({
|
|
@@ -28980,7 +29087,7 @@ async function cmdSlice(id, flags = {}) {
|
|
|
28980
29087
|
);
|
|
28981
29088
|
process.exit(1);
|
|
28982
29089
|
}
|
|
28983
|
-
claims.updateClaim(claim.id, { worktreePath: dest, branch, state: "working" });
|
|
29090
|
+
const working = claims.updateClaim(claim.id, { worktreePath: dest, branch, state: "working" });
|
|
28984
29091
|
console.log(`
|
|
28985
29092
|
worktree: ${dest}`);
|
|
28986
29093
|
console.log(` branch: ${branch}`);
|
|
@@ -28988,6 +29095,7 @@ async function cmdSlice(id, flags = {}) {
|
|
|
28988
29095
|
`
|
|
28989
29096
|
Author your change there (commit as you go), then: terminalhire claim submit ${claim.id}`
|
|
28990
29097
|
);
|
|
29098
|
+
await beatFounderPresence(working ?? claim);
|
|
28991
29099
|
}
|
|
28992
29100
|
async function cmdRuns(id, flags = {}) {
|
|
28993
29101
|
const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
@@ -28996,7 +29104,7 @@ async function cmdRuns(id, flags = {}) {
|
|
|
28996
29104
|
const fetchOnce = async () => {
|
|
28997
29105
|
let res;
|
|
28998
29106
|
try {
|
|
28999
|
-
res = await fetch(`${
|
|
29107
|
+
res = await fetch(`${CLAIM_SYNC_BASE4}/api/patch/runs`, {
|
|
29000
29108
|
method: "POST",
|
|
29001
29109
|
headers: { "Content-Type": "application/json" },
|
|
29002
29110
|
body: JSON.stringify({
|
|
@@ -29144,7 +29252,7 @@ async function submitFounderPatch({ claims, claim, id, wt, flags }) {
|
|
|
29144
29252
|
console.log("\n Submitting the patch through terminalhire...");
|
|
29145
29253
|
let res;
|
|
29146
29254
|
try {
|
|
29147
|
-
res = await fetch(`${
|
|
29255
|
+
res = await fetch(`${CLAIM_SYNC_BASE4}/api/patch`, {
|
|
29148
29256
|
method: "POST",
|
|
29149
29257
|
headers: { "Content-Type": "application/json" },
|
|
29150
29258
|
body: JSON.stringify(submission),
|
|
@@ -29171,7 +29279,7 @@ async function submitFounderPatch({ claims, claim, id, wt, flags }) {
|
|
|
29171
29279
|
console.error("terminalhire claim: malformed patch response from the server.");
|
|
29172
29280
|
process.exit(1);
|
|
29173
29281
|
}
|
|
29174
|
-
claims.updateClaim(id, { state: "submitted" });
|
|
29282
|
+
const submitted = claims.updateClaim(id, { state: "submitted" });
|
|
29175
29283
|
console.log(`
|
|
29176
29284
|
\u2713 Patch applied by terminalhire`);
|
|
29177
29285
|
console.log(` branch: ${body.branch}`);
|
|
@@ -29179,6 +29287,7 @@ async function submitFounderPatch({ claims, claim, id, wt, flags }) {
|
|
|
29179
29287
|
if (Array.isArray(body.touchedPaths)) console.log(` touched: ${body.touchedPaths.join(", ")}`);
|
|
29180
29288
|
console.log(`
|
|
29181
29289
|
Read the CI result: terminalhire claim runs ${id} --watch`);
|
|
29290
|
+
await beatFounderPresence(submitted ?? claim);
|
|
29182
29291
|
}
|
|
29183
29292
|
async function cmdSubmit(id, flags = {}) {
|
|
29184
29293
|
const worktreeOverride = flags.worktree;
|
|
@@ -29666,9 +29775,18 @@ function renderAutoConsent() {
|
|
|
29666
29775
|
console.log(" pushing the SAME score-free fields, at most once/day \u2014 until you run");
|
|
29667
29776
|
console.log(" `terminalhire claim --push --revoke`.");
|
|
29668
29777
|
console.log("");
|
|
29669
|
-
console.log("
|
|
29670
|
-
console.log("
|
|
29671
|
-
console.log("
|
|
29778
|
+
console.log(" It also lets a FOUNDER see, on their own posting, roughly when you were");
|
|
29779
|
+
console.log(" last at the keyboard on a claim of theirs (active / idle). Meaningful");
|
|
29780
|
+
console.log(" steps send a timestamp and nothing else \u2014 never a note, never progress,");
|
|
29781
|
+
console.log(" never a rating, and never on OSS claims, which stay entirely local.");
|
|
29782
|
+
console.log("");
|
|
29783
|
+
console.log(" This stores an encrypted credential on this machine. It can");
|
|
29784
|
+
console.log(" add/update your OWN dashboard claim mirror, register a new");
|
|
29785
|
+
console.log(" claim on a founder posting in your name, write that coarse");
|
|
29786
|
+
console.log(" presence on founder claims you own, and re-read the slice and");
|
|
29787
|
+
console.log(" CI results for those same claims. It can never delete, and it");
|
|
29788
|
+
console.log(" cannot touch anyone else's claims. The daily dashboard payload");
|
|
29789
|
+
console.log(" is otherwise identical to the manual push above.");
|
|
29672
29790
|
console.log("");
|
|
29673
29791
|
}
|
|
29674
29792
|
function backgroundEnableFailed(autoConsent, pushToken) {
|
|
@@ -29717,7 +29835,7 @@ async function cmdPush({ keepUpdated = false } = {}) {
|
|
|
29717
29835
|
console.log("\n Starting browser verification...");
|
|
29718
29836
|
let begin;
|
|
29719
29837
|
try {
|
|
29720
|
-
const r = await fetch(`${
|
|
29838
|
+
const r = await fetch(`${CLAIM_SYNC_BASE4}/api/claim-sync/begin`, {
|
|
29721
29839
|
method: "POST",
|
|
29722
29840
|
headers: { "Content-Type": "application/json" },
|
|
29723
29841
|
body: JSON.stringify({ hostname: osHostname() }),
|
|
@@ -29760,7 +29878,7 @@ async function cmdPush({ keepUpdated = false } = {}) {
|
|
|
29760
29878
|
let statusRes;
|
|
29761
29879
|
try {
|
|
29762
29880
|
statusRes = await fetch(
|
|
29763
|
-
`${
|
|
29881
|
+
`${CLAIM_SYNC_BASE4}/api/claim-sync/status?challenge=${encodeURIComponent(challenge)}`,
|
|
29764
29882
|
{ signal: AbortSignal.timeout(1e4) }
|
|
29765
29883
|
);
|
|
29766
29884
|
} catch {
|
|
@@ -29791,7 +29909,7 @@ async function cmdPush({ keepUpdated = false } = {}) {
|
|
|
29791
29909
|
console.log("\n Verified. Sharing your claims...");
|
|
29792
29910
|
let res;
|
|
29793
29911
|
try {
|
|
29794
|
-
res = await fetch(`${
|
|
29912
|
+
res = await fetch(`${CLAIM_SYNC_BASE4}/api/claim-sync`, {
|
|
29795
29913
|
method: "POST",
|
|
29796
29914
|
headers: { "Content-Type": "application/json" },
|
|
29797
29915
|
// autoConsent is included ONLY when the dev opted into background updates —
|
|
@@ -29918,7 +30036,7 @@ async function cmdRevoke() {
|
|
|
29918
30036
|
console.log("\n Requesting deletion...");
|
|
29919
30037
|
let res;
|
|
29920
30038
|
try {
|
|
29921
|
-
res = await fetch(`${
|
|
30039
|
+
res = await fetch(`${CLAIM_SYNC_BASE4}/api/claim-sync`, {
|
|
29922
30040
|
method: "DELETE",
|
|
29923
30041
|
headers: { "Content-Type": "application/json" },
|
|
29924
30042
|
body: JSON.stringify({ login, deleteToken }),
|
|
@@ -30223,6 +30341,7 @@ export {
|
|
|
30223
30341
|
SUBMIT_ACCEPTS,
|
|
30224
30342
|
SYNC_BACKGROUND_PUSH_ACTIVE_FIELD,
|
|
30225
30343
|
backgroundEnableFailed,
|
|
30344
|
+
beatFounderPresence,
|
|
30226
30345
|
buildAssignmentComment,
|
|
30227
30346
|
buildPatchSubmission,
|
|
30228
30347
|
buildStakeComment,
|
|
@@ -30259,6 +30378,7 @@ export {
|
|
|
30259
30378
|
pickStartableClaim,
|
|
30260
30379
|
printNextSteps,
|
|
30261
30380
|
readCredentialDisposition,
|
|
30381
|
+
renderAutoConsent,
|
|
30262
30382
|
renderClaimHistory,
|
|
30263
30383
|
renderRunView,
|
|
30264
30384
|
renderServerRefusal,
|
package/dist/bin/jpi-config.js
CHANGED
|
@@ -118,7 +118,7 @@ function parseNudgeMode(raw) {
|
|
|
118
118
|
function printMixValues() {
|
|
119
119
|
console.log(" Valid mix values (roles vs. contribution items on the ambient surface):");
|
|
120
120
|
console.log(" jobs \u2014 more roles, fewer contributions (contribute 5, roles ~15)");
|
|
121
|
-
console.log(" balanced \u2014
|
|
121
|
+
console.log(" balanced \u2014 contribution-first default (contribute 10, roles ~10)");
|
|
122
122
|
console.log(" credential \u2014 contribution-forward (contribute 12, roles ~8)");
|
|
123
123
|
}
|
|
124
124
|
async function run() {
|