terminalhire 0.28.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.
@@ -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
  };
@@ -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");
@@ -9302,6 +9340,12 @@ ${list.length} ${active ? "active " : ""}claim${list.length === 1 ? "" : "s"}:
9302
9340
  console.log(` id: ${c.id}${pr}`);
9303
9341
  }
9304
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
+ }
9305
9349
  }
9306
9350
  async function cmdStatus(id) {
9307
9351
  const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
@@ -10118,7 +10162,13 @@ async function cmdPush({ keepUpdated = false } = {}) {
10118
10162
  pushToken = body?.pushToken || null;
10119
10163
  } catch {
10120
10164
  }
10121
- writeClaimPushMarker({ consentedAt, login, deleteToken });
10165
+ writeClaimPushMarker({
10166
+ consentedAt,
10167
+ login,
10168
+ deleteToken,
10169
+ lastPushedAt: consentedAt,
10170
+ lastSnapshotHash: computeSnapshotHash(pushed)
10171
+ });
10122
10172
  if (autoConsent && pushToken) {
10123
10173
  try {
10124
10174
  await writePushTokenEnc(pushToken);
@@ -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;
@@ -11708,6 +11749,12 @@ ${list.length} ${active ? "active " : ""}claim${list.length === 1 ? "" : "s"}:
11708
11749
  console.log(` id: ${c.id}${pr}`);
11709
11750
  }
11710
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
+ }
11711
11758
  }
11712
11759
  async function cmdStatus(id) {
11713
11760
  const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
@@ -12524,7 +12571,13 @@ async function cmdPush({ keepUpdated = false } = {}) {
12524
12571
  pushToken = body?.pushToken || null;
12525
12572
  } catch {
12526
12573
  }
12527
- writeClaimPushMarker({ consentedAt, login, deleteToken });
12574
+ writeClaimPushMarker({
12575
+ consentedAt,
12576
+ login,
12577
+ deleteToken,
12578
+ lastPushedAt: consentedAt,
12579
+ lastSnapshotHash: computeSnapshotHash(pushed)
12580
+ });
12528
12581
  if (autoConsent && pushToken) {
12529
12582
  try {
12530
12583
  await writePushTokenEnc(pushToken);
@@ -41191,6 +41244,9 @@ function buildContributeNudgeLine(contributeNudge) {
41191
41244
  function buildSessionStaleLine(sessionStale) {
41192
41245
  return sessionStale === true ? "\u26A0 terminalhire: linked session expired \u2014 run: terminalhire login" : null;
41193
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
+ }
41194
41250
  function buildSpinnerPool(topMatches, max = 6, opts = {}) {
41195
41251
  const {
41196
41252
  sessionTags,
@@ -41199,16 +41255,19 @@ function buildSpinnerPool(topMatches, max = 6, opts = {}) {
41199
41255
  incomingPending,
41200
41256
  sessionStale,
41201
41257
  contributeNudge,
41258
+ unpushedClaims,
41202
41259
  seenHistory
41203
41260
  } = opts;
41204
41261
  const staleLine = buildSessionStaleLine(sessionStale);
41205
41262
  const withStale = (pool2) => staleLine ? [staleLine, ...pool2] : pool2;
41206
41263
  const introLine = buildIncomingIntroLine(incomingPending);
41207
41264
  const contributeLine = buildContributeNudgeLine(contributeNudge);
41265
+ const unpushedLine = buildUnpushedClaimsLine(unpushedClaims);
41208
41266
  const ranked = filterFreshMatches(rankBySessionTags(topMatches, sessionTags), seenHistory);
41209
41267
  if (!Array.isArray(ranked) || ranked.length === 0) {
41210
41268
  if (introLine) return withStale([introLine]);
41211
41269
  if (contributeLine) return withStale([contributeLine]);
41270
+ if (unpushedLine) return withStale([unpushedLine]);
41212
41271
  const peerLine = buildPeerLine(topPeers);
41213
41272
  return withStale(peerLine ? [peerLine] : []);
41214
41273
  }
@@ -41218,6 +41277,7 @@ function buildSpinnerPool(topMatches, max = 6, opts = {}) {
41218
41277
  const pool = [...headers.slice(0, cap), ctaVerb()];
41219
41278
  if (introLine) pool.push(introLine);
41220
41279
  if (contributeLine) pool.push(contributeLine);
41280
+ if (unpushedLine) pool.push(unpushedLine);
41221
41281
  return withStale(pool);
41222
41282
  }
41223
41283
  var init_spinner_verbs = __esm({
@@ -41357,6 +41417,7 @@ function renderRefreshSurface(topMatches, sc, opts = {}) {
41357
41417
  incomingPending: opts.incomingPending,
41358
41418
  sessionStale: opts.sessionStale,
41359
41419
  contributeNudge: opts.contributeNudge,
41420
+ unpushedClaims: opts.unpushedClaims,
41360
41421
  seenHistory
41361
41422
  });
41362
41423
  if (verbs.length > 0) applySpinnerVerbs(verbs, sc.mode);
@@ -41396,6 +41457,7 @@ __export(spinner_exports, {
41396
41457
  buildSpinnerPool: () => buildSpinnerPool,
41397
41458
  buildTips: () => buildTips,
41398
41459
  buildTipsDetailed: () => buildTipsDetailed,
41460
+ buildUnpushedClaimsLine: () => buildUnpushedClaimsLine,
41399
41461
  clearSpinnerTips: () => clearSpinnerTips,
41400
41462
  clearSpinnerVerbs: () => clearSpinnerVerbs,
41401
41463
  ctaVerb: () => ctaVerb,
@@ -41420,6 +41482,7 @@ var init_spinner = __esm({
41420
41482
  init_spinner_verbs();
41421
41483
  init_spinner_verbs();
41422
41484
  init_spinner_verbs();
41485
+ init_spinner_verbs();
41423
41486
  init_spinner_select();
41424
41487
  init_spinner_select();
41425
41488
  init_spinner_select();
@@ -42582,6 +42645,12 @@ async function run22() {
42582
42645
  topMatches = filterFreshMatches2(topMatches, seenHistory);
42583
42646
  } catch {
42584
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
+ }
42585
42654
  const cacheEntry = {
42586
42655
  index,
42587
42656
  matchCount,
@@ -42590,6 +42659,7 @@ async function run22() {
42590
42659
  incomingPending,
42591
42660
  unreadChat,
42592
42661
  sessionStale,
42662
+ unpushedClaims,
42593
42663
  // In-process-only despite being persisted: the render pass receives it as
42594
42664
  // a function argument below. A future cache READER must not gate on this
42595
42665
  // field without re-checking isContributeEnabled/isContributePrompted at
@@ -42628,6 +42698,7 @@ async function run22() {
42628
42698
  incomingPending,
42629
42699
  sessionStale,
42630
42700
  contributeNudge,
42701
+ unpushedClaims,
42631
42702
  baseUrl: API_URL8,
42632
42703
  seenHistory,
42633
42704
  widen,
@@ -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 readFileSync12,
9430
- writeFileSync as writeFileSync10,
9431
- mkdirSync as mkdirSync10,
9432
- existsSync as existsSync7,
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 join13 } from "path";
9436
- import { homedir as homedir10 } from "os";
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
- mkdirSync10(TERMINALHIRE_DIR6, { recursive: true });
9448
- if (existsSync7(KEY_FILE2)) {
9449
- return Buffer.from(readFileSync12(KEY_FILE2, "utf8").trim(), "hex");
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
- writeFileSync10(KEY_FILE2, key.toString("hex"), { mode: 384, encoding: "utf8" });
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 = join13(homedir10(), ".terminalhire");
9476
- TOKEN_FILE = join13(TERMINALHIRE_DIR6, "github-token.enc");
9477
- KEY_FILE2 = join13(TERMINALHIRE_DIR6, "key");
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 readFileSync13, writeFileSync as writeFileSync11, mkdirSync as mkdirSync11, renameSync as renameSync6, existsSync as existsSync8 } from "fs";
9498
- import { join as join14 } from "path";
9499
- import { homedir as homedir11 } from "os";
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 (!existsSync8(CLAIMS_FILE)) return [];
9520
- const data = JSON.parse(readFileSync13(CLAIMS_FILE, "utf8"));
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
- mkdirSync11(TERMINALHIRE_DIR7, { recursive: true });
9407
+ mkdirSync10(TERMINALHIRE_DIR7, { recursive: true });
9529
9408
  const tmp = `${CLAIMS_FILE}.tmp`;
9530
9409
  const payload = { claims };
9531
- writeFileSync11(tmp, JSON.stringify(payload, null, 2), "utf8");
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 || join14(homedir11(), ".terminalhire");
9593
- CLAIMS_FILE = join14(TERMINALHIRE_DIR7, "claims.json");
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 readFileSync14, writeFileSync as writeFileSync12, mkdirSync as mkdirSync12, existsSync as existsSync9, rmSync as rmSync3 } from "fs";
9626
- import { join as join15 } from "path";
9627
- import { homedir as homedir12 } from "os";
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
- mkdirSync12(TERMINALHIRE_DIR8, { recursive: true });
9511
+ mkdirSync11(TERMINALHIRE_DIR8, { recursive: true });
9630
9512
  const key = await loadKey2();
9631
9513
  const blob = encrypt2(rawToken, key);
9632
- writeFileSync12(CLAIM_PUSH_TOKEN_FILE, JSON.stringify(blob, null, 2), { encoding: "utf8" });
9514
+ writeFileSync11(CLAIM_PUSH_TOKEN_FILE, JSON.stringify(blob, null, 2), { encoding: "utf8" });
9633
9515
  }
9634
9516
  async function readPushTokenEnc() {
9635
- if (!existsSync9(CLAIM_PUSH_TOKEN_FILE)) return void 0;
9517
+ if (!existsSync8(CLAIM_PUSH_TOKEN_FILE)) return void 0;
9636
9518
  try {
9637
9519
  const key = await loadKey2();
9638
- const blob = JSON.parse(readFileSync14(CLAIM_PUSH_TOKEN_FILE, "utf8"));
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 existsSync9(CLAIM_PUSH_AUTO_MARKER) ? JSON.parse(readFileSync14(CLAIM_PUSH_AUTO_MARKER, "utf8")) : null;
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
- mkdirSync12(TERMINALHIRE_DIR8, { recursive: true });
9659
- writeFileSync12(CLAIM_PUSH_AUTO_MARKER, JSON.stringify(marker, null, 2) + "\n", "utf8");
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 (!existsSync9(CLAIM_PUSH_AUTO_MARKER) || !existsSync9(CLAIM_PUSH_TOKEN_FILE)) return;
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 || join15(homedir12(), ".terminalhire");
9738
- CLAIM_PUSH_AUTO_MARKER = join15(TERMINALHIRE_DIR8, "claim-push-auto.json");
9739
- CLAIM_PUSH_TOKEN_FILE = join15(TERMINALHIRE_DIR8, "claim-push-token.enc");
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,
@@ -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
 
@@ -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.28.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",