terminalhire 0.42.27 → 0.42.28

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.
@@ -106,7 +106,7 @@ function buildApprovalsNudge(awaitingApproval) {
106
106
  if (!Number.isInteger(awaitingApproval) || awaitingApproval <= 0) return null;
107
107
  const n = awaitingApproval;
108
108
  return ` \u26A0 ${n} claim${n === 1 ? "" : "s"} awaiting poster approval \u2014 terminalhire cannot check in the background until you enrol:
109
- terminalhire claim --push --keep-updated (or check one now: terminalhire claim slice <id>)`;
109
+ terminalhire claim --push --keep-updated (or check one now: terminalhire claim start <id>)`;
110
110
  }
111
111
  async function syncApprovedClaims({
112
112
  readAutoMarker,
@@ -30492,7 +30492,10 @@ var init_boundary = __esm({
30492
30492
  },
30493
30493
  {
30494
30494
  code: "lockfile",
30495
- matches: (_p, _segments, base) => LOCKFILE_NAMES.has(base),
30495
+ // The `.lock` suffix is the server's rule since TERM-412; this copy missed it, so
30496
+ // the pre-flight passed `uv.lock` and bun's text `bun.lock` that the server refuses
30497
+ // (TERM-1235). The name set only covers lockfiles whose names do not end in `.lock`.
30498
+ matches: (_p, _segments, base) => LOCKFILE_NAMES.has(base) || base.endsWith(".lock"),
30496
30499
  why: "is a dependency lockfile. Nobody reads a lockfile diff, so a change to one is refused rather than shown"
30497
30500
  }
30498
30501
  ];
@@ -37853,9 +37856,13 @@ function fmtContestedWarning(b) {
37853
37856
  }
37854
37857
  return ` \u26A0 This issue looks taken: ${parts.join(" / ")}. A merged PR here is unlikely.`;
37855
37858
  }
37856
- async function mintRegistrationProof({ claimRef } = {}) {
37857
- console.log("\n This posting registers your claim with terminalhire, so your");
37858
- console.log(" GitHub identity has to be verified once in the browser.");
37859
+ async function mintRegistrationProof({ claimRef, intro } = {}) {
37860
+ console.log("");
37861
+ for (const line of intro ?? [
37862
+ "This posting registers your claim with terminalhire, so your",
37863
+ "GitHub identity has to be verified once in the browser."
37864
+ ])
37865
+ console.log(` ${line}`);
37859
37866
  let oauthBase;
37860
37867
  try {
37861
37868
  oauthBase = resolveOAuthBase();
@@ -38182,17 +38189,18 @@ function readCredentialDisposition({
38182
38189
  return "store-and-clear-stale-marker";
38183
38190
  }
38184
38191
  async function bootstrapFounderClaimEnrollment(claim, registration) {
38185
- if (!(claim.amountUSD > 0)) return { stored: false, reason: "free" };
38186
38192
  if (!registration.pushToken || !registration.claimantLogin) {
38187
38193
  return { stored: false, reason: "token-unavailable" };
38188
38194
  }
38195
+ const current = await readPushTokenEnc().catch(() => null);
38189
38196
  const disposition = readCredentialDisposition({
38190
38197
  markerExists: Boolean(readAutoMarker()),
38191
- enrolledTokenExists: Boolean(await readPushTokenEnc().catch(() => null))
38198
+ enrolledTokenExists: Boolean(current)
38192
38199
  });
38193
38200
  if (disposition === "keep-enrolled") {
38194
38201
  return { stored: false, reason: "kept-enrolled" };
38195
38202
  }
38203
+ if (current && current === registration.pushToken) return { stored: true, reason: "ok" };
38196
38204
  try {
38197
38205
  await writePushTokenEnc(registration.pushToken);
38198
38206
  if (disposition === "store-and-clear-stale-marker") clearAutoMarker();
@@ -38413,7 +38421,7 @@ terminalhire claim: refusing to record \u2014 read ${b.repoFullName}'s contribut
38413
38421
  console.log(
38414
38422
  ` registered with terminalhire${claim.approval.claimId ? ` (server claim ${claim.approval.claimId})` : ""}`
38415
38423
  );
38416
- if (claim.amountUSD > 0) {
38424
+ if (enrollment) {
38417
38425
  if (enrollment?.stored) {
38418
38426
  console.log(" \u2713 credential for your granted slice + CI results stored encrypted here");
38419
38427
  console.log(" Background dashboard updates are NOT on (claiming does not enable them).");
@@ -38426,7 +38434,7 @@ terminalhire claim: refusing to record \u2014 read ${b.repoFullName}'s contribut
38426
38434
  if (enrollment?.detail) {
38427
38435
  for (const line of String(enrollment.detail).split("\n")) console.log(` ${line}`);
38428
38436
  }
38429
- console.log(" Store it before fetching your slice: terminalhire claim --push");
38437
+ console.log(" `claim start` will ask you to confirm in the browser once more.");
38430
38438
  }
38431
38439
  }
38432
38440
  if (claim.approval.state === "pending") {
@@ -38588,7 +38596,7 @@ async function cmdList(active) {
38588
38596
  );
38589
38597
  return;
38590
38598
  }
38591
- await syncFounderApprovals(claims, list);
38599
+ const { approvalsChecked } = await syncFounderApprovals(claims, list);
38592
38600
  list = claims.listClaims({ active });
38593
38601
  await syncFounderVerdicts({
38594
38602
  claimsModule: claims,
@@ -38606,7 +38614,7 @@ ${list.length} ${active ? "active " : ""}claim${list.length === 1 ? "" : "s"}:
38606
38614
  `);
38607
38615
  for (const c of list) {
38608
38616
  const pr = c.prUrl ? ` \xB7 ${c.prUrl}` : "";
38609
- console.log(` [${claimTurnLabel(c.state)}] ${fmtClaimAmount(c)} \xB7 ${c.title}`);
38617
+ console.log(` [${claimListTurnLabel(c, approvalsChecked)}] ${fmtClaimAmount(c)} \xB7 ${c.title}`);
38610
38618
  console.log(` id: ${c.id} \xB7 ${c.state}${pr}`);
38611
38619
  }
38612
38620
  printMetric(claims.acceptedPRRate());
@@ -38714,6 +38722,12 @@ async function syncFounderApprovals(claimsModule, targets) {
38714
38722
  }
38715
38723
  return { pushToken, approvalsChecked, approvalsUnavailable };
38716
38724
  }
38725
+ function claimListTurnLabel(claim, approvalsChecked) {
38726
+ if (approvalsChecked && claim.state === "claimed" && claim.approval?.mode === "approval-only" && claim.approval?.state === "pending") {
38727
+ return "waiting on poster";
38728
+ }
38729
+ return claimTurnLabel(claim.state);
38730
+ }
38717
38731
  function founderClaimStanding(claim, approvalsChecked) {
38718
38732
  const turn = claimTurnLabel(claim.state);
38719
38733
  switch (claim.state) {
@@ -39956,12 +39970,74 @@ terminalhire claim: ${what} needs the stored credential, and the server says it
39956
39970
  }
39957
39971
  return true;
39958
39972
  }
39959
- async function requireReadPushToken(what) {
39973
+ async function enrolHeldClaimReadToken(postingId) {
39974
+ const fail = (lines) => {
39975
+ for (const line of lines) console.error(line);
39976
+ process.exit(1);
39977
+ };
39978
+ const proofToken = await mintRegistrationProof({
39979
+ claimRef: opportunityShortToken(`bounty:founder:${postingId}`),
39980
+ intro: [
39981
+ "This machine holds no terminalhire credential yet. Confirm once in the",
39982
+ "browser, signed in as the GitHub account that claimed this work, and",
39983
+ "this machine is linked to your claim."
39984
+ ]
39985
+ });
39986
+ if (!proofToken) {
39987
+ fail(["terminalhire claim: could not link this machine to your claim (see above)."]);
39988
+ }
39989
+ let res;
39990
+ try {
39991
+ res = await fetch(`${CLAIM_SYNC_BASE4}/api/claim/enrol`, {
39992
+ method: "POST",
39993
+ headers: { "Content-Type": "application/json" },
39994
+ body: JSON.stringify({ bountyId: postingId, proofToken }),
39995
+ signal: AbortSignal.timeout(CLAIM_SYNC_WRITE_TIMEOUT_MS)
39996
+ });
39997
+ } catch (err) {
39998
+ fail([
39999
+ `terminalhire claim: terminalhire is unreachable (${err instanceof Error ? err.message : String(err)}).`,
40000
+ " Nothing was stored. Run the same command again."
40001
+ ]);
40002
+ }
40003
+ let body = null;
40004
+ try {
40005
+ body = await res.json();
40006
+ } catch {
40007
+ }
40008
+ if (res.status === 404 && body?.error === "no-live-claim") {
40009
+ const who = typeof body.claimantLogin === "string" ? `@${body.claimantLogin}` : "that account";
40010
+ fail([
40011
+ `terminalhire claim: you confirmed in the browser as ${who}, and ${who} does not`,
40012
+ " hold a live claim on this posting. Nothing was stored.",
40013
+ " Sign the browser into the GitHub account that claimed it, then run this again."
40014
+ ]);
40015
+ }
40016
+ if (!res.ok || typeof body?.pushToken !== "string" || body.pushToken.length === 0) {
40017
+ fail([
40018
+ `terminalhire claim: linking this machine failed (${res.status}${typeof body?.message === "string" ? `: ${body.message}` : ""}). Nothing was stored.`
40019
+ ]);
40020
+ }
40021
+ try {
40022
+ await writePushTokenEnc(body.pushToken);
40023
+ } catch (err) {
40024
+ fail([
40025
+ `terminalhire claim: the credential could not be stored on this machine: ${err instanceof Error ? err.message : String(err)}`
40026
+ ]);
40027
+ }
40028
+ console.log(" \u2713 This machine is linked to your claim (credential stored encrypted here).");
40029
+ return body.pushToken;
40030
+ }
40031
+ var FOUNDER_POSTING_ID = /^fb_[A-Za-z0-9_-]{1,80}$/;
40032
+ async function requireReadPushToken(what, { enrolFor } = {}) {
39960
40033
  let stored = null;
39961
40034
  try {
39962
40035
  stored = await readPushTokenEnc();
39963
40036
  } catch {
39964
40037
  }
40038
+ if (!stored && typeof enrolFor === "string" && FOUNDER_POSTING_ID.test(enrolFor)) {
40039
+ return enrolHeldClaimReadToken(enrolFor);
40040
+ }
39965
40041
  if (!stored) {
39966
40042
  console.error(
39967
40043
  `terminalhire claim: ${what} needs the persistent push token, and none is enrolled on this machine.
@@ -39984,7 +40060,9 @@ function cloneFullTierRepo({ engine, dest, url, sha, token, branch }) {
39984
40060
  async function cmdSliceFullTier(claims, id, local, fullTierBody, flags, cloneRepo = cloneFullTierRepo) {
39985
40061
  const claimId = fullTierBody.claimId;
39986
40062
  const bountyId = fullTierBody.bountyId;
39987
- const pushToken = await requireReadPushToken("fetching your full-repo clone credential");
40063
+ const pushToken = await requireReadPushToken("fetching your full-repo clone credential", {
40064
+ enrolFor: bountyId
40065
+ });
39988
40066
  let res;
39989
40067
  try {
39990
40068
  res = await fetch(`${CLAIM_SYNC_BASE4}/api/claim/clone-token`, {
@@ -40160,7 +40238,9 @@ async function attemptSliceDelivery(id, flags = {}) {
40160
40238
  requireFounderLoopClaim(claims, id, "slice");
40161
40239
  }
40162
40240
  const bountyId = local ? founderPostingIdOf(local) : String(id).replace(/^bounty:founder:/, "");
40163
- const pushToken = await requireReadPushToken("fetching your granted slice");
40241
+ const pushToken = await requireReadPushToken("fetching your granted slice", {
40242
+ enrolFor: bountyId
40243
+ });
40164
40244
  let res;
40165
40245
  try {
40166
40246
  res = await fetch(`${CLAIM_SYNC_BASE4}/api/claim/slice`, {
@@ -41978,6 +42058,7 @@ export {
41978
42058
  buildStakeComment,
41979
42059
  buildStandDownComment,
41980
42060
  buildSubmitBody,
42061
+ claimListTurnLabel,
41981
42062
  claimUpdatePatch,
41982
42063
  cloneFullTierRepo,
41983
42064
  cmdNote,
@@ -36235,7 +36235,10 @@ var init_boundary = __esm({
36235
36235
  },
36236
36236
  {
36237
36237
  code: "lockfile",
36238
- matches: (_p, _segments, base) => LOCKFILE_NAMES.has(base),
36238
+ // The `.lock` suffix is the server's rule since TERM-412; this copy missed it, so
36239
+ // the pre-flight passed `uv.lock` and bun's text `bun.lock` that the server refuses
36240
+ // (TERM-1235). The name set only covers lockfiles whose names do not end in `.lock`.
36241
+ matches: (_p, _segments, base) => LOCKFILE_NAMES.has(base) || base.endsWith(".lock"),
36239
36242
  why: "is a dependency lockfile. Nobody reads a lockfile diff, so a change to one is refused rather than shown"
36240
36243
  }
36241
36244
  ];
@@ -41989,6 +41992,7 @@ __export(jpi_claim_exports, {
41989
41992
  buildStakeComment: () => buildStakeComment,
41990
41993
  buildStandDownComment: () => buildStandDownComment,
41991
41994
  buildSubmitBody: () => buildSubmitBody,
41995
+ claimListTurnLabel: () => claimListTurnLabel,
41992
41996
  claimUpdatePatch: () => claimUpdatePatch,
41993
41997
  cloneFullTierRepo: () => cloneFullTierRepo,
41994
41998
  cmdNote: () => cmdNote,
@@ -43171,9 +43175,13 @@ function fmtContestedWarning(b) {
43171
43175
  }
43172
43176
  return ` \u26A0 This issue looks taken: ${parts.join(" / ")}. A merged PR here is unlikely.`;
43173
43177
  }
43174
- async function mintRegistrationProof({ claimRef } = {}) {
43175
- console.log("\n This posting registers your claim with terminalhire, so your");
43176
- console.log(" GitHub identity has to be verified once in the browser.");
43178
+ async function mintRegistrationProof({ claimRef, intro } = {}) {
43179
+ console.log("");
43180
+ for (const line of intro ?? [
43181
+ "This posting registers your claim with terminalhire, so your",
43182
+ "GitHub identity has to be verified once in the browser."
43183
+ ])
43184
+ console.log(` ${line}`);
43177
43185
  let oauthBase2;
43178
43186
  try {
43179
43187
  oauthBase2 = resolveOAuthBase();
@@ -43475,17 +43483,18 @@ function readCredentialDisposition({
43475
43483
  return "store-and-clear-stale-marker";
43476
43484
  }
43477
43485
  async function bootstrapFounderClaimEnrollment(claim, registration) {
43478
- if (!(claim.amountUSD > 0)) return { stored: false, reason: "free" };
43479
43486
  if (!registration.pushToken || !registration.claimantLogin) {
43480
43487
  return { stored: false, reason: "token-unavailable" };
43481
43488
  }
43489
+ const current = await readPushTokenEnc().catch(() => null);
43482
43490
  const disposition = readCredentialDisposition({
43483
43491
  markerExists: Boolean(readAutoMarker()),
43484
- enrolledTokenExists: Boolean(await readPushTokenEnc().catch(() => null))
43492
+ enrolledTokenExists: Boolean(current)
43485
43493
  });
43486
43494
  if (disposition === "keep-enrolled") {
43487
43495
  return { stored: false, reason: "kept-enrolled" };
43488
43496
  }
43497
+ if (current && current === registration.pushToken) return { stored: true, reason: "ok" };
43489
43498
  try {
43490
43499
  await writePushTokenEnc(registration.pushToken);
43491
43500
  if (disposition === "store-and-clear-stale-marker") clearAutoMarker();
@@ -43706,7 +43715,7 @@ terminalhire claim: refusing to record \u2014 read ${b.repoFullName}'s contribut
43706
43715
  console.log(
43707
43716
  ` registered with terminalhire${claim.approval.claimId ? ` (server claim ${claim.approval.claimId})` : ""}`
43708
43717
  );
43709
- if (claim.amountUSD > 0) {
43718
+ if (enrollment) {
43710
43719
  if (enrollment?.stored) {
43711
43720
  console.log(" \u2713 credential for your granted slice + CI results stored encrypted here");
43712
43721
  console.log(" Background dashboard updates are NOT on (claiming does not enable them).");
@@ -43719,7 +43728,7 @@ terminalhire claim: refusing to record \u2014 read ${b.repoFullName}'s contribut
43719
43728
  if (enrollment?.detail) {
43720
43729
  for (const line of String(enrollment.detail).split("\n")) console.log(` ${line}`);
43721
43730
  }
43722
- console.log(" Store it before fetching your slice: terminalhire claim --push");
43731
+ console.log(" `claim start` will ask you to confirm in the browser once more.");
43723
43732
  }
43724
43733
  }
43725
43734
  if (claim.approval.state === "pending") {
@@ -43881,7 +43890,7 @@ async function cmdList(active) {
43881
43890
  );
43882
43891
  return;
43883
43892
  }
43884
- await syncFounderApprovals(claims, list);
43893
+ const { approvalsChecked } = await syncFounderApprovals(claims, list);
43885
43894
  list = claims.listClaims({ active });
43886
43895
  await syncFounderVerdicts({
43887
43896
  claimsModule: claims,
@@ -43899,7 +43908,7 @@ ${list.length} ${active ? "active " : ""}claim${list.length === 1 ? "" : "s"}:
43899
43908
  `);
43900
43909
  for (const c of list) {
43901
43910
  const pr = c.prUrl ? ` \xB7 ${c.prUrl}` : "";
43902
- console.log(` [${claimTurnLabel(c.state)}] ${fmtClaimAmount(c)} \xB7 ${c.title}`);
43911
+ console.log(` [${claimListTurnLabel(c, approvalsChecked)}] ${fmtClaimAmount(c)} \xB7 ${c.title}`);
43903
43912
  console.log(` id: ${c.id} \xB7 ${c.state}${pr}`);
43904
43913
  }
43905
43914
  printMetric(claims.acceptedPRRate());
@@ -44006,6 +44015,12 @@ async function syncFounderApprovals(claimsModule, targets) {
44006
44015
  }
44007
44016
  return { pushToken, approvalsChecked, approvalsUnavailable };
44008
44017
  }
44018
+ function claimListTurnLabel(claim, approvalsChecked) {
44019
+ if (approvalsChecked && claim.state === "claimed" && claim.approval?.mode === "approval-only" && claim.approval?.state === "pending") {
44020
+ return "waiting on poster";
44021
+ }
44022
+ return claimTurnLabel(claim.state);
44023
+ }
44009
44024
  function founderClaimStanding(claim, approvalsChecked) {
44010
44025
  const turn = claimTurnLabel(claim.state);
44011
44026
  switch (claim.state) {
@@ -45219,12 +45234,73 @@ terminalhire claim: ${what} needs the stored credential, and the server says it
45219
45234
  }
45220
45235
  return true;
45221
45236
  }
45222
- async function requireReadPushToken(what) {
45237
+ async function enrolHeldClaimReadToken(postingId) {
45238
+ const fail = (lines) => {
45239
+ for (const line of lines) console.error(line);
45240
+ process.exit(1);
45241
+ };
45242
+ const proofToken = await mintRegistrationProof({
45243
+ claimRef: opportunityShortToken(`bounty:founder:${postingId}`),
45244
+ intro: [
45245
+ "This machine holds no terminalhire credential yet. Confirm once in the",
45246
+ "browser, signed in as the GitHub account that claimed this work, and",
45247
+ "this machine is linked to your claim."
45248
+ ]
45249
+ });
45250
+ if (!proofToken) {
45251
+ fail(["terminalhire claim: could not link this machine to your claim (see above)."]);
45252
+ }
45253
+ let res;
45254
+ try {
45255
+ res = await fetch(`${CLAIM_SYNC_BASE4}/api/claim/enrol`, {
45256
+ method: "POST",
45257
+ headers: { "Content-Type": "application/json" },
45258
+ body: JSON.stringify({ bountyId: postingId, proofToken }),
45259
+ signal: AbortSignal.timeout(CLAIM_SYNC_WRITE_TIMEOUT_MS)
45260
+ });
45261
+ } catch (err) {
45262
+ fail([
45263
+ `terminalhire claim: terminalhire is unreachable (${err instanceof Error ? err.message : String(err)}).`,
45264
+ " Nothing was stored. Run the same command again."
45265
+ ]);
45266
+ }
45267
+ let body = null;
45268
+ try {
45269
+ body = await res.json();
45270
+ } catch {
45271
+ }
45272
+ if (res.status === 404 && body?.error === "no-live-claim") {
45273
+ const who = typeof body.claimantLogin === "string" ? `@${body.claimantLogin}` : "that account";
45274
+ fail([
45275
+ `terminalhire claim: you confirmed in the browser as ${who}, and ${who} does not`,
45276
+ " hold a live claim on this posting. Nothing was stored.",
45277
+ " Sign the browser into the GitHub account that claimed it, then run this again."
45278
+ ]);
45279
+ }
45280
+ if (!res.ok || typeof body?.pushToken !== "string" || body.pushToken.length === 0) {
45281
+ fail([
45282
+ `terminalhire claim: linking this machine failed (${res.status}${typeof body?.message === "string" ? `: ${body.message}` : ""}). Nothing was stored.`
45283
+ ]);
45284
+ }
45285
+ try {
45286
+ await writePushTokenEnc(body.pushToken);
45287
+ } catch (err) {
45288
+ fail([
45289
+ `terminalhire claim: the credential could not be stored on this machine: ${err instanceof Error ? err.message : String(err)}`
45290
+ ]);
45291
+ }
45292
+ console.log(" \u2713 This machine is linked to your claim (credential stored encrypted here).");
45293
+ return body.pushToken;
45294
+ }
45295
+ async function requireReadPushToken(what, { enrolFor } = {}) {
45223
45296
  let stored = null;
45224
45297
  try {
45225
45298
  stored = await readPushTokenEnc();
45226
45299
  } catch {
45227
45300
  }
45301
+ if (!stored && typeof enrolFor === "string" && FOUNDER_POSTING_ID.test(enrolFor)) {
45302
+ return enrolHeldClaimReadToken(enrolFor);
45303
+ }
45228
45304
  if (!stored) {
45229
45305
  console.error(
45230
45306
  `terminalhire claim: ${what} needs the persistent push token, and none is enrolled on this machine.
@@ -45247,7 +45323,9 @@ function cloneFullTierRepo({ engine, dest, url, sha, token, branch }) {
45247
45323
  async function cmdSliceFullTier(claims, id, local, fullTierBody, flags, cloneRepo = cloneFullTierRepo) {
45248
45324
  const claimId = fullTierBody.claimId;
45249
45325
  const bountyId = fullTierBody.bountyId;
45250
- const pushToken = await requireReadPushToken("fetching your full-repo clone credential");
45326
+ const pushToken = await requireReadPushToken("fetching your full-repo clone credential", {
45327
+ enrolFor: bountyId
45328
+ });
45251
45329
  let res;
45252
45330
  try {
45253
45331
  res = await fetch(`${CLAIM_SYNC_BASE4}/api/claim/clone-token`, {
@@ -45423,7 +45501,9 @@ async function attemptSliceDelivery(id, flags = {}) {
45423
45501
  requireFounderLoopClaim(claims, id, "slice");
45424
45502
  }
45425
45503
  const bountyId = local ? founderPostingIdOf(local) : String(id).replace(/^bounty:founder:/, "");
45426
- const pushToken = await requireReadPushToken("fetching your granted slice");
45504
+ const pushToken = await requireReadPushToken("fetching your granted slice", {
45505
+ enrolFor: bountyId
45506
+ });
45427
45507
  let res;
45428
45508
  try {
45429
45509
  res = await fetch(`${CLAIM_SYNC_BASE4}/api/claim/slice`, {
@@ -47197,7 +47277,7 @@ async function run7() {
47197
47277
  process.exit(1);
47198
47278
  }
47199
47279
  }
47200
- var TERMINALHIRE_DIR17, INDEX_CACHE_FILE5, CLAIM_PUSH_MARKER, REPO_CONTINUITY_NUDGE_MARKER, API_URL6, CLAIM_SYNC_BASE4, CLAIM_CONSENT_VERSION, CLAIM_POLL_INTERVAL_MS, CLAIM_POLL_TIMEOUT_MS, CLAIM_SYNC_WRITE_TIMEOUT_MS, GH_API3, GH_HEADERS2, CONTENTION_HINT, AI_DISCLOSURE_NOTE, pExecFile, VALUE_FLAGS, ASSIGNMENT_MARKER, STAKE_MARKER, STANDDOWN_MARKER, OUR_MARKERS, STAKE_POST_TIMEOUT_MS, STAKE_POSTING_GRACE_MS, TAKE_BOT_REPOS, SUBMIT_ACCEPTS, REVISE_RECOVERY_STATES, CLOSED_STATES, GH_SESSION_COOKIE4, PUSH_TOKEN_REFUSAL, SYNC_BACKGROUND_PUSH_ACTIVE_FIELD, ISSUE_OUTCOME_TERMINAL, RUNS_POLL_INTERVAL_MS, RUNS_POLL_ATTEMPTS, OPENABLE_AGENTS, BRIEF_DIR, BRIEF_REL_PATH, VERIFY_REL_PATH, AGENTS_REL_PATH, BRIEF_EXCLUDE_LINE, PACK_SAFE_ID, CLAIM_EVENT_LABEL, LINE_BREAKS, CONTROL_CHARS3, CLAIM_RESOLUTION_REASONS, POSTING_LEVEL_RESOLUTION_REASONS, RESOLUTION_REASON_BLURB;
47280
+ var TERMINALHIRE_DIR17, INDEX_CACHE_FILE5, CLAIM_PUSH_MARKER, REPO_CONTINUITY_NUDGE_MARKER, API_URL6, CLAIM_SYNC_BASE4, CLAIM_CONSENT_VERSION, CLAIM_POLL_INTERVAL_MS, CLAIM_POLL_TIMEOUT_MS, CLAIM_SYNC_WRITE_TIMEOUT_MS, GH_API3, GH_HEADERS2, CONTENTION_HINT, AI_DISCLOSURE_NOTE, pExecFile, VALUE_FLAGS, ASSIGNMENT_MARKER, STAKE_MARKER, STANDDOWN_MARKER, OUR_MARKERS, STAKE_POST_TIMEOUT_MS, STAKE_POSTING_GRACE_MS, TAKE_BOT_REPOS, SUBMIT_ACCEPTS, REVISE_RECOVERY_STATES, CLOSED_STATES, GH_SESSION_COOKIE4, PUSH_TOKEN_REFUSAL, SYNC_BACKGROUND_PUSH_ACTIVE_FIELD, ISSUE_OUTCOME_TERMINAL, RUNS_POLL_INTERVAL_MS, RUNS_POLL_ATTEMPTS, OPENABLE_AGENTS, BRIEF_DIR, BRIEF_REL_PATH, VERIFY_REL_PATH, AGENTS_REL_PATH, BRIEF_EXCLUDE_LINE, PACK_SAFE_ID, CLAIM_EVENT_LABEL, LINE_BREAKS, CONTROL_CHARS3, FOUNDER_POSTING_ID, CLAIM_RESOLUTION_REASONS, POSTING_LEVEL_RESOLUTION_REASONS, RESOLUTION_REASON_BLURB;
47201
47281
  var init_jpi_claim = __esm({
47202
47282
  "bin/jpi-claim.js"() {
47203
47283
  "use strict";
@@ -47313,6 +47393,7 @@ var init_jpi_claim = __esm({
47313
47393
  };
47314
47394
  LINE_BREAKS = /\r\n|[\r\n\v\f\u0085\u2028\u2029]/;
47315
47395
  CONTROL_CHARS3 = /[\u0000-\u001F\u007F-\u009F]/g;
47396
+ FOUNDER_POSTING_ID = /^fb_[A-Za-z0-9_-]{1,80}$/;
47316
47397
  CLAIM_RESOLUTION_REASONS = [
47317
47398
  "not-my-stack",
47318
47399
  "out-of-time",
@@ -79452,7 +79533,7 @@ function buildApprovalsNudge(awaitingApproval) {
79452
79533
  if (!Number.isInteger(awaitingApproval) || awaitingApproval <= 0) return null;
79453
79534
  const n = awaitingApproval;
79454
79535
  return ` \u26A0 ${n} claim${n === 1 ? "" : "s"} awaiting poster approval \u2014 terminalhire cannot check in the background until you enrol:
79455
- terminalhire claim --push --keep-updated (or check one now: terminalhire claim slice <id>)`;
79536
+ terminalhire claim --push --keep-updated (or check one now: terminalhire claim start <id>)`;
79456
79537
  }
79457
79538
  async function syncApprovedClaims({
79458
79539
  readAutoMarker: readAutoMarker2,
@@ -31010,7 +31010,10 @@ var init_boundary = __esm({
31010
31010
  },
31011
31011
  {
31012
31012
  code: "lockfile",
31013
- matches: (_p, _segments, base) => LOCKFILE_NAMES.has(base),
31013
+ // The `.lock` suffix is the server's rule since TERM-412; this copy missed it, so
31014
+ // the pre-flight passed `uv.lock` and bun's text `bun.lock` that the server refuses
31015
+ // (TERM-1235). The name set only covers lockfiles whose names do not end in `.lock`.
31016
+ matches: (_p, _segments, base) => LOCKFILE_NAMES.has(base) || base.endsWith(".lock"),
31014
31017
  why: "is a dependency lockfile. Nobody reads a lockfile diff, so a change to one is refused rather than shown"
31015
31018
  }
31016
31019
  ];
@@ -36764,6 +36767,7 @@ __export(jpi_claim_exports, {
36764
36767
  buildStakeComment: () => buildStakeComment,
36765
36768
  buildStandDownComment: () => buildStandDownComment,
36766
36769
  buildSubmitBody: () => buildSubmitBody,
36770
+ claimListTurnLabel: () => claimListTurnLabel,
36767
36771
  claimUpdatePatch: () => claimUpdatePatch,
36768
36772
  cloneFullTierRepo: () => cloneFullTierRepo,
36769
36773
  cmdNote: () => cmdNote,
@@ -37946,9 +37950,13 @@ function fmtContestedWarning(b) {
37946
37950
  }
37947
37951
  return ` \u26A0 This issue looks taken: ${parts.join(" / ")}. A merged PR here is unlikely.`;
37948
37952
  }
37949
- async function mintRegistrationProof({ claimRef } = {}) {
37950
- console.log("\n This posting registers your claim with terminalhire, so your");
37951
- console.log(" GitHub identity has to be verified once in the browser.");
37953
+ async function mintRegistrationProof({ claimRef, intro } = {}) {
37954
+ console.log("");
37955
+ for (const line of intro ?? [
37956
+ "This posting registers your claim with terminalhire, so your",
37957
+ "GitHub identity has to be verified once in the browser."
37958
+ ])
37959
+ console.log(` ${line}`);
37952
37960
  let oauthBase;
37953
37961
  try {
37954
37962
  oauthBase = resolveOAuthBase();
@@ -38250,17 +38258,18 @@ function readCredentialDisposition({
38250
38258
  return "store-and-clear-stale-marker";
38251
38259
  }
38252
38260
  async function bootstrapFounderClaimEnrollment(claim, registration) {
38253
- if (!(claim.amountUSD > 0)) return { stored: false, reason: "free" };
38254
38261
  if (!registration.pushToken || !registration.claimantLogin) {
38255
38262
  return { stored: false, reason: "token-unavailable" };
38256
38263
  }
38264
+ const current = await readPushTokenEnc().catch(() => null);
38257
38265
  const disposition = readCredentialDisposition({
38258
38266
  markerExists: Boolean(readAutoMarker()),
38259
- enrolledTokenExists: Boolean(await readPushTokenEnc().catch(() => null))
38267
+ enrolledTokenExists: Boolean(current)
38260
38268
  });
38261
38269
  if (disposition === "keep-enrolled") {
38262
38270
  return { stored: false, reason: "kept-enrolled" };
38263
38271
  }
38272
+ if (current && current === registration.pushToken) return { stored: true, reason: "ok" };
38264
38273
  try {
38265
38274
  await writePushTokenEnc(registration.pushToken);
38266
38275
  if (disposition === "store-and-clear-stale-marker") clearAutoMarker();
@@ -38481,7 +38490,7 @@ terminalhire claim: refusing to record \u2014 read ${b.repoFullName}'s contribut
38481
38490
  console.log(
38482
38491
  ` registered with terminalhire${claim.approval.claimId ? ` (server claim ${claim.approval.claimId})` : ""}`
38483
38492
  );
38484
- if (claim.amountUSD > 0) {
38493
+ if (enrollment) {
38485
38494
  if (enrollment?.stored) {
38486
38495
  console.log(" \u2713 credential for your granted slice + CI results stored encrypted here");
38487
38496
  console.log(" Background dashboard updates are NOT on (claiming does not enable them).");
@@ -38494,7 +38503,7 @@ terminalhire claim: refusing to record \u2014 read ${b.repoFullName}'s contribut
38494
38503
  if (enrollment?.detail) {
38495
38504
  for (const line of String(enrollment.detail).split("\n")) console.log(` ${line}`);
38496
38505
  }
38497
- console.log(" Store it before fetching your slice: terminalhire claim --push");
38506
+ console.log(" `claim start` will ask you to confirm in the browser once more.");
38498
38507
  }
38499
38508
  }
38500
38509
  if (claim.approval.state === "pending") {
@@ -38656,7 +38665,7 @@ async function cmdList(active) {
38656
38665
  );
38657
38666
  return;
38658
38667
  }
38659
- await syncFounderApprovals(claims, list);
38668
+ const { approvalsChecked } = await syncFounderApprovals(claims, list);
38660
38669
  list = claims.listClaims({ active });
38661
38670
  await syncFounderVerdicts({
38662
38671
  claimsModule: claims,
@@ -38674,7 +38683,7 @@ ${list.length} ${active ? "active " : ""}claim${list.length === 1 ? "" : "s"}:
38674
38683
  `);
38675
38684
  for (const c of list) {
38676
38685
  const pr = c.prUrl ? ` \xB7 ${c.prUrl}` : "";
38677
- console.log(` [${claimTurnLabel(c.state)}] ${fmtClaimAmount(c)} \xB7 ${c.title}`);
38686
+ console.log(` [${claimListTurnLabel(c, approvalsChecked)}] ${fmtClaimAmount(c)} \xB7 ${c.title}`);
38678
38687
  console.log(` id: ${c.id} \xB7 ${c.state}${pr}`);
38679
38688
  }
38680
38689
  printMetric(claims.acceptedPRRate());
@@ -38781,6 +38790,12 @@ async function syncFounderApprovals(claimsModule, targets) {
38781
38790
  }
38782
38791
  return { pushToken, approvalsChecked, approvalsUnavailable };
38783
38792
  }
38793
+ function claimListTurnLabel(claim, approvalsChecked) {
38794
+ if (approvalsChecked && claim.state === "claimed" && claim.approval?.mode === "approval-only" && claim.approval?.state === "pending") {
38795
+ return "waiting on poster";
38796
+ }
38797
+ return claimTurnLabel(claim.state);
38798
+ }
38784
38799
  function founderClaimStanding(claim, approvalsChecked) {
38785
38800
  const turn = claimTurnLabel(claim.state);
38786
38801
  switch (claim.state) {
@@ -39994,12 +40009,73 @@ terminalhire claim: ${what} needs the stored credential, and the server says it
39994
40009
  }
39995
40010
  return true;
39996
40011
  }
39997
- async function requireReadPushToken(what) {
40012
+ async function enrolHeldClaimReadToken(postingId) {
40013
+ const fail = (lines) => {
40014
+ for (const line of lines) console.error(line);
40015
+ process.exit(1);
40016
+ };
40017
+ const proofToken = await mintRegistrationProof({
40018
+ claimRef: opportunityShortToken(`bounty:founder:${postingId}`),
40019
+ intro: [
40020
+ "This machine holds no terminalhire credential yet. Confirm once in the",
40021
+ "browser, signed in as the GitHub account that claimed this work, and",
40022
+ "this machine is linked to your claim."
40023
+ ]
40024
+ });
40025
+ if (!proofToken) {
40026
+ fail(["terminalhire claim: could not link this machine to your claim (see above)."]);
40027
+ }
40028
+ let res;
40029
+ try {
40030
+ res = await fetch(`${CLAIM_SYNC_BASE4}/api/claim/enrol`, {
40031
+ method: "POST",
40032
+ headers: { "Content-Type": "application/json" },
40033
+ body: JSON.stringify({ bountyId: postingId, proofToken }),
40034
+ signal: AbortSignal.timeout(CLAIM_SYNC_WRITE_TIMEOUT_MS)
40035
+ });
40036
+ } catch (err) {
40037
+ fail([
40038
+ `terminalhire claim: terminalhire is unreachable (${err instanceof Error ? err.message : String(err)}).`,
40039
+ " Nothing was stored. Run the same command again."
40040
+ ]);
40041
+ }
40042
+ let body = null;
40043
+ try {
40044
+ body = await res.json();
40045
+ } catch {
40046
+ }
40047
+ if (res.status === 404 && body?.error === "no-live-claim") {
40048
+ const who = typeof body.claimantLogin === "string" ? `@${body.claimantLogin}` : "that account";
40049
+ fail([
40050
+ `terminalhire claim: you confirmed in the browser as ${who}, and ${who} does not`,
40051
+ " hold a live claim on this posting. Nothing was stored.",
40052
+ " Sign the browser into the GitHub account that claimed it, then run this again."
40053
+ ]);
40054
+ }
40055
+ if (!res.ok || typeof body?.pushToken !== "string" || body.pushToken.length === 0) {
40056
+ fail([
40057
+ `terminalhire claim: linking this machine failed (${res.status}${typeof body?.message === "string" ? `: ${body.message}` : ""}). Nothing was stored.`
40058
+ ]);
40059
+ }
40060
+ try {
40061
+ await writePushTokenEnc(body.pushToken);
40062
+ } catch (err) {
40063
+ fail([
40064
+ `terminalhire claim: the credential could not be stored on this machine: ${err instanceof Error ? err.message : String(err)}`
40065
+ ]);
40066
+ }
40067
+ console.log(" \u2713 This machine is linked to your claim (credential stored encrypted here).");
40068
+ return body.pushToken;
40069
+ }
40070
+ async function requireReadPushToken(what, { enrolFor } = {}) {
39998
40071
  let stored = null;
39999
40072
  try {
40000
40073
  stored = await readPushTokenEnc();
40001
40074
  } catch {
40002
40075
  }
40076
+ if (!stored && typeof enrolFor === "string" && FOUNDER_POSTING_ID.test(enrolFor)) {
40077
+ return enrolHeldClaimReadToken(enrolFor);
40078
+ }
40003
40079
  if (!stored) {
40004
40080
  console.error(
40005
40081
  `terminalhire claim: ${what} needs the persistent push token, and none is enrolled on this machine.
@@ -40022,7 +40098,9 @@ function cloneFullTierRepo({ engine, dest, url, sha, token, branch }) {
40022
40098
  async function cmdSliceFullTier(claims, id, local, fullTierBody, flags, cloneRepo = cloneFullTierRepo) {
40023
40099
  const claimId = fullTierBody.claimId;
40024
40100
  const bountyId = fullTierBody.bountyId;
40025
- const pushToken = await requireReadPushToken("fetching your full-repo clone credential");
40101
+ const pushToken = await requireReadPushToken("fetching your full-repo clone credential", {
40102
+ enrolFor: bountyId
40103
+ });
40026
40104
  let res;
40027
40105
  try {
40028
40106
  res = await fetch(`${CLAIM_SYNC_BASE4}/api/claim/clone-token`, {
@@ -40198,7 +40276,9 @@ async function attemptSliceDelivery(id, flags = {}) {
40198
40276
  requireFounderLoopClaim(claims, id, "slice");
40199
40277
  }
40200
40278
  const bountyId = local ? founderPostingIdOf(local) : String(id).replace(/^bounty:founder:/, "");
40201
- const pushToken = await requireReadPushToken("fetching your granted slice");
40279
+ const pushToken = await requireReadPushToken("fetching your granted slice", {
40280
+ enrolFor: bountyId
40281
+ });
40202
40282
  let res;
40203
40283
  try {
40204
40284
  res = await fetch(`${CLAIM_SYNC_BASE4}/api/claim/slice`, {
@@ -41972,7 +42052,7 @@ async function run() {
41972
42052
  process.exit(1);
41973
42053
  }
41974
42054
  }
41975
- var TERMINALHIRE_DIR11, INDEX_CACHE_FILE2, CLAIM_PUSH_MARKER, REPO_CONTINUITY_NUDGE_MARKER, API_URL, CLAIM_SYNC_BASE4, CLAIM_CONSENT_VERSION, CLAIM_POLL_INTERVAL_MS, CLAIM_POLL_TIMEOUT_MS, CLAIM_SYNC_WRITE_TIMEOUT_MS, GH_API2, GH_HEADERS2, CONTENTION_HINT, AI_DISCLOSURE_NOTE, pExecFile, VALUE_FLAGS, ASSIGNMENT_MARKER, STAKE_MARKER, STANDDOWN_MARKER, OUR_MARKERS, STAKE_POST_TIMEOUT_MS, STAKE_POSTING_GRACE_MS, TAKE_BOT_REPOS, SUBMIT_ACCEPTS, REVISE_RECOVERY_STATES, CLOSED_STATES, GH_SESSION_COOKIE, PUSH_TOKEN_REFUSAL, SYNC_BACKGROUND_PUSH_ACTIVE_FIELD, ISSUE_OUTCOME_TERMINAL, RUNS_POLL_INTERVAL_MS, RUNS_POLL_ATTEMPTS, OPENABLE_AGENTS, BRIEF_DIR, BRIEF_REL_PATH, VERIFY_REL_PATH, AGENTS_REL_PATH, BRIEF_EXCLUDE_LINE, PACK_SAFE_ID, CLAIM_EVENT_LABEL, LINE_BREAKS, CONTROL_CHARS3, CLAIM_RESOLUTION_REASONS, POSTING_LEVEL_RESOLUTION_REASONS, RESOLUTION_REASON_BLURB;
42055
+ var TERMINALHIRE_DIR11, INDEX_CACHE_FILE2, CLAIM_PUSH_MARKER, REPO_CONTINUITY_NUDGE_MARKER, API_URL, CLAIM_SYNC_BASE4, CLAIM_CONSENT_VERSION, CLAIM_POLL_INTERVAL_MS, CLAIM_POLL_TIMEOUT_MS, CLAIM_SYNC_WRITE_TIMEOUT_MS, GH_API2, GH_HEADERS2, CONTENTION_HINT, AI_DISCLOSURE_NOTE, pExecFile, VALUE_FLAGS, ASSIGNMENT_MARKER, STAKE_MARKER, STANDDOWN_MARKER, OUR_MARKERS, STAKE_POST_TIMEOUT_MS, STAKE_POSTING_GRACE_MS, TAKE_BOT_REPOS, SUBMIT_ACCEPTS, REVISE_RECOVERY_STATES, CLOSED_STATES, GH_SESSION_COOKIE, PUSH_TOKEN_REFUSAL, SYNC_BACKGROUND_PUSH_ACTIVE_FIELD, ISSUE_OUTCOME_TERMINAL, RUNS_POLL_INTERVAL_MS, RUNS_POLL_ATTEMPTS, OPENABLE_AGENTS, BRIEF_DIR, BRIEF_REL_PATH, VERIFY_REL_PATH, AGENTS_REL_PATH, BRIEF_EXCLUDE_LINE, PACK_SAFE_ID, CLAIM_EVENT_LABEL, LINE_BREAKS, CONTROL_CHARS3, FOUNDER_POSTING_ID, CLAIM_RESOLUTION_REASONS, POSTING_LEVEL_RESOLUTION_REASONS, RESOLUTION_REASON_BLURB;
41976
42056
  var init_jpi_claim = __esm({
41977
42057
  "bin/jpi-claim.js"() {
41978
42058
  "use strict";
@@ -42088,6 +42168,7 @@ var init_jpi_claim = __esm({
42088
42168
  };
42089
42169
  LINE_BREAKS = /\r\n|[\r\n\v\f\u0085\u2028\u2029]/;
42090
42170
  CONTROL_CHARS3 = /[\u0000-\u001F\u007F-\u009F]/g;
42171
+ FOUNDER_POSTING_ID = /^fb_[A-Za-z0-9_-]{1,80}$/;
42091
42172
  CLAIM_RESOLUTION_REASONS = [
42092
42173
  "not-my-stack",
42093
42174
  "out-of-time",
@@ -13483,7 +13483,7 @@ function buildApprovalsNudge(awaitingApproval) {
13483
13483
  if (!Number.isInteger(awaitingApproval) || awaitingApproval <= 0) return null;
13484
13484
  const n = awaitingApproval;
13485
13485
  return ` \u26A0 ${n} claim${n === 1 ? "" : "s"} awaiting poster approval \u2014 terminalhire cannot check in the background until you enrol:
13486
- terminalhire claim --push --keep-updated (or check one now: terminalhire claim slice <id>)`;
13486
+ terminalhire claim --push --keep-updated (or check one now: terminalhire claim start <id>)`;
13487
13487
  }
13488
13488
  async function syncApprovedClaims({
13489
13489
  readAutoMarker: readAutoMarker2,
@@ -4485,7 +4485,10 @@ var init_boundary = __esm({
4485
4485
  },
4486
4486
  {
4487
4487
  code: "lockfile",
4488
- matches: (_p, _segments, base) => LOCKFILE_NAMES.has(base),
4488
+ // The `.lock` suffix is the server's rule since TERM-412; this copy missed it, so
4489
+ // the pre-flight passed `uv.lock` and bun's text `bun.lock` that the server refuses
4490
+ // (TERM-1235). The name set only covers lockfiles whose names do not end in `.lock`.
4491
+ matches: (_p, _segments, base) => LOCKFILE_NAMES.has(base) || base.endsWith(".lock"),
4489
4492
  why: "is a dependency lockfile. Nobody reads a lockfile diff, so a change to one is refused rather than shown"
4490
4493
  }
4491
4494
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "terminalhire",
3
- "version": "0.42.27",
3
+ "version": "0.42.28",
4
4
  "description": "Developer jobs and paid bounties, matched in the terminal — ambient matches in the Claude Code spinner. Claim a scoped bounty and get paid when the poster accepts it.",
5
5
  "repository": {
6
6
  "type": "git",