terminalhire 0.42.11 → 0.42.13

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.
@@ -0,0 +1,138 @@
1
+ // bin/index-revalidate.js
2
+ var PULSE_TIMEOUT_MS = 2e3;
3
+ var INDEX_TIMEOUT_WARM_MS = 5e3;
4
+ var INDEX_TIMEOUT_COLD_MS = 1e4;
5
+ function usableMember(member) {
6
+ return typeof member === "object" && member !== null && !Array.isArray(member) && Array.isArray(member.tags);
7
+ }
8
+ function usableIndex(index) {
9
+ if (!index || typeof index !== "object" || Array.isArray(index)) return false;
10
+ if (!Array.isArray(index.jobs) || !index.jobs.every(usableMember)) return false;
11
+ if (index.contribute !== void 0) {
12
+ if (!Array.isArray(index.contribute) || !index.contribute.every(usableMember)) return false;
13
+ }
14
+ return true;
15
+ }
16
+ function deadlineFetch(fetchImpl, url, { headers, timeoutMs }) {
17
+ const controller = new AbortController();
18
+ const timer = setTimeout(() => controller.abort(new Error("deadline exceeded")), timeoutMs);
19
+ timer.unref?.();
20
+ const promise = Promise.resolve(fetchImpl(url, { headers, signal: controller.signal })).finally(
21
+ () => clearTimeout(timer)
22
+ );
23
+ return { promise, abort: () => controller.abort(new Error("superseded")) };
24
+ }
25
+ function cacheFallbackNotice(source, cachedAt, now = Date.now()) {
26
+ if (source !== "cache-fallback") return null;
27
+ const base = "terminalhire: could not refresh the list \u2014 showing cached results";
28
+ if (typeof cachedAt !== "number" || !Number.isFinite(cachedAt)) return `${base}.`;
29
+ const minutes = Math.floor(Math.max(0, now - cachedAt) / 6e4);
30
+ if (minutes < 1) return `${base} from moments ago.`;
31
+ if (minutes < 60) return `${base} from ${minutes}m ago.`;
32
+ const hours = Math.floor(minutes / 60);
33
+ return `${base} from ${hours}h ago.`;
34
+ }
35
+ async function revalidateIndex({
36
+ apiUrl,
37
+ fetchImpl,
38
+ readEntry,
39
+ writeEntry,
40
+ pulseTimeoutMs = PULSE_TIMEOUT_MS,
41
+ indexTimeoutWarmMs = INDEX_TIMEOUT_WARM_MS,
42
+ indexTimeoutColdMs = INDEX_TIMEOUT_COLD_MS
43
+ }) {
44
+ let entry = {};
45
+ try {
46
+ entry = readEntry() ?? {};
47
+ } catch {
48
+ }
49
+ const cached = usableIndex(entry.index) ? entry.index : null;
50
+ const cachedAt = cached && typeof entry.ts === "number" ? entry.ts : null;
51
+ const indexTimeoutMs = cached ? indexTimeoutWarmMs : indexTimeoutColdMs;
52
+ const headers = { Accept: "application/json" };
53
+ if (cached && typeof entry.indexETag === "string" && entry.indexETag !== "") {
54
+ headers["If-None-Match"] = entry.indexETag;
55
+ }
56
+ const pulseCall = deadlineFetch(fetchImpl, `${apiUrl}/api/pulse`, {
57
+ headers: { Accept: "application/json" },
58
+ timeoutMs: pulseTimeoutMs
59
+ });
60
+ const speculative = deadlineFetch(fetchImpl, `${apiUrl}/api/index`, {
61
+ headers,
62
+ timeoutMs: indexTimeoutMs
63
+ });
64
+ const speculativeSettled = speculative.promise.then(
65
+ (res2) => ({ ok: true, res: res2 }),
66
+ (err) => ({ ok: false, err })
67
+ );
68
+ let latestPostedAt = null;
69
+ try {
70
+ const res2 = await pulseCall.promise;
71
+ if (res2.ok) {
72
+ const pulse = await res2.json();
73
+ latestPostedAt = pulse?.latestPostedAt ?? null;
74
+ }
75
+ } catch {
76
+ }
77
+ const bust = latestPostedAt !== null && entry.latestPostedAt !== latestPostedAt;
78
+ let outcome;
79
+ let reachedOrigin = false;
80
+ if (bust) {
81
+ speculative.abort();
82
+ const bustCall = deadlineFetch(fetchImpl, `${apiUrl}/api/index`, {
83
+ // `no-cache` forces revalidation against the origin; the validator rides along
84
+ // so a genuinely unchanged body still costs a 304 rather than a full download.
85
+ headers: { ...headers, "Cache-Control": "no-cache" },
86
+ timeoutMs: indexTimeoutMs
87
+ });
88
+ try {
89
+ outcome = { ok: true, res: await bustCall.promise };
90
+ reachedOrigin = true;
91
+ } catch (err) {
92
+ const spec = await speculativeSettled;
93
+ outcome = spec.ok ? spec : { ok: false, err };
94
+ }
95
+ } else {
96
+ outcome = await speculativeSettled;
97
+ }
98
+ const fellBack = (reason) => cached ? { index: cached, source: "cache-fallback", reason, cachedAt } : { index: null, source: "none", reason, cachedAt };
99
+ if (!outcome.ok) {
100
+ const err = outcome.err;
101
+ return fellBack(err instanceof Error ? err.message : String(err));
102
+ }
103
+ const res = outcome.res;
104
+ try {
105
+ if (res.status === 304 && cached) {
106
+ if (reachedOrigin && latestPostedAt && latestPostedAt !== entry.latestPostedAt) {
107
+ try {
108
+ writeEntry({ latestPostedAt });
109
+ } catch {
110
+ }
111
+ }
112
+ return { index: cached, source: "cache-304", reason: null, cachedAt };
113
+ }
114
+ if (!res.ok) {
115
+ return fellBack(`/api/index returned ${res.status}`);
116
+ }
117
+ const body = await res.json();
118
+ if (!usableIndex(body)) {
119
+ return fellBack("/api/index returned a body that will not render");
120
+ }
121
+ const patch = { index: body, indexETag: res.headers.get("etag") ?? "" };
122
+ if (reachedOrigin && latestPostedAt) patch.latestPostedAt = latestPostedAt;
123
+ try {
124
+ writeEntry(patch);
125
+ } catch {
126
+ }
127
+ return { index: body, source: "fresh", reason: null, cachedAt };
128
+ } catch (err) {
129
+ return fellBack(err instanceof Error ? err.message : String(err));
130
+ }
131
+ }
132
+ export {
133
+ INDEX_TIMEOUT_COLD_MS,
134
+ INDEX_TIMEOUT_WARM_MS,
135
+ PULSE_TIMEOUT_MS,
136
+ cacheFallbackNotice,
137
+ revalidateIndex
138
+ };
@@ -10550,52 +10550,6 @@ var init_state_dir = __esm({
10550
10550
  }
10551
10551
  });
10552
10552
 
10553
- // bin/cache-store.js
10554
- var cache_store_exports = {};
10555
- __export(cache_store_exports, {
10556
- readCacheEntry: () => readCacheEntry,
10557
- updateIndexCache: () => updateIndexCache,
10558
- writeIndexCache: () => writeIndexCache
10559
- });
10560
- import { readFileSync as readFileSync2, writeFileSync, renameSync } from "fs";
10561
- import { join as join2 } from "path";
10562
- import { homedir } from "os";
10563
- function readCacheEntry() {
10564
- try {
10565
- return JSON.parse(readFileSync2(INDEX_CACHE_FILE, "utf8"));
10566
- } catch {
10567
- return null;
10568
- }
10569
- }
10570
- function updateIndexCache(patch) {
10571
- ensureStateDir(TERMINALHIRE_DIR);
10572
- const existing = readCacheEntry() ?? {};
10573
- const entry = {
10574
- ...existing,
10575
- ...patch,
10576
- schemaVersion: SCHEMA_VERSION2,
10577
- ts: Date.now()
10578
- };
10579
- const tmp = `${INDEX_CACHE_FILE}.${process.pid}.${tmpCounter++}.tmp`;
10580
- writeFileSync(tmp, JSON.stringify(entry), "utf8");
10581
- renameSync(tmp, INDEX_CACHE_FILE);
10582
- return entry;
10583
- }
10584
- function writeIndexCache(index) {
10585
- return updateIndexCache({ index, indexETag: "" });
10586
- }
10587
- var TERMINALHIRE_DIR, INDEX_CACHE_FILE, SCHEMA_VERSION2, tmpCounter;
10588
- var init_cache_store = __esm({
10589
- "bin/cache-store.js"() {
10590
- "use strict";
10591
- init_state_dir();
10592
- TERMINALHIRE_DIR = process.env.TERMINALHIRE_DIR || join2(homedir(), ".terminalhire");
10593
- INDEX_CACHE_FILE = join2(TERMINALHIRE_DIR, "index-cache.json");
10594
- SCHEMA_VERSION2 = 1;
10595
- tmpCounter = 0;
10596
- }
10597
- });
10598
-
10599
10553
  // bin/founder-pin.js
10600
10554
  function isPinnedFounderBounty(j) {
10601
10555
  return j?.bounty?.bountySource === "founder" && j?.bounty?.claimable === true;
@@ -12021,8 +11975,168 @@ function formatCents(cents) {
12021
11975
  return cents % 100 === 0 ? `$${cents / 100}` : `$${(cents / 100).toFixed(2)}`;
12022
11976
  }
12023
11977
 
12024
- // bin/jpi-bounties.js
12025
- init_cache_store();
11978
+ // bin/cache-store.js
11979
+ init_state_dir();
11980
+ import { readFileSync as readFileSync2, writeFileSync, renameSync } from "fs";
11981
+ import { join as join2 } from "path";
11982
+ import { homedir } from "os";
11983
+ var TERMINALHIRE_DIR = process.env.TERMINALHIRE_DIR || join2(homedir(), ".terminalhire");
11984
+ var INDEX_CACHE_FILE = join2(TERMINALHIRE_DIR, "index-cache.json");
11985
+ var SCHEMA_VERSION2 = 1;
11986
+ var tmpCounter = 0;
11987
+ function readCacheEntry() {
11988
+ try {
11989
+ return JSON.parse(readFileSync2(INDEX_CACHE_FILE, "utf8"));
11990
+ } catch {
11991
+ return null;
11992
+ }
11993
+ }
11994
+ function updateIndexCache(patch) {
11995
+ ensureStateDir(TERMINALHIRE_DIR);
11996
+ const existing = readCacheEntry() ?? {};
11997
+ const entry = {
11998
+ ...existing,
11999
+ ...patch,
12000
+ schemaVersion: SCHEMA_VERSION2,
12001
+ ts: Date.now()
12002
+ };
12003
+ const tmp = `${INDEX_CACHE_FILE}.${process.pid}.${tmpCounter++}.tmp`;
12004
+ writeFileSync(tmp, JSON.stringify(entry), "utf8");
12005
+ renameSync(tmp, INDEX_CACHE_FILE);
12006
+ return entry;
12007
+ }
12008
+
12009
+ // bin/index-revalidate.js
12010
+ var PULSE_TIMEOUT_MS = 2e3;
12011
+ var INDEX_TIMEOUT_WARM_MS = 5e3;
12012
+ var INDEX_TIMEOUT_COLD_MS = 1e4;
12013
+ function usableMember(member) {
12014
+ return typeof member === "object" && member !== null && !Array.isArray(member) && Array.isArray(member.tags);
12015
+ }
12016
+ function usableIndex(index) {
12017
+ if (!index || typeof index !== "object" || Array.isArray(index)) return false;
12018
+ if (!Array.isArray(index.jobs) || !index.jobs.every(usableMember)) return false;
12019
+ if (index.contribute !== void 0) {
12020
+ if (!Array.isArray(index.contribute) || !index.contribute.every(usableMember)) return false;
12021
+ }
12022
+ return true;
12023
+ }
12024
+ function deadlineFetch(fetchImpl, url, { headers, timeoutMs }) {
12025
+ const controller = new AbortController();
12026
+ const timer = setTimeout(() => controller.abort(new Error("deadline exceeded")), timeoutMs);
12027
+ timer.unref?.();
12028
+ const promise = Promise.resolve(fetchImpl(url, { headers, signal: controller.signal })).finally(
12029
+ () => clearTimeout(timer)
12030
+ );
12031
+ return { promise, abort: () => controller.abort(new Error("superseded")) };
12032
+ }
12033
+ function cacheFallbackNotice(source, cachedAt, now = Date.now()) {
12034
+ if (source !== "cache-fallback") return null;
12035
+ const base = "terminalhire: could not refresh the list \u2014 showing cached results";
12036
+ if (typeof cachedAt !== "number" || !Number.isFinite(cachedAt)) return `${base}.`;
12037
+ const minutes = Math.floor(Math.max(0, now - cachedAt) / 6e4);
12038
+ if (minutes < 1) return `${base} from moments ago.`;
12039
+ if (minutes < 60) return `${base} from ${minutes}m ago.`;
12040
+ const hours = Math.floor(minutes / 60);
12041
+ return `${base} from ${hours}h ago.`;
12042
+ }
12043
+ async function revalidateIndex({
12044
+ apiUrl,
12045
+ fetchImpl,
12046
+ readEntry,
12047
+ writeEntry,
12048
+ pulseTimeoutMs = PULSE_TIMEOUT_MS,
12049
+ indexTimeoutWarmMs = INDEX_TIMEOUT_WARM_MS,
12050
+ indexTimeoutColdMs = INDEX_TIMEOUT_COLD_MS
12051
+ }) {
12052
+ let entry = {};
12053
+ try {
12054
+ entry = readEntry() ?? {};
12055
+ } catch {
12056
+ }
12057
+ const cached = usableIndex(entry.index) ? entry.index : null;
12058
+ const cachedAt = cached && typeof entry.ts === "number" ? entry.ts : null;
12059
+ const indexTimeoutMs = cached ? indexTimeoutWarmMs : indexTimeoutColdMs;
12060
+ const headers = { Accept: "application/json" };
12061
+ if (cached && typeof entry.indexETag === "string" && entry.indexETag !== "") {
12062
+ headers["If-None-Match"] = entry.indexETag;
12063
+ }
12064
+ const pulseCall = deadlineFetch(fetchImpl, `${apiUrl}/api/pulse`, {
12065
+ headers: { Accept: "application/json" },
12066
+ timeoutMs: pulseTimeoutMs
12067
+ });
12068
+ const speculative = deadlineFetch(fetchImpl, `${apiUrl}/api/index`, {
12069
+ headers,
12070
+ timeoutMs: indexTimeoutMs
12071
+ });
12072
+ const speculativeSettled = speculative.promise.then(
12073
+ (res2) => ({ ok: true, res: res2 }),
12074
+ (err) => ({ ok: false, err })
12075
+ );
12076
+ let latestPostedAt = null;
12077
+ try {
12078
+ const res2 = await pulseCall.promise;
12079
+ if (res2.ok) {
12080
+ const pulse = await res2.json();
12081
+ latestPostedAt = pulse?.latestPostedAt ?? null;
12082
+ }
12083
+ } catch {
12084
+ }
12085
+ const bust = latestPostedAt !== null && entry.latestPostedAt !== latestPostedAt;
12086
+ let outcome;
12087
+ let reachedOrigin = false;
12088
+ if (bust) {
12089
+ speculative.abort();
12090
+ const bustCall = deadlineFetch(fetchImpl, `${apiUrl}/api/index`, {
12091
+ // `no-cache` forces revalidation against the origin; the validator rides along
12092
+ // so a genuinely unchanged body still costs a 304 rather than a full download.
12093
+ headers: { ...headers, "Cache-Control": "no-cache" },
12094
+ timeoutMs: indexTimeoutMs
12095
+ });
12096
+ try {
12097
+ outcome = { ok: true, res: await bustCall.promise };
12098
+ reachedOrigin = true;
12099
+ } catch (err) {
12100
+ const spec = await speculativeSettled;
12101
+ outcome = spec.ok ? spec : { ok: false, err };
12102
+ }
12103
+ } else {
12104
+ outcome = await speculativeSettled;
12105
+ }
12106
+ const fellBack = (reason) => cached ? { index: cached, source: "cache-fallback", reason, cachedAt } : { index: null, source: "none", reason, cachedAt };
12107
+ if (!outcome.ok) {
12108
+ const err = outcome.err;
12109
+ return fellBack(err instanceof Error ? err.message : String(err));
12110
+ }
12111
+ const res = outcome.res;
12112
+ try {
12113
+ if (res.status === 304 && cached) {
12114
+ if (reachedOrigin && latestPostedAt && latestPostedAt !== entry.latestPostedAt) {
12115
+ try {
12116
+ writeEntry({ latestPostedAt });
12117
+ } catch {
12118
+ }
12119
+ }
12120
+ return { index: cached, source: "cache-304", reason: null, cachedAt };
12121
+ }
12122
+ if (!res.ok) {
12123
+ return fellBack(`/api/index returned ${res.status}`);
12124
+ }
12125
+ const body = await res.json();
12126
+ if (!usableIndex(body)) {
12127
+ return fellBack("/api/index returned a body that will not render");
12128
+ }
12129
+ const patch = { index: body, indexETag: res.headers.get("etag") ?? "" };
12130
+ if (reachedOrigin && latestPostedAt) patch.latestPostedAt = latestPostedAt;
12131
+ try {
12132
+ writeEntry(patch);
12133
+ } catch {
12134
+ }
12135
+ return { index: body, source: "fresh", reason: null, cachedAt };
12136
+ } catch (err) {
12137
+ return fellBack(err instanceof Error ? err.message : String(err));
12138
+ }
12139
+ }
12026
12140
 
12027
12141
  // bin/sanitize.js
12028
12142
  var WHITESPACE_CONTROLS = /[\t\n\v\f\r]+/g;
@@ -12178,14 +12292,20 @@ function readIndexCache() {
12178
12292
  return null;
12179
12293
  }
12180
12294
  }
12181
- async function fetchIndex() {
12182
- const cached = readIndexCache();
12183
- if (cached) return cached;
12184
- const res = await fetch(`${API_URL2}/api/index`, { signal: AbortSignal.timeout(1e4) });
12185
- if (!res.ok) throw new Error(`/api/index returned ${res.status}`);
12186
- const index = await res.json();
12187
- writeIndexCache(index);
12188
- return index;
12295
+ async function fetchIndex(quiet = false) {
12296
+ const { index, reason, source, cachedAt } = await revalidateIndex({
12297
+ apiUrl: API_URL2,
12298
+ fetchImpl: (url, init) => fetch(url, init),
12299
+ readEntry: readCacheEntry,
12300
+ writeEntry: updateIndexCache
12301
+ });
12302
+ if (!quiet) {
12303
+ const notice = cacheFallbackNotice(source, cachedAt);
12304
+ if (notice) process.stderr.write(`${notice}
12305
+ `);
12306
+ }
12307
+ if (index) return index;
12308
+ throw new Error(reason ?? "/api/index returned no usable index");
12189
12309
  }
12190
12310
  function prompt2(question) {
12191
12311
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
@@ -12326,7 +12446,7 @@ function classifyEmptyStatus({ preFilterCount, postFilterCount, priced }) {
12326
12446
  }
12327
12447
  async function getBounties({ quiet = false, offline = false, priced = PRICED_ONLY } = {}) {
12328
12448
  if (!quiet) console.log(`Fetching bounty index from ${API_URL2}/api/index...`);
12329
- const index = offline ? readIndexCache() : await fetchIndex();
12449
+ const index = offline ? readIndexCache() : await fetchIndex(quiet);
12330
12450
  if (offline && !index) return { status: "no-cache" };
12331
12451
  let bounties = (index.jobs ?? []).filter((j) => j.source === "bounty");
12332
12452
  if (PRICED_ONLY) bounties = bounties.filter((j) => j.bounty?.amountUSD != null);
@@ -12378,10 +12498,9 @@ async function run() {
12378
12498
  try {
12379
12499
  if (args.length === 0) {
12380
12500
  try {
12381
- const { readCacheEntry: readCacheEntry2 } = await Promise.resolve().then(() => (init_cache_store(), cache_store_exports));
12382
12501
  const { getSurfaceLeadOverride: getSurfaceLeadOverride2 } = await Promise.resolve().then(() => (init_config(), config_exports));
12383
12502
  const { founderRows: founderRows2, resolveSurfaceLead: resolveSurfaceLead2 } = await Promise.resolve().then(() => (init_founder_surface(), founder_surface_exports));
12384
- const surface = (readCacheEntry2() ?? {}).founderSurface;
12503
+ const surface = (readCacheEntry() ?? {}).founderSurface;
12385
12504
  if (resolveSurfaceLead2(getSurfaceLeadOverride2(), surface) === "founder") {
12386
12505
  const rows = founderRows2(surface, "bounties");
12387
12506
  console.log("\n\u26A1 Your TerminalHire postings\n");
@@ -12449,12 +12568,11 @@ async function run() {
12449
12568
  }
12450
12569
  try {
12451
12570
  const { acknowledgeFounderPaid: acknowledgeFounderPaid2 } = await Promise.resolve().then(() => (init_founder_paid_badge(), founder_paid_badge_exports));
12452
- const { readCacheEntry: readCacheEntry2 } = await Promise.resolve().then(() => (init_cache_store(), cache_store_exports));
12453
12571
  updateIndexCache({
12454
12572
  founderPaid: acknowledgeFounderPaid2({
12455
12573
  shown,
12456
12574
  open: bounties,
12457
- previous: (readCacheEntry2() ?? {}).founderPaid
12575
+ previous: (readCacheEntry() ?? {}).founderPaid
12458
12576
  })
12459
12577
  });
12460
12578
  } catch {