terminalhire 0.41.2 → 0.41.3

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.
@@ -167,7 +167,7 @@ function nowISO() {
167
167
  return (/* @__PURE__ */ new Date()).toISOString();
168
168
  }
169
169
  function defangText(s) {
170
- return typeof s === "string" ? s.replace(CONTROL_CHARS, "") : s;
170
+ return typeof s === "string" ? s.replace(WHITESPACE_CONTROLS, " ").replace(CONTROL_CHARS, "") : s;
171
171
  }
172
172
  function finiteAmount(a) {
173
173
  if (typeof a === "number") return Number.isFinite(a) ? a : null;
@@ -305,7 +305,7 @@ function acceptedPRRate(claims = readClaims()) {
305
305
  const merged = claims.filter((c) => c.state === "merged").length;
306
306
  return { merged, total, rate: total === 0 ? 0 : merged / total };
307
307
  }
308
- var TERMINALHIRE_DIR3, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, CONTROL_CHARS;
308
+ var TERMINALHIRE_DIR3, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, WHITESPACE_CONTROLS, CONTROL_CHARS;
309
309
  var init_claims = __esm({
310
310
  "src/claims.ts"() {
311
311
  "use strict";
@@ -361,6 +361,7 @@ var init_claims = __esm({
361
361
  ]),
362
362
  submitted: /* @__PURE__ */ new Set(["claimed", "working", "in-review", "ready"])
363
363
  };
364
+ WHITESPACE_CONTROLS = /[\t\n\v\f\r]+/g;
364
365
  CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
365
366
  }
366
367
  });
@@ -11339,7 +11339,7 @@ function nowISO2() {
11339
11339
  return (/* @__PURE__ */ new Date()).toISOString();
11340
11340
  }
11341
11341
  function defangText(s) {
11342
- return typeof s === "string" ? s.replace(CONTROL_CHARS2, "") : s;
11342
+ return typeof s === "string" ? s.replace(WHITESPACE_CONTROLS2, " ").replace(CONTROL_CHARS2, "") : s;
11343
11343
  }
11344
11344
  function finiteAmount(a) {
11345
11345
  if (typeof a === "number") return Number.isFinite(a) ? a : null;
@@ -11477,7 +11477,7 @@ function acceptedPRRate(claims = readClaims()) {
11477
11477
  const merged = claims.filter((c) => c.state === "merged").length;
11478
11478
  return { merged, total, rate: total === 0 ? 0 : merged / total };
11479
11479
  }
11480
- var TERMINALHIRE_DIR5, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, CONTROL_CHARS2;
11480
+ var TERMINALHIRE_DIR5, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, WHITESPACE_CONTROLS2, CONTROL_CHARS2;
11481
11481
  var init_claims = __esm({
11482
11482
  "src/claims.ts"() {
11483
11483
  "use strict";
@@ -11533,6 +11533,7 @@ var init_claims = __esm({
11533
11533
  ]),
11534
11534
  submitted: /* @__PURE__ */ new Set(["claimed", "working", "in-review", "ready"])
11535
11535
  };
11536
+ WHITESPACE_CONTROLS2 = /[\t\n\v\f\r]+/g;
11536
11537
  CONTROL_CHARS2 = /[\x00-\x1f\x7f-\x9f]/g;
11537
11538
  }
11538
11539
  });
@@ -11755,10 +11756,17 @@ import { homedir as homedir7 } from "os";
11755
11756
  import { createInterface } from "readline";
11756
11757
 
11757
11758
  // bin/sanitize.js
11759
+ var WHITESPACE_CONTROLS = /[\t\n\v\f\r]+/g;
11758
11760
  var CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
11759
11761
  function sanitizeText(s) {
11760
11762
  if (s == null) return "";
11761
- return String(s).replace(CONTROL_CHARS, "");
11763
+ return String(s).replace(WHITESPACE_CONTROLS, " ").replace(CONTROL_CHARS, "");
11764
+ }
11765
+ function formatUsd(a) {
11766
+ if (typeof a === "number") return Number.isFinite(a) ? "$" + a.toLocaleString() : "$\u2014";
11767
+ if (typeof a !== "string" || a.trim() === "") return "$\u2014";
11768
+ const n = Number(a);
11769
+ return Number.isFinite(n) ? "$" + n.toLocaleString() : "$\u2014";
11762
11770
  }
11763
11771
  function safeHttpUrl(url) {
11764
11772
  if (url == null) return null;
@@ -11831,7 +11839,7 @@ function prompt(question) {
11831
11839
  });
11832
11840
  }
11833
11841
  function formatAmount(b) {
11834
- return b.amountUSD != null ? "$" + b.amountUSD.toLocaleString() : "$\u2014";
11842
+ return formatUsd(b.amountUSD);
11835
11843
  }
11836
11844
  var EFFORT_LABEL = {
11837
11845
  small: "small (~\xBD day)",
@@ -11980,7 +11988,9 @@ async function run() {
11980
11988
  const rows = founderRows2(surface, "bounties");
11981
11989
  console.log("\n\u26A1 Your TerminalHire postings\n");
11982
11990
  if (!rows.length) {
11983
- console.log(" No current posting data. Run `terminalhire refresh`, or lead with work:");
11991
+ console.log(
11992
+ " No current posting data. Run `terminalhire refresh`, or lead with work:"
11993
+ );
11984
11994
  console.log(" terminalhire config set lead dev\n");
11985
11995
  } else {
11986
11996
  rows.forEach((row, index) => {
@@ -11989,7 +11999,9 @@ async function run() {
11989
11999
  );
11990
12000
  });
11991
12001
  console.log("\n Review and act: https://terminalhire.com/dashboard?tab=postings");
11992
- console.log(" Lead with developer bounties instead: terminalhire config set lead dev\n");
12002
+ console.log(
12003
+ " Lead with developer bounties instead: terminalhire config set lead dev\n"
12004
+ );
11993
12005
  }
11994
12006
  return;
11995
12007
  }
@@ -10639,7 +10639,7 @@ function nowISO() {
10639
10639
  return (/* @__PURE__ */ new Date()).toISOString();
10640
10640
  }
10641
10641
  function defangText(s) {
10642
- return typeof s === "string" ? s.replace(CONTROL_CHARS2, "") : s;
10642
+ return typeof s === "string" ? s.replace(WHITESPACE_CONTROLS2, " ").replace(CONTROL_CHARS2, "") : s;
10643
10643
  }
10644
10644
  function finiteAmount(a) {
10645
10645
  if (typeof a === "number") return Number.isFinite(a) ? a : null;
@@ -10777,7 +10777,7 @@ function acceptedPRRate(claims = readClaims()) {
10777
10777
  const merged = claims.filter((c) => c.state === "merged").length;
10778
10778
  return { merged, total, rate: total === 0 ? 0 : merged / total };
10779
10779
  }
10780
- var TERMINALHIRE_DIR2, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, CONTROL_CHARS2;
10780
+ var TERMINALHIRE_DIR2, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, WHITESPACE_CONTROLS2, CONTROL_CHARS2;
10781
10781
  var init_claims = __esm({
10782
10782
  "src/claims.ts"() {
10783
10783
  "use strict";
@@ -10833,6 +10833,7 @@ var init_claims = __esm({
10833
10833
  ]),
10834
10834
  submitted: /* @__PURE__ */ new Set(["claimed", "working", "in-review", "ready"])
10835
10835
  };
10836
+ WHITESPACE_CONTROLS2 = /[\t\n\v\f\r]+/g;
10836
10837
  CONTROL_CHARS2 = /[\x00-\x1f\x7f-\x9f]/g;
10837
10838
  }
10838
10839
  });
@@ -26074,10 +26075,17 @@ function openInBrowser(url) {
26074
26075
  }
26075
26076
 
26076
26077
  // bin/sanitize.js
26078
+ var WHITESPACE_CONTROLS = /[\t\n\v\f\r]+/g;
26077
26079
  var CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
26078
26080
  function sanitizeText(s) {
26079
26081
  if (s == null) return "";
26080
- return String(s).replace(CONTROL_CHARS, "");
26082
+ return String(s).replace(WHITESPACE_CONTROLS, " ").replace(CONTROL_CHARS, "");
26083
+ }
26084
+ function formatUsd(a) {
26085
+ if (typeof a === "number") return Number.isFinite(a) ? "$" + a.toLocaleString() : "$\u2014";
26086
+ if (typeof a !== "string" || a.trim() === "") return "$\u2014";
26087
+ const n = Number(a);
26088
+ return Number.isFinite(n) ? "$" + n.toLocaleString() : "$\u2014";
26081
26089
  }
26082
26090
 
26083
26091
  // src/policy-acks.ts
@@ -27003,10 +27011,7 @@ async function pollPR(prUrl) {
27003
27011
  }
27004
27012
  }
27005
27013
  function fmtAmount(a) {
27006
- if (a == null) return "$\u2014";
27007
- if (typeof a === "string" && a.trim() === "") return "$\u2014";
27008
- const n = typeof a === "number" ? a : Number(a);
27009
- return Number.isFinite(n) ? "$" + n.toLocaleString() : "$\u2014";
27014
+ return formatUsd(a);
27010
27015
  }
27011
27016
  function fmtClaimAmount(c) {
27012
27017
  return c.kind === "contribution" ? "contribution" : fmtAmount(c.amountUSD);
@@ -27147,8 +27152,15 @@ async function resolveBounty(arg) {
27147
27152
  return {
27148
27153
  bountyId,
27149
27154
  indexNativeId,
27150
- title,
27151
- repoFullName,
27155
+ // DEFANGED HERE, at the one place every tier of this resolver converges.
27156
+ // `claims.ts` cleans what comes OUT of the local store, which covers every later
27157
+ // read — but this object is printed (the preview card, the record card) and
27158
+ // handed to `recordClaim` BEFORE any store exists for it, so on a claim's first
27159
+ // use the store boundary has not run yet. `repoFullName` also goes on to be
27160
+ // interpolated into GitHub API URLs from here. Found by a cross-model review of
27161
+ // TERM-380; the store fix alone left this path open.
27162
+ title: sanitizeText(title),
27163
+ repoFullName: repoFullName == null ? repoFullName : sanitizeText(repoFullName),
27152
27164
  issueUrl,
27153
27165
  amountUSD,
27154
27166
  source,
@@ -28388,7 +28400,7 @@ async function cmdStart(id, flags = {}) {
28388
28400
  console.error(`terminalhire claim: no claim with id '${id}'.`);
28389
28401
  process.exit(1);
28390
28402
  }
28391
- if (claim.approval?.state === "pending" && !approvalsChecked) {
28403
+ if (claim.approval?.state === "pending") {
28392
28404
  ({ approvalsChecked, approvalsUnavailable } = await syncFounderApprovals(claims, [claim]));
28393
28405
  claim = claims.findClaim(id);
28394
28406
  }
@@ -3618,7 +3618,7 @@ function nowISO2() {
3618
3618
  return (/* @__PURE__ */ new Date()).toISOString();
3619
3619
  }
3620
3620
  function defangText(s) {
3621
- return typeof s === "string" ? s.replace(CONTROL_CHARS2, "") : s;
3621
+ return typeof s === "string" ? s.replace(WHITESPACE_CONTROLS2, " ").replace(CONTROL_CHARS2, "") : s;
3622
3622
  }
3623
3623
  function finiteAmount(a) {
3624
3624
  if (typeof a === "number") return Number.isFinite(a) ? a : null;
@@ -3756,7 +3756,7 @@ function acceptedPRRate(claims = readClaims()) {
3756
3756
  const merged = claims.filter((c) => c.state === "merged").length;
3757
3757
  return { merged, total, rate: total === 0 ? 0 : merged / total };
3758
3758
  }
3759
- var TERMINALHIRE_DIR7, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, CONTROL_CHARS2;
3759
+ var TERMINALHIRE_DIR7, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, WHITESPACE_CONTROLS2, CONTROL_CHARS2;
3760
3760
  var init_claims = __esm({
3761
3761
  "src/claims.ts"() {
3762
3762
  "use strict";
@@ -3812,6 +3812,7 @@ var init_claims = __esm({
3812
3812
  ]),
3813
3813
  submitted: /* @__PURE__ */ new Set(["claimed", "working", "in-review", "ready"])
3814
3814
  };
3815
+ WHITESPACE_CONTROLS2 = /[\t\n\v\f\r]+/g;
3815
3816
  CONTROL_CHARS2 = /[\x00-\x1f\x7f-\x9f]/g;
3816
3817
  }
3817
3818
  });
@@ -3896,10 +3897,11 @@ function isContributeEnabled() {
3896
3897
  init_state_dir();
3897
3898
 
3898
3899
  // bin/sanitize.js
3900
+ var WHITESPACE_CONTROLS = /[\t\n\v\f\r]+/g;
3899
3901
  var CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
3900
3902
  function sanitizeText(s) {
3901
3903
  if (s == null) return "";
3902
- return String(s).replace(CONTROL_CHARS, "");
3904
+ return String(s).replace(WHITESPACE_CONTROLS, " ").replace(CONTROL_CHARS, "");
3903
3905
  }
3904
3906
  function safeHttpUrl(url) {
3905
3907
  if (url == null) return null;
@@ -11053,10 +11053,11 @@ function excludeOwnCard(results, ownLogin) {
11053
11053
  }
11054
11054
 
11055
11055
  // bin/sanitize.js
11056
+ var WHITESPACE_CONTROLS = /[\t\n\v\f\r]+/g;
11056
11057
  var CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
11057
11058
  function sanitizeText(s) {
11058
11059
  if (s == null) return "";
11059
- return String(s).replace(CONTROL_CHARS, "");
11060
+ return String(s).replace(WHITESPACE_CONTROLS, " ").replace(CONTROL_CHARS, "");
11060
11061
  }
11061
11062
  function safeHttpUrl(url) {
11062
11063
  if (url == null) return null;
@@ -12831,7 +12831,13 @@ var init_cache_store = __esm({
12831
12831
  // bin/sanitize.js
12832
12832
  function sanitizeText(s) {
12833
12833
  if (s == null) return "";
12834
- return String(s).replace(CONTROL_CHARS, "");
12834
+ return String(s).replace(WHITESPACE_CONTROLS, " ").replace(CONTROL_CHARS, "");
12835
+ }
12836
+ function formatUsd(a) {
12837
+ if (typeof a === "number") return Number.isFinite(a) ? "$" + a.toLocaleString() : "$\u2014";
12838
+ if (typeof a !== "string" || a.trim() === "") return "$\u2014";
12839
+ const n = Number(a);
12840
+ return Number.isFinite(n) ? "$" + n.toLocaleString() : "$\u2014";
12835
12841
  }
12836
12842
  function safeHttpUrl(url) {
12837
12843
  if (url == null) return null;
@@ -12857,10 +12863,11 @@ function linkTitle(title, url) {
12857
12863
  }
12858
12864
  return href ? `${safeTitle} (${href})` : safeTitle;
12859
12865
  }
12860
- var CONTROL_CHARS;
12866
+ var WHITESPACE_CONTROLS, CONTROL_CHARS;
12861
12867
  var init_sanitize = __esm({
12862
12868
  "bin/sanitize.js"() {
12863
12869
  "use strict";
12870
+ WHITESPACE_CONTROLS = /[\t\n\v\f\r]+/g;
12864
12871
  CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
12865
12872
  }
12866
12873
  });
@@ -13578,7 +13585,7 @@ function buildTipsDetailed(topMatches, baseUrl, max = 8, opts = {}) {
13578
13585
  const token = jobShortToken(String(m.id));
13579
13586
  const url = `${base}/j/${token}`;
13580
13587
  if (source === "bounty") {
13581
- const money = m.amountUSD != null ? `$${Number(m.amountUSD).toLocaleString()}` : "$\u2014";
13588
+ const money = formatUsd(m.amountUSD);
13582
13589
  const repo = m.repo || companyRaw;
13583
13590
  out.push(`\u{1F48E} ${money} \xB7 ${title} \xB7 ${repo} \xB7 ${pct2}% \u2014 ${url}`);
13584
13591
  } else if (source === "contribute") {
@@ -13648,6 +13655,7 @@ var init_spinner_render = __esm({
13648
13655
  init_spinner_verbs();
13649
13656
  init_spinner_seen();
13650
13657
  init_spinner_io();
13658
+ init_sanitize();
13651
13659
  init_src();
13652
13660
  }
13653
13661
  });
@@ -15182,7 +15190,7 @@ function nowISO2() {
15182
15190
  return (/* @__PURE__ */ new Date()).toISOString();
15183
15191
  }
15184
15192
  function defangText(s) {
15185
- return typeof s === "string" ? s.replace(CONTROL_CHARS2, "") : s;
15193
+ return typeof s === "string" ? s.replace(WHITESPACE_CONTROLS2, " ").replace(CONTROL_CHARS2, "") : s;
15186
15194
  }
15187
15195
  function finiteAmount(a) {
15188
15196
  if (typeof a === "number") return Number.isFinite(a) ? a : null;
@@ -15320,7 +15328,7 @@ function acceptedPRRate(claims = readClaims()) {
15320
15328
  const merged = claims.filter((c) => c.state === "merged").length;
15321
15329
  return { merged, total, rate: total === 0 ? 0 : merged / total };
15322
15330
  }
15323
- var TERMINALHIRE_DIR11, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS2, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, CONTROL_CHARS2;
15331
+ var TERMINALHIRE_DIR11, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS2, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, WHITESPACE_CONTROLS2, CONTROL_CHARS2;
15324
15332
  var init_claims = __esm({
15325
15333
  "src/claims.ts"() {
15326
15334
  "use strict";
@@ -15376,6 +15384,7 @@ var init_claims = __esm({
15376
15384
  ]),
15377
15385
  submitted: /* @__PURE__ */ new Set(["claimed", "working", "in-review", "ready"])
15378
15386
  };
15387
+ WHITESPACE_CONTROLS2 = /[\t\n\v\f\r]+/g;
15379
15388
  CONTROL_CHARS2 = /[\x00-\x1f\x7f-\x9f]/g;
15380
15389
  }
15381
15390
  });
@@ -15465,7 +15474,7 @@ function prompt3(question) {
15465
15474
  });
15466
15475
  }
15467
15476
  function formatAmount(b) {
15468
- return b.amountUSD != null ? "$" + b.amountUSD.toLocaleString() : "$\u2014";
15477
+ return formatUsd(b.amountUSD);
15469
15478
  }
15470
15479
  function printBounty(i, job, score, reason, matchedTags, claimedIds = /* @__PURE__ */ new Set(), continuityNote = null) {
15471
15480
  const b = job.bounty ?? {};
@@ -15609,7 +15618,9 @@ async function run5() {
15609
15618
  const rows = founderRows2(surface, "bounties");
15610
15619
  console.log("\n\u26A1 Your TerminalHire postings\n");
15611
15620
  if (!rows.length) {
15612
- console.log(" No current posting data. Run `terminalhire refresh`, or lead with work:");
15621
+ console.log(
15622
+ " No current posting data. Run `terminalhire refresh`, or lead with work:"
15623
+ );
15613
15624
  console.log(" terminalhire config set lead dev\n");
15614
15625
  } else {
15615
15626
  rows.forEach((row, index) => {
@@ -15618,7 +15629,9 @@ async function run5() {
15618
15629
  );
15619
15630
  });
15620
15631
  console.log("\n Review and act: https://terminalhire.com/dashboard?tab=postings");
15621
- console.log(" Lead with developer bounties instead: terminalhire config set lead dev\n");
15632
+ console.log(
15633
+ " Lead with developer bounties instead: terminalhire config set lead dev\n"
15634
+ );
15622
15635
  }
15623
15636
  return;
15624
15637
  }
@@ -31602,10 +31615,7 @@ async function pollPR(prUrl) {
31602
31615
  }
31603
31616
  }
31604
31617
  function fmtAmount(a) {
31605
- if (a == null) return "$\u2014";
31606
- if (typeof a === "string" && a.trim() === "") return "$\u2014";
31607
- const n = typeof a === "number" ? a : Number(a);
31608
- return Number.isFinite(n) ? "$" + n.toLocaleString() : "$\u2014";
31618
+ return formatUsd(a);
31609
31619
  }
31610
31620
  function fmtClaimAmount(c) {
31611
31621
  return c.kind === "contribution" ? "contribution" : fmtAmount(c.amountUSD);
@@ -31744,8 +31754,15 @@ async function resolveBounty(arg) {
31744
31754
  return {
31745
31755
  bountyId,
31746
31756
  indexNativeId,
31747
- title,
31748
- repoFullName,
31757
+ // DEFANGED HERE, at the one place every tier of this resolver converges.
31758
+ // `claims.ts` cleans what comes OUT of the local store, which covers every later
31759
+ // read — but this object is printed (the preview card, the record card) and
31760
+ // handed to `recordClaim` BEFORE any store exists for it, so on a claim's first
31761
+ // use the store boundary has not run yet. `repoFullName` also goes on to be
31762
+ // interpolated into GitHub API URLs from here. Found by a cross-model review of
31763
+ // TERM-380; the store fix alone left this path open.
31764
+ title: sanitizeText(title),
31765
+ repoFullName: repoFullName == null ? repoFullName : sanitizeText(repoFullName),
31749
31766
  issueUrl,
31750
31767
  amountUSD,
31751
31768
  source,
@@ -32966,7 +32983,7 @@ async function cmdStart(id, flags = {}) {
32966
32983
  console.error(`terminalhire claim: no claim with id '${id}'.`);
32967
32984
  process.exit(1);
32968
32985
  }
32969
- if (claim.approval?.state === "pending" && !approvalsChecked) {
32986
+ if (claim.approval?.state === "pending") {
32970
32987
  ({ approvalsChecked, approvalsUnavailable } = await syncFounderApprovals(claims, [claim]));
32971
32988
  claim = claims.findClaim(id);
32972
32989
  }
@@ -39766,7 +39783,12 @@ function substrMatch(query, candidate) {
39766
39783
  if (query === "") return true;
39767
39784
  return candidate.toLowerCase().includes(query.toLowerCase());
39768
39785
  }
39769
- function runHubTui({ input = process.stdin, output = process.stdout, signals, deps = {} } = {}) {
39786
+ function runHubTui({
39787
+ input = process.stdin,
39788
+ output = process.stdout,
39789
+ signals,
39790
+ deps = {}
39791
+ } = {}) {
39770
39792
  const {
39771
39793
  listClaims: _listClaims = listClaims,
39772
39794
  acceptedPRRate: _acceptedPRRate = acceptedPRRate,
@@ -39929,20 +39951,50 @@ function runHubTui({ input = process.stdin, output = process.stdout, signals, de
39929
39951
  acked = false;
39930
39952
  }
39931
39953
  if (!acked) {
39932
- inboxState = { loaded: true, loading: false, status: "disclosure", items: null, message: null };
39954
+ inboxState = {
39955
+ loaded: true,
39956
+ loading: false,
39957
+ status: "disclosure",
39958
+ items: null,
39959
+ message: null
39960
+ };
39933
39961
  return;
39934
39962
  }
39935
39963
  inboxState = { ...inboxState, loading: true };
39936
39964
  _buildInboxItems().then((built) => {
39937
39965
  if (built.status === "ok") {
39938
- inboxState = { loaded: true, loading: false, status: "ok", items: built.items, message: null };
39966
+ inboxState = {
39967
+ loaded: true,
39968
+ loading: false,
39969
+ status: "ok",
39970
+ items: built.items,
39971
+ message: null
39972
+ };
39939
39973
  } else if (built.status === "error") {
39940
- inboxState = { loaded: true, loading: false, status: "error", items: null, message: built.message || "could not load inbox" };
39974
+ inboxState = {
39975
+ loaded: true,
39976
+ loading: false,
39977
+ status: "error",
39978
+ items: null,
39979
+ message: built.message || "could not load inbox"
39980
+ };
39941
39981
  } else {
39942
- inboxState = { loaded: true, loading: false, status: built.status, items: null, message: null };
39982
+ inboxState = {
39983
+ loaded: true,
39984
+ loading: false,
39985
+ status: built.status,
39986
+ items: null,
39987
+ message: null
39988
+ };
39943
39989
  }
39944
39990
  }).catch((e) => {
39945
- inboxState = { loaded: true, loading: false, status: "error", items: null, message: errMsg(e) };
39991
+ inboxState = {
39992
+ loaded: true,
39993
+ loading: false,
39994
+ status: "error",
39995
+ items: null,
39996
+ message: errMsg(e)
39997
+ };
39946
39998
  }).finally(() => repaint());
39947
39999
  return;
39948
40000
  }
@@ -39976,7 +40028,9 @@ function runHubTui({ input = process.stdin, output = process.stdout, signals, de
39976
40028
  function inboxRows(st) {
39977
40029
  switch (st.status) {
39978
40030
  case "disclosure":
39979
- return ["Messaging disclosure not yet acknowledged \u2014 run `terminalhire inbox` once to review it and enable messages."];
40031
+ return [
40032
+ "Messaging disclosure not yet acknowledged \u2014 run `terminalhire inbox` once to review it and enable messages."
40033
+ ];
39980
40034
  case "not-linked":
39981
40035
  return ["Not linked \u2014 run `terminalhire link` to connect your web session."];
39982
40036
  case "expired":
@@ -40019,11 +40073,13 @@ function runHubTui({ input = process.stdin, output = process.stdout, signals, de
40019
40073
  case "empty":
40020
40074
  return ["No bounties available right now. Check back through the day."];
40021
40075
  case "demoted":
40022
- return ["Paid bounties paused \u2014 run `bounties --priced` to view them. Try Contribute or Jobs."];
40076
+ return [
40077
+ "Paid bounties paused \u2014 run `bounties --priced` to view them. Try Contribute or Jobs."
40078
+ ];
40023
40079
  case "ok":
40024
40080
  return result.bounties.map((job) => {
40025
40081
  const b = job.bounty || {};
40026
- const amt = b.amountUSD != null ? "$" + b.amountUSD.toLocaleString() : "$\u2014";
40082
+ const amt = formatUsd(b.amountUSD);
40027
40083
  const repo = sanitizeLine(b.repoFullName || job.company);
40028
40084
  const prs = b.competingOpenPRs;
40029
40085
  const contend = prs != null && prs > 0 ? ` \xB7 \u26A0 ${prs} in flight` : "";
@@ -40078,7 +40134,9 @@ function runHubTui({ input = process.stdin, output = process.stdout, signals, de
40078
40134
  if (it.note) rows.push(` note: ${sanitizeLine(it.note)}`);
40079
40135
  if (it.contact) rows.push(` contact: ${sanitizeLine(it.contact)}`);
40080
40136
  else if (it.role === "incoming" && it.status === "pending") {
40081
- rows.push(` \u2192 accept: terminalhire intro --accept @${sanitizeLine(it.counterpartyLogin)}`);
40137
+ rows.push(
40138
+ ` \u2192 accept: terminalhire intro --accept @${sanitizeLine(it.counterpartyLogin)}`
40139
+ );
40082
40140
  }
40083
40141
  }
40084
40142
  return rows;
@@ -40165,7 +40223,8 @@ function runHubTui({ input = process.stdin, output = process.stdout, signals, de
40165
40223
  if (name === "Claims") {
40166
40224
  if (!claimsState.loaded) return ["Loading\u2026"];
40167
40225
  if (claimsState.error) return ["Could not read claims \u2014 " + claimsState.error];
40168
- if (!claimsState.rows.length) return ["No claims yet \u2014 run `terminalhire claim <url>` to start one."];
40226
+ if (!claimsState.rows.length)
40227
+ return ["No claims yet \u2014 run `terminalhire claim <url>` to start one."];
40169
40228
  return claimsState.rows.map(formatClaimRow);
40170
40229
  }
40171
40230
  if (name === "Profile") {
@@ -40195,8 +40254,10 @@ function runHubTui({ input = process.stdin, output = process.stdout, signals, de
40195
40254
  const st = listState[name];
40196
40255
  if (st.loaded && !st.error && st.result && st.result.status === "ok") {
40197
40256
  if (name === "Jobs") return `${st.result.ranked.length} roles matching your profile`;
40198
- if (name === "Bounties") return `${st.result.bounties.length} bounties you could knock out`;
40199
- if (name === "Devs") return `${st.result.results.length} matches in the builder directory`;
40257
+ if (name === "Bounties")
40258
+ return `${st.result.bounties.length} bounties you could knock out`;
40259
+ if (name === "Devs")
40260
+ return `${st.result.results.length} matches in the builder directory`;
40200
40261
  }
40201
40262
  return name;
40202
40263
  }
@@ -40226,7 +40287,9 @@ function runHubTui({ input = process.stdin, output = process.stdout, signals, de
40226
40287
  const row = startRow + i;
40227
40288
  if (row < 0 || row >= rows) continue;
40228
40289
  const t = TH_GLYPH.length <= 1 ? 0 : i / (TH_GLYPH.length - 1);
40229
- drawText(buf, cols, row, startCol, TH_GLYPH[i], { fg: gradientFg(BRAND_GRADIENT, t, level) });
40290
+ drawText(buf, cols, row, startCol, TH_GLYPH[i], {
40291
+ fg: gradientFg(BRAND_GRADIENT, t, level)
40292
+ });
40230
40293
  }
40231
40294
  const hint = "press any key to continue";
40232
40295
  const hintRow = Math.min(rows - 1, startRow + TH_GLYPH.length + 1);
@@ -40680,6 +40743,7 @@ var init_jpi_hub = __esm({
40680
40743
  "bin/jpi-hub.js"() {
40681
40744
  "use strict";
40682
40745
  init_tui_core();
40746
+ init_sanitize();
40683
40747
  init_claims();
40684
40748
  init_profile();
40685
40749
  init_config();
@@ -40717,7 +40781,15 @@ var init_jpi_hub = __esm({
40717
40781
  ];
40718
40782
  BRAND_GRADIENT = ["#9d8fff", "#7c6af7", "#5b4fcf"];
40719
40783
  DEFAULT_SPLASH_MS = 900;
40720
- STATE_LEVEL = { abandoned: 0, claimed: 2, working: 3, "in-review": 4, ready: 5, submitted: 6, merged: 7 };
40784
+ STATE_LEVEL = {
40785
+ abandoned: 0,
40786
+ claimed: 2,
40787
+ working: 3,
40788
+ "in-review": 4,
40789
+ ready: 5,
40790
+ submitted: 6,
40791
+ merged: 7
40792
+ };
40721
40793
  SPARK_LEVELS = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
40722
40794
  }
40723
40795
  });
@@ -380,6 +380,19 @@ var init_tui_core = __esm({
380
380
  }
381
381
  });
382
382
 
383
+ // bin/sanitize.js
384
+ function formatUsd(a) {
385
+ if (typeof a === "number") return Number.isFinite(a) ? "$" + a.toLocaleString() : "$\u2014";
386
+ if (typeof a !== "string" || a.trim() === "") return "$\u2014";
387
+ const n = Number(a);
388
+ return Number.isFinite(n) ? "$" + n.toLocaleString() : "$\u2014";
389
+ }
390
+ var init_sanitize = __esm({
391
+ "bin/sanitize.js"() {
392
+ "use strict";
393
+ }
394
+ });
395
+
383
396
  // src/state-dir.ts
384
397
  import { closeSync, constants, fchmodSync, fstatSync, mkdirSync, openSync } from "fs";
385
398
  function warnStateDirOnce(dir, message) {
@@ -539,7 +552,7 @@ function nowISO() {
539
552
  return (/* @__PURE__ */ new Date()).toISOString();
540
553
  }
541
554
  function defangText(s) {
542
- return typeof s === "string" ? s.replace(CONTROL_CHARS, "") : s;
555
+ return typeof s === "string" ? s.replace(WHITESPACE_CONTROLS, " ").replace(CONTROL_CHARS, "") : s;
543
556
  }
544
557
  function finiteAmount(a) {
545
558
  if (typeof a === "number") return Number.isFinite(a) ? a : null;
@@ -677,7 +690,7 @@ function acceptedPRRate(claims = readClaims()) {
677
690
  const merged = claims.filter((c) => c.state === "merged").length;
678
691
  return { merged, total, rate: total === 0 ? 0 : merged / total };
679
692
  }
680
- var TERMINALHIRE_DIR, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, CONTROL_CHARS;
693
+ var TERMINALHIRE_DIR, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, WHITESPACE_CONTROLS, CONTROL_CHARS;
681
694
  var init_claims = __esm({
682
695
  "src/claims.ts"() {
683
696
  "use strict";
@@ -733,6 +746,7 @@ var init_claims = __esm({
733
746
  ]),
734
747
  submitted: /* @__PURE__ */ new Set(["claimed", "working", "in-review", "ready"])
735
748
  };
749
+ WHITESPACE_CONTROLS = /[\t\n\v\f\r]+/g;
736
750
  CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
737
751
  }
738
752
  });
@@ -12649,6 +12663,7 @@ var init_intro2 = __esm({
12649
12663
 
12650
12664
  // bin/jpi-hub.js
12651
12665
  init_tui_core();
12666
+ init_sanitize();
12652
12667
  init_claims();
12653
12668
  init_profile();
12654
12669
  init_config();
@@ -12685,6 +12700,7 @@ var BAK_FILE = `${STATUS_FILE}.bak`;
12685
12700
 
12686
12701
  // bin/jpi-jobs.js
12687
12702
  init_cache_store();
12703
+ init_sanitize();
12688
12704
  var __dirname = fileURLToPath2(new URL(".", import.meta.url));
12689
12705
  var TERMINALHIRE_DIR11 = process.env.TERMINALHIRE_DIR || join16(homedir13(), ".terminalhire");
12690
12706
  var INDEX_CACHE_FILE3 = join16(TERMINALHIRE_DIR11, "index-cache.json");
@@ -12772,11 +12788,12 @@ async function getJobMatches({ quiet = false, offline = false } = {}) {
12772
12788
  // bin/jpi-bounties.js
12773
12789
  init_src();
12774
12790
  init_cache_store();
12791
+ init_sanitize();
12792
+ init_founder_pin();
12775
12793
  import { readFileSync as readFileSync15 } from "fs";
12776
12794
  import { join as join18 } from "path";
12777
12795
  import { homedir as homedir15 } from "os";
12778
12796
  import { createInterface as createInterface3 } from "readline";
12779
- init_founder_pin();
12780
12797
  var TERMINALHIRE_DIR13 = process.env.TERMINALHIRE_DIR || join18(homedir15(), ".terminalhire");
12781
12798
  var INDEX_CACHE_FILE4 = join18(TERMINALHIRE_DIR13, "index-cache.json");
12782
12799
  var INDEX_TTL_MS2 = 15 * 60 * 1e3;
@@ -12970,6 +12987,9 @@ function excludeOwnCard(results, ownLogin) {
12970
12987
  });
12971
12988
  }
12972
12989
 
12990
+ // bin/jpi-devs.js
12991
+ init_sanitize();
12992
+
12973
12993
  // bin/match-slots.js
12974
12994
  var ROTATE_WINDOW_MS = 5 * 60 * 1e3;
12975
12995
  var ROLE_HEAD_ROTATE_MS = 60 * 60 * 1e3;
@@ -13081,7 +13101,15 @@ var TH_GLYPH = [
13081
13101
  ];
13082
13102
  var BRAND_GRADIENT = ["#9d8fff", "#7c6af7", "#5b4fcf"];
13083
13103
  var DEFAULT_SPLASH_MS = 900;
13084
- var STATE_LEVEL = { abandoned: 0, claimed: 2, working: 3, "in-review": 4, ready: 5, submitted: 6, merged: 7 };
13104
+ var STATE_LEVEL = {
13105
+ abandoned: 0,
13106
+ claimed: 2,
13107
+ working: 3,
13108
+ "in-review": 4,
13109
+ ready: 5,
13110
+ submitted: 6,
13111
+ merged: 7
13112
+ };
13085
13113
  var SPARK_LEVELS = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
13086
13114
  function sparkline(rows) {
13087
13115
  if (!rows || !rows.length) return "";
@@ -13103,7 +13131,12 @@ function substrMatch(query, candidate) {
13103
13131
  if (query === "") return true;
13104
13132
  return candidate.toLowerCase().includes(query.toLowerCase());
13105
13133
  }
13106
- function runHubTui({ input = process.stdin, output = process.stdout, signals, deps = {} } = {}) {
13134
+ function runHubTui({
13135
+ input = process.stdin,
13136
+ output = process.stdout,
13137
+ signals,
13138
+ deps = {}
13139
+ } = {}) {
13107
13140
  const {
13108
13141
  listClaims: _listClaims = listClaims,
13109
13142
  acceptedPRRate: _acceptedPRRate = acceptedPRRate,
@@ -13266,20 +13299,50 @@ function runHubTui({ input = process.stdin, output = process.stdout, signals, de
13266
13299
  acked = false;
13267
13300
  }
13268
13301
  if (!acked) {
13269
- inboxState = { loaded: true, loading: false, status: "disclosure", items: null, message: null };
13302
+ inboxState = {
13303
+ loaded: true,
13304
+ loading: false,
13305
+ status: "disclosure",
13306
+ items: null,
13307
+ message: null
13308
+ };
13270
13309
  return;
13271
13310
  }
13272
13311
  inboxState = { ...inboxState, loading: true };
13273
13312
  _buildInboxItems().then((built) => {
13274
13313
  if (built.status === "ok") {
13275
- inboxState = { loaded: true, loading: false, status: "ok", items: built.items, message: null };
13314
+ inboxState = {
13315
+ loaded: true,
13316
+ loading: false,
13317
+ status: "ok",
13318
+ items: built.items,
13319
+ message: null
13320
+ };
13276
13321
  } else if (built.status === "error") {
13277
- inboxState = { loaded: true, loading: false, status: "error", items: null, message: built.message || "could not load inbox" };
13322
+ inboxState = {
13323
+ loaded: true,
13324
+ loading: false,
13325
+ status: "error",
13326
+ items: null,
13327
+ message: built.message || "could not load inbox"
13328
+ };
13278
13329
  } else {
13279
- inboxState = { loaded: true, loading: false, status: built.status, items: null, message: null };
13330
+ inboxState = {
13331
+ loaded: true,
13332
+ loading: false,
13333
+ status: built.status,
13334
+ items: null,
13335
+ message: null
13336
+ };
13280
13337
  }
13281
13338
  }).catch((e) => {
13282
- inboxState = { loaded: true, loading: false, status: "error", items: null, message: errMsg(e) };
13339
+ inboxState = {
13340
+ loaded: true,
13341
+ loading: false,
13342
+ status: "error",
13343
+ items: null,
13344
+ message: errMsg(e)
13345
+ };
13283
13346
  }).finally(() => repaint());
13284
13347
  return;
13285
13348
  }
@@ -13313,7 +13376,9 @@ function runHubTui({ input = process.stdin, output = process.stdout, signals, de
13313
13376
  function inboxRows(st) {
13314
13377
  switch (st.status) {
13315
13378
  case "disclosure":
13316
- return ["Messaging disclosure not yet acknowledged \u2014 run `terminalhire inbox` once to review it and enable messages."];
13379
+ return [
13380
+ "Messaging disclosure not yet acknowledged \u2014 run `terminalhire inbox` once to review it and enable messages."
13381
+ ];
13317
13382
  case "not-linked":
13318
13383
  return ["Not linked \u2014 run `terminalhire link` to connect your web session."];
13319
13384
  case "expired":
@@ -13356,11 +13421,13 @@ function runHubTui({ input = process.stdin, output = process.stdout, signals, de
13356
13421
  case "empty":
13357
13422
  return ["No bounties available right now. Check back through the day."];
13358
13423
  case "demoted":
13359
- return ["Paid bounties paused \u2014 run `bounties --priced` to view them. Try Contribute or Jobs."];
13424
+ return [
13425
+ "Paid bounties paused \u2014 run `bounties --priced` to view them. Try Contribute or Jobs."
13426
+ ];
13360
13427
  case "ok":
13361
13428
  return result.bounties.map((job) => {
13362
13429
  const b = job.bounty || {};
13363
- const amt = b.amountUSD != null ? "$" + b.amountUSD.toLocaleString() : "$\u2014";
13430
+ const amt = formatUsd(b.amountUSD);
13364
13431
  const repo = sanitizeLine(b.repoFullName || job.company);
13365
13432
  const prs = b.competingOpenPRs;
13366
13433
  const contend = prs != null && prs > 0 ? ` \xB7 \u26A0 ${prs} in flight` : "";
@@ -13415,7 +13482,9 @@ function runHubTui({ input = process.stdin, output = process.stdout, signals, de
13415
13482
  if (it.note) rows.push(` note: ${sanitizeLine(it.note)}`);
13416
13483
  if (it.contact) rows.push(` contact: ${sanitizeLine(it.contact)}`);
13417
13484
  else if (it.role === "incoming" && it.status === "pending") {
13418
- rows.push(` \u2192 accept: terminalhire intro --accept @${sanitizeLine(it.counterpartyLogin)}`);
13485
+ rows.push(
13486
+ ` \u2192 accept: terminalhire intro --accept @${sanitizeLine(it.counterpartyLogin)}`
13487
+ );
13419
13488
  }
13420
13489
  }
13421
13490
  return rows;
@@ -13502,7 +13571,8 @@ function runHubTui({ input = process.stdin, output = process.stdout, signals, de
13502
13571
  if (name === "Claims") {
13503
13572
  if (!claimsState.loaded) return ["Loading\u2026"];
13504
13573
  if (claimsState.error) return ["Could not read claims \u2014 " + claimsState.error];
13505
- if (!claimsState.rows.length) return ["No claims yet \u2014 run `terminalhire claim <url>` to start one."];
13574
+ if (!claimsState.rows.length)
13575
+ return ["No claims yet \u2014 run `terminalhire claim <url>` to start one."];
13506
13576
  return claimsState.rows.map(formatClaimRow);
13507
13577
  }
13508
13578
  if (name === "Profile") {
@@ -13532,8 +13602,10 @@ function runHubTui({ input = process.stdin, output = process.stdout, signals, de
13532
13602
  const st = listState[name];
13533
13603
  if (st.loaded && !st.error && st.result && st.result.status === "ok") {
13534
13604
  if (name === "Jobs") return `${st.result.ranked.length} roles matching your profile`;
13535
- if (name === "Bounties") return `${st.result.bounties.length} bounties you could knock out`;
13536
- if (name === "Devs") return `${st.result.results.length} matches in the builder directory`;
13605
+ if (name === "Bounties")
13606
+ return `${st.result.bounties.length} bounties you could knock out`;
13607
+ if (name === "Devs")
13608
+ return `${st.result.results.length} matches in the builder directory`;
13537
13609
  }
13538
13610
  return name;
13539
13611
  }
@@ -13563,7 +13635,9 @@ function runHubTui({ input = process.stdin, output = process.stdout, signals, de
13563
13635
  const row = startRow + i;
13564
13636
  if (row < 0 || row >= rows) continue;
13565
13637
  const t = TH_GLYPH.length <= 1 ? 0 : i / (TH_GLYPH.length - 1);
13566
- drawText(buf, cols, row, startCol, TH_GLYPH[i], { fg: gradientFg(BRAND_GRADIENT, t, level) });
13638
+ drawText(buf, cols, row, startCol, TH_GLYPH[i], {
13639
+ fg: gradientFg(BRAND_GRADIENT, t, level)
13640
+ });
13567
13641
  }
13568
13642
  const hint = "press any key to continue";
13569
13643
  const hintRow = Math.min(rows - 1, startRow + TH_GLYPH.length + 1);
@@ -10578,6 +10578,50 @@ var init_cache_store = __esm({
10578
10578
  }
10579
10579
  });
10580
10580
 
10581
+ // bin/sanitize.js
10582
+ function sanitizeText(s) {
10583
+ if (s == null) return "";
10584
+ return String(s).replace(WHITESPACE_CONTROLS, " ").replace(CONTROL_CHARS, "");
10585
+ }
10586
+ function formatUsd(a) {
10587
+ if (typeof a === "number") return Number.isFinite(a) ? "$" + a.toLocaleString() : "$\u2014";
10588
+ if (typeof a !== "string" || a.trim() === "") return "$\u2014";
10589
+ const n = Number(a);
10590
+ return Number.isFinite(n) ? "$" + n.toLocaleString() : "$\u2014";
10591
+ }
10592
+ function safeHttpUrl(url) {
10593
+ if (url == null) return null;
10594
+ const raw = String(url);
10595
+ CONTROL_CHARS.lastIndex = 0;
10596
+ if (CONTROL_CHARS.test(raw)) return null;
10597
+ let parsed;
10598
+ try {
10599
+ parsed = new URL(raw);
10600
+ } catch {
10601
+ return null;
10602
+ }
10603
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
10604
+ return parsed.href;
10605
+ }
10606
+ function linkTitle(title, url) {
10607
+ const safeTitle = sanitizeText(title);
10608
+ const href = safeHttpUrl(url);
10609
+ const isTTY = process.stdout.isTTY;
10610
+ const noColor = process.env["NO_COLOR"] !== void 0;
10611
+ if (isTTY && !noColor && href) {
10612
+ return `\x1B]8;;${href}\x1B\\${safeTitle}\x1B]8;;\x1B\\`;
10613
+ }
10614
+ return href ? `${safeTitle} (${href})` : safeTitle;
10615
+ }
10616
+ var WHITESPACE_CONTROLS, CONTROL_CHARS;
10617
+ var init_sanitize = __esm({
10618
+ "bin/sanitize.js"() {
10619
+ "use strict";
10620
+ WHITESPACE_CONTROLS = /[\t\n\v\f\r]+/g;
10621
+ CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
10622
+ }
10623
+ });
10624
+
10581
10625
  // bin/spinner-io.js
10582
10626
  import {
10583
10627
  readFileSync as readFileSync4,
@@ -11291,7 +11335,7 @@ function buildTipsDetailed(topMatches, baseUrl, max = 8, opts = {}) {
11291
11335
  const token = jobShortToken(String(m.id));
11292
11336
  const url = `${base}/j/${token}`;
11293
11337
  if (source === "bounty") {
11294
- const money = m.amountUSD != null ? `$${Number(m.amountUSD).toLocaleString()}` : "$\u2014";
11338
+ const money = formatUsd(m.amountUSD);
11295
11339
  const repo = m.repo || companyRaw;
11296
11340
  out.push(`\u{1F48E} ${money} \xB7 ${title} \xB7 ${repo} \xB7 ${pct}% \u2014 ${url}`);
11297
11341
  } else if (source === "contribute") {
@@ -11361,6 +11405,7 @@ var init_spinner_render = __esm({
11361
11405
  init_spinner_verbs();
11362
11406
  init_spinner_seen();
11363
11407
  init_spinner_io();
11408
+ init_sanitize();
11364
11409
  init_src();
11365
11410
  }
11366
11411
  });
@@ -12297,39 +12342,7 @@ function markClicked(id) {
12297
12342
 
12298
12343
  // bin/jpi-jobs.js
12299
12344
  init_cache_store();
12300
-
12301
- // bin/sanitize.js
12302
- var CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
12303
- function sanitizeText(s) {
12304
- if (s == null) return "";
12305
- return String(s).replace(CONTROL_CHARS, "");
12306
- }
12307
- function safeHttpUrl(url) {
12308
- if (url == null) return null;
12309
- const raw = String(url);
12310
- CONTROL_CHARS.lastIndex = 0;
12311
- if (CONTROL_CHARS.test(raw)) return null;
12312
- let parsed;
12313
- try {
12314
- parsed = new URL(raw);
12315
- } catch {
12316
- return null;
12317
- }
12318
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
12319
- return parsed.href;
12320
- }
12321
- function linkTitle(title, url) {
12322
- const safeTitle = sanitizeText(title);
12323
- const href = safeHttpUrl(url);
12324
- const isTTY = process.stdout.isTTY;
12325
- const noColor = process.env["NO_COLOR"] !== void 0;
12326
- if (isTTY && !noColor && href) {
12327
- return `\x1B]8;;${href}\x1B\\${safeTitle}\x1B]8;;\x1B\\`;
12328
- }
12329
- return href ? `${safeTitle} (${href})` : safeTitle;
12330
- }
12331
-
12332
- // bin/jpi-jobs.js
12345
+ init_sanitize();
12333
12346
  var __dirname2 = fileURLToPath3(new URL(".", import.meta.url));
12334
12347
  var TERMINALHIRE_DIR6 = process.env.TERMINALHIRE_DIR || join14(homedir9(), ".terminalhire");
12335
12348
  var INDEX_CACHE_FILE2 = join14(TERMINALHIRE_DIR6, "index-cache.json");
@@ -10980,12 +10980,19 @@ var init_open_url = __esm({
10980
10980
  // bin/sanitize.js
10981
10981
  function sanitizeText(s) {
10982
10982
  if (s == null) return "";
10983
- return String(s).replace(CONTROL_CHARS, "");
10983
+ return String(s).replace(WHITESPACE_CONTROLS, " ").replace(CONTROL_CHARS, "");
10984
10984
  }
10985
- var CONTROL_CHARS;
10985
+ function formatUsd(a) {
10986
+ if (typeof a === "number") return Number.isFinite(a) ? "$" + a.toLocaleString() : "$\u2014";
10987
+ if (typeof a !== "string" || a.trim() === "") return "$\u2014";
10988
+ const n = Number(a);
10989
+ return Number.isFinite(n) ? "$" + n.toLocaleString() : "$\u2014";
10990
+ }
10991
+ var WHITESPACE_CONTROLS, CONTROL_CHARS;
10986
10992
  var init_sanitize = __esm({
10987
10993
  "bin/sanitize.js"() {
10988
10994
  "use strict";
10995
+ WHITESPACE_CONTROLS = /[\t\n\v\f\r]+/g;
10989
10996
  CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
10990
10997
  }
10991
10998
  });
@@ -11141,7 +11148,7 @@ function nowISO() {
11141
11148
  return (/* @__PURE__ */ new Date()).toISOString();
11142
11149
  }
11143
11150
  function defangText(s) {
11144
- return typeof s === "string" ? s.replace(CONTROL_CHARS2, "") : s;
11151
+ return typeof s === "string" ? s.replace(WHITESPACE_CONTROLS2, " ").replace(CONTROL_CHARS2, "") : s;
11145
11152
  }
11146
11153
  function finiteAmount(a) {
11147
11154
  if (typeof a === "number") return Number.isFinite(a) ? a : null;
@@ -11279,7 +11286,7 @@ function acceptedPRRate(claims = readClaims()) {
11279
11286
  const merged = claims.filter((c) => c.state === "merged").length;
11280
11287
  return { merged, total, rate: total === 0 ? 0 : merged / total };
11281
11288
  }
11282
- var TERMINALHIRE_DIR5, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, CONTROL_CHARS2;
11289
+ var TERMINALHIRE_DIR5, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, WHITESPACE_CONTROLS2, CONTROL_CHARS2;
11283
11290
  var init_claims = __esm({
11284
11291
  "src/claims.ts"() {
11285
11292
  "use strict";
@@ -11335,6 +11342,7 @@ var init_claims = __esm({
11335
11342
  ]),
11336
11343
  submitted: /* @__PURE__ */ new Set(["claimed", "working", "in-review", "ready"])
11337
11344
  };
11345
+ WHITESPACE_CONTROLS2 = /[\t\n\v\f\r]+/g;
11338
11346
  CONTROL_CHARS2 = /[\x00-\x1f\x7f-\x9f]/g;
11339
11347
  }
11340
11348
  });
@@ -27080,10 +27088,7 @@ async function pollPR(prUrl) {
27080
27088
  }
27081
27089
  }
27082
27090
  function fmtAmount(a) {
27083
- if (a == null) return "$\u2014";
27084
- if (typeof a === "string" && a.trim() === "") return "$\u2014";
27085
- const n = typeof a === "number" ? a : Number(a);
27086
- return Number.isFinite(n) ? "$" + n.toLocaleString() : "$\u2014";
27091
+ return formatUsd(a);
27087
27092
  }
27088
27093
  function fmtClaimAmount(c) {
27089
27094
  return c.kind === "contribution" ? "contribution" : fmtAmount(c.amountUSD);
@@ -27222,8 +27227,15 @@ async function resolveBounty(arg) {
27222
27227
  return {
27223
27228
  bountyId,
27224
27229
  indexNativeId,
27225
- title,
27226
- repoFullName,
27230
+ // DEFANGED HERE, at the one place every tier of this resolver converges.
27231
+ // `claims.ts` cleans what comes OUT of the local store, which covers every later
27232
+ // read — but this object is printed (the preview card, the record card) and
27233
+ // handed to `recordClaim` BEFORE any store exists for it, so on a claim's first
27234
+ // use the store boundary has not run yet. `repoFullName` also goes on to be
27235
+ // interpolated into GitHub API URLs from here. Found by a cross-model review of
27236
+ // TERM-380; the store fix alone left this path open.
27237
+ title: sanitizeText(title),
27238
+ repoFullName: repoFullName == null ? repoFullName : sanitizeText(repoFullName),
27227
27239
  issueUrl,
27228
27240
  amountUSD,
27229
27241
  source,
@@ -28444,7 +28456,7 @@ async function cmdStart(id, flags = {}) {
28444
28456
  console.error(`terminalhire claim: no claim with id '${id}'.`);
28445
28457
  process.exit(1);
28446
28458
  }
28447
- if (claim.approval?.state === "pending" && !approvalsChecked) {
28459
+ if (claim.approval?.state === "pending") {
28448
28460
  ({ approvalsChecked, approvalsUnavailable } = await syncFounderApprovals(claims, [claim]));
28449
28461
  claim = claims.findClaim(id);
28450
28462
  }
@@ -12236,6 +12236,19 @@ var init_spinner_verbs = __esm({
12236
12236
  }
12237
12237
  });
12238
12238
 
12239
+ // bin/sanitize.js
12240
+ function formatUsd(a) {
12241
+ if (typeof a === "number") return Number.isFinite(a) ? "$" + a.toLocaleString() : "$\u2014";
12242
+ if (typeof a !== "string" || a.trim() === "") return "$\u2014";
12243
+ const n = Number(a);
12244
+ return Number.isFinite(n) ? "$" + n.toLocaleString() : "$\u2014";
12245
+ }
12246
+ var init_sanitize = __esm({
12247
+ "bin/sanitize.js"() {
12248
+ "use strict";
12249
+ }
12250
+ });
12251
+
12239
12252
  // bin/spinner-render.js
12240
12253
  function interleaveBySource(topMatches) {
12241
12254
  if (!Array.isArray(topMatches) || topMatches.length === 0) return topMatches;
@@ -12317,7 +12330,7 @@ function buildTipsDetailed(topMatches, baseUrl, max = 8, opts = {}) {
12317
12330
  const token = jobShortToken(String(m.id));
12318
12331
  const url = `${base}/j/${token}`;
12319
12332
  if (source === "bounty") {
12320
- const money = m.amountUSD != null ? `$${Number(m.amountUSD).toLocaleString()}` : "$\u2014";
12333
+ const money = formatUsd(m.amountUSD);
12321
12334
  const repo = m.repo || companyRaw;
12322
12335
  out.push(`\u{1F48E} ${money} \xB7 ${title} \xB7 ${repo} \xB7 ${pct}% \u2014 ${url}`);
12323
12336
  } else if (source === "contribute") {
@@ -12387,6 +12400,7 @@ var init_spinner_render = __esm({
12387
12400
  init_spinner_verbs();
12388
12401
  init_spinner_seen();
12389
12402
  init_spinner_io();
12403
+ init_sanitize();
12390
12404
  init_src();
12391
12405
  }
12392
12406
  });
@@ -12563,7 +12577,7 @@ function nowISO() {
12563
12577
  return (/* @__PURE__ */ new Date()).toISOString();
12564
12578
  }
12565
12579
  function defangText(s) {
12566
- return typeof s === "string" ? s.replace(CONTROL_CHARS, "") : s;
12580
+ return typeof s === "string" ? s.replace(WHITESPACE_CONTROLS, " ").replace(CONTROL_CHARS, "") : s;
12567
12581
  }
12568
12582
  function finiteAmount(a) {
12569
12583
  if (typeof a === "number") return Number.isFinite(a) ? a : null;
@@ -12701,7 +12715,7 @@ function acceptedPRRate(claims = readClaims()) {
12701
12715
  const merged = claims.filter((c) => c.state === "merged").length;
12702
12716
  return { merged, total, rate: total === 0 ? 0 : merged / total };
12703
12717
  }
12704
- var TERMINALHIRE_DIR8, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS2, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, CONTROL_CHARS;
12718
+ var TERMINALHIRE_DIR8, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS2, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, WHITESPACE_CONTROLS, CONTROL_CHARS;
12705
12719
  var init_claims = __esm({
12706
12720
  "src/claims.ts"() {
12707
12721
  "use strict";
@@ -12757,6 +12771,7 @@ var init_claims = __esm({
12757
12771
  ]),
12758
12772
  submitted: /* @__PURE__ */ new Set(["claimed", "working", "in-review", "ready"])
12759
12773
  };
12774
+ WHITESPACE_CONTROLS = /[\t\n\v\f\r]+/g;
12760
12775
  CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
12761
12776
  }
12762
12777
  });
@@ -1910,7 +1910,7 @@ function nowISO2() {
1910
1910
  return (/* @__PURE__ */ new Date()).toISOString();
1911
1911
  }
1912
1912
  function defangText(s) {
1913
- return typeof s === "string" ? s.replace(CONTROL_CHARS, "") : s;
1913
+ return typeof s === "string" ? s.replace(WHITESPACE_CONTROLS, " ").replace(CONTROL_CHARS, "") : s;
1914
1914
  }
1915
1915
  function finiteAmount(a) {
1916
1916
  if (typeof a === "number") return Number.isFinite(a) ? a : null;
@@ -2048,7 +2048,7 @@ function acceptedPRRate(claims = readClaims()) {
2048
2048
  const merged = claims.filter((c) => c.state === "merged").length;
2049
2049
  return { merged, total, rate: total === 0 ? 0 : merged / total };
2050
2050
  }
2051
- var TERMINALHIRE_DIR4, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, CONTROL_CHARS;
2051
+ var TERMINALHIRE_DIR4, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, WHITESPACE_CONTROLS, CONTROL_CHARS;
2052
2052
  var init_claims = __esm({
2053
2053
  "src/claims.ts"() {
2054
2054
  "use strict";
@@ -2104,6 +2104,7 @@ var init_claims = __esm({
2104
2104
  ]),
2105
2105
  submitted: /* @__PURE__ */ new Set(["claimed", "working", "in-review", "ready"])
2106
2106
  };
2107
+ WHITESPACE_CONTROLS = /[\t\n\v\f\r]+/g;
2107
2108
  CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
2108
2109
  }
2109
2110
  });
@@ -1,8 +1,15 @@
1
1
  // bin/sanitize.js
2
+ var WHITESPACE_CONTROLS = /[\t\n\v\f\r]+/g;
2
3
  var CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
3
4
  function sanitizeText(s) {
4
5
  if (s == null) return "";
5
- return String(s).replace(CONTROL_CHARS, "");
6
+ return String(s).replace(WHITESPACE_CONTROLS, " ").replace(CONTROL_CHARS, "");
7
+ }
8
+ function formatUsd(a) {
9
+ if (typeof a === "number") return Number.isFinite(a) ? "$" + a.toLocaleString() : "$\u2014";
10
+ if (typeof a !== "string" || a.trim() === "") return "$\u2014";
11
+ const n = Number(a);
12
+ return Number.isFinite(n) ? "$" + n.toLocaleString() : "$\u2014";
6
13
  }
7
14
  function safeHttpUrl(url) {
8
15
  if (url == null) return null;
@@ -29,6 +36,7 @@ function linkTitle(title, url) {
29
36
  return href ? `${safeTitle} (${href})` : safeTitle;
30
37
  }
31
38
  export {
39
+ formatUsd,
32
40
  linkTitle,
33
41
  safeHttpUrl,
34
42
  sanitizeText
@@ -607,6 +607,14 @@ function clearSpinnerTips() {
607
607
  return { cleared: true };
608
608
  }
609
609
 
610
+ // bin/sanitize.js
611
+ function formatUsd(a) {
612
+ if (typeof a === "number") return Number.isFinite(a) ? "$" + a.toLocaleString() : "$\u2014";
613
+ if (typeof a !== "string" || a.trim() === "") return "$\u2014";
614
+ const n = Number(a);
615
+ return Number.isFinite(n) ? "$" + n.toLocaleString() : "$\u2014";
616
+ }
617
+
610
618
  // ../../packages/core/src/vocab/graph.data.ts
611
619
  var VOCAB_NODES = [
612
620
  // ── Languages ─────────────────────────────────────────────────────────────
@@ -1404,7 +1412,7 @@ function buildTipsDetailed(topMatches, baseUrl, max = 8, opts = {}) {
1404
1412
  const token = jobShortToken(String(m.id));
1405
1413
  const url = `${base}/j/${token}`;
1406
1414
  if (source === "bounty") {
1407
- const money = m.amountUSD != null ? `$${Number(m.amountUSD).toLocaleString()}` : "$\u2014";
1415
+ const money = formatUsd(m.amountUSD);
1408
1416
  const repo = m.repo || companyRaw;
1409
1417
  out.push(`\u{1F48E} ${money} \xB7 ${title} \xB7 ${repo} \xB7 ${pct}% \u2014 ${url}`);
1410
1418
  } else if (source === "contribute") {
@@ -635,6 +635,14 @@ function buildSpinnerPool(topMatches, max = 6, opts = {}) {
635
635
  return withStale(pool);
636
636
  }
637
637
 
638
+ // bin/sanitize.js
639
+ function formatUsd(a) {
640
+ if (typeof a === "number") return Number.isFinite(a) ? "$" + a.toLocaleString() : "$\u2014";
641
+ if (typeof a !== "string" || a.trim() === "") return "$\u2014";
642
+ const n = Number(a);
643
+ return Number.isFinite(n) ? "$" + n.toLocaleString() : "$\u2014";
644
+ }
645
+
638
646
  // ../../packages/core/src/vocab/graph.data.ts
639
647
  var VOCAB_NODES = [
640
648
  // ── Languages ─────────────────────────────────────────────────────────────
@@ -1432,7 +1440,7 @@ function buildTipsDetailed(topMatches, baseUrl, max = 8, opts = {}) {
1432
1440
  const token = jobShortToken(String(m.id));
1433
1441
  const url = `${base}/j/${token}`;
1434
1442
  if (source === "bounty") {
1435
- const money = m.amountUSD != null ? `$${Number(m.amountUSD).toLocaleString()}` : "$\u2014";
1443
+ const money = formatUsd(m.amountUSD);
1436
1444
  const repo = m.repo || companyRaw;
1437
1445
  out.push(`\u{1F48E} ${money} \xB7 ${title} \xB7 ${repo} \xB7 ${pct}% \u2014 ${url}`);
1438
1446
  } else if (source === "contribute") {
@@ -161,9 +161,10 @@ function nextPolledState(from, observed) {
161
161
  function nowISO() {
162
162
  return (/* @__PURE__ */ new Date()).toISOString();
163
163
  }
164
+ var WHITESPACE_CONTROLS = /[\t\n\v\f\r]+/g;
164
165
  var CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
165
166
  function defangText(s) {
166
- return typeof s === "string" ? s.replace(CONTROL_CHARS, "") : s;
167
+ return typeof s === "string" ? s.replace(WHITESPACE_CONTROLS, " ").replace(CONTROL_CHARS, "") : s;
167
168
  }
168
169
  function finiteAmount(a) {
169
170
  if (typeof a === "number") return Number.isFinite(a) ? a : null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "terminalhire",
3
- "version": "0.41.2",
3
+ "version": "0.41.3",
4
4
  "description": "Local-first job matching for developers — ambient job matches in the Claude Code spinner. Your profile never leaves your machine.",
5
5
  "repository": {
6
6
  "type": "git",