terminalhire 0.42.3 → 0.42.5

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.
@@ -514,7 +514,7 @@ var CLAIM_PUSH_AUTO_MARKER = join5(TERMINALHIRE_DIR4, "claim-push-auto.json");
514
514
  var CLAIM_PUSH_TOKEN_FILE = join5(TERMINALHIRE_DIR4, "claim-push-token.enc");
515
515
  var CLAIM_PUSH_MANUAL_MARKER = join5(TERMINALHIRE_DIR4, "claim-push.json");
516
516
  var CLAIM_SYNC_BASE = "https://terminalhire.com";
517
- var AUTO_CONSENT_VERSION = 2;
517
+ var AUTO_CONSENT_VERSION = 3;
518
518
  var AUTO_PUSH_THROTTLE_MS = 24 * 60 * 60 * 1e3;
519
519
  async function writePushTokenEnc(rawToken) {
520
520
  ensureStateDirForSecret(TERMINALHIRE_DIR4);
@@ -617,6 +617,82 @@ async function shouldNudgeUnpushed() {
617
617
  return false;
618
618
  }
619
619
  }
620
+ var CLAIM_HEARTBEAT_FILE = join5(TERMINALHIRE_DIR4, "claim-heartbeat.json");
621
+ var HEARTBEAT_MIN_INTERVAL_MS = 3e4;
622
+ var HEARTBEAT_MIN_CONSENT_VERSION = 3;
623
+ function readHeartbeatState() {
624
+ try {
625
+ return existsSync5(CLAIM_HEARTBEAT_FILE) ? JSON.parse(readFileSync4(CLAIM_HEARTBEAT_FILE, "utf8")) : {};
626
+ } catch {
627
+ return {};
628
+ }
629
+ }
630
+ function recordHeartbeatBeat(claimId, at) {
631
+ try {
632
+ ensureStateDir(TERMINALHIRE_DIR4);
633
+ const state = readHeartbeatState();
634
+ state[claimId] = at;
635
+ writeFileSync4(CLAIM_HEARTBEAT_FILE, JSON.stringify(state, null, 2) + "\n", "utf8");
636
+ } catch {
637
+ }
638
+ }
639
+ function heartbeatGate(params) {
640
+ const {
641
+ autoMarkerExists,
642
+ tokenFileExists,
643
+ consentVersion,
644
+ bountyId,
645
+ claimId,
646
+ lastBeatAt,
647
+ now = Date.now(),
648
+ minIntervalMs = HEARTBEAT_MIN_INTERVAL_MS
649
+ } = params;
650
+ if (typeof bountyId !== "string" || bountyId.trim() === "") {
651
+ return { beat: false, reason: "no-server-ids" };
652
+ }
653
+ if (typeof claimId !== "string" || claimId.trim() === "") {
654
+ return { beat: false, reason: "no-server-ids" };
655
+ }
656
+ if (!autoMarkerExists || !tokenFileExists) {
657
+ return { beat: false, reason: "not-opted-in" };
658
+ }
659
+ if (!(Number.isInteger(consentVersion) && consentVersion >= HEARTBEAT_MIN_CONSENT_VERSION)) {
660
+ return { beat: false, reason: "consent-predates-presence" };
661
+ }
662
+ const last = lastBeatAt ? Date.parse(lastBeatAt) : NaN;
663
+ if (!Number.isNaN(last) && now - last < minIntervalMs) {
664
+ return { beat: false, reason: "throttled" };
665
+ }
666
+ return { beat: true, reason: "ok" };
667
+ }
668
+ async function postClaimHeartbeat({ bountyId, claimId, now = Date.now() } = {}) {
669
+ try {
670
+ const marker = readAutoMarker();
671
+ const gate = heartbeatGate({
672
+ autoMarkerExists: Boolean(marker && marker.autoConsentedAt),
673
+ tokenFileExists: existsSync5(CLAIM_PUSH_TOKEN_FILE),
674
+ consentVersion: marker?.version,
675
+ bountyId,
676
+ claimId,
677
+ lastBeatAt: readHeartbeatState()[claimId] ?? null,
678
+ now
679
+ });
680
+ if (!gate.beat) return { beat: false, reason: gate.reason };
681
+ const token = await readPushTokenEnc();
682
+ if (!token) return { beat: false, reason: "unreadable-token" };
683
+ const res = await fetch(`${CLAIM_SYNC_BASE}/api/claim/heartbeat`, {
684
+ method: "POST",
685
+ headers: { "Content-Type": "application/json" },
686
+ body: JSON.stringify({ bountyId, claimId, pushToken: token }),
687
+ signal: AbortSignal.timeout(5e3)
688
+ });
689
+ if (!res.ok) return { beat: false, reason: `server-${res.status}` };
690
+ recordHeartbeatBeat(claimId, new Date(now).toISOString());
691
+ return { beat: true, reason: "ok" };
692
+ } catch {
693
+ return { beat: false, reason: "failed" };
694
+ }
695
+ }
620
696
  async function runBackgroundClaimPush({ now = Date.now() } = {}) {
621
697
  try {
622
698
  if (!existsSync5(CLAIM_PUSH_AUTO_MARKER) || !existsSync5(CLAIM_PUSH_TOKEN_FILE)) {
@@ -666,15 +742,22 @@ async function runBackgroundClaimPush({ now = Date.now() } = {}) {
666
742
  export {
667
743
  AUTO_CONSENT_VERSION,
668
744
  AUTO_PUSH_THROTTLE_MS,
745
+ CLAIM_HEARTBEAT_FILE,
669
746
  CLAIM_PUSH_AUTO_MARKER,
670
747
  CLAIM_PUSH_MANUAL_MARKER,
671
748
  CLAIM_PUSH_TOKEN_FILE,
749
+ HEARTBEAT_MIN_CONSENT_VERSION,
750
+ HEARTBEAT_MIN_INTERVAL_MS,
672
751
  backgroundPushGate,
673
752
  clearAutoMarker,
674
753
  clearPushTokenEnc,
675
754
  computeSnapshotHash,
755
+ heartbeatGate,
756
+ postClaimHeartbeat,
676
757
  readAutoMarker,
758
+ readHeartbeatState,
677
759
  readPushTokenEnc,
760
+ recordHeartbeatBeat,
678
761
  runBackgroundClaimPush,
679
762
  shouldNudgeUnpushed,
680
763
  unpushedNudgeGate,
@@ -0,0 +1,117 @@
1
+ // bin/founder-bounty-notify.js
2
+ import { spawn } from "child_process";
3
+
4
+ // bin/founder-pin.js
5
+ function isPinnedFounderBounty(j) {
6
+ return j?.bounty?.bountySource === "founder" && j?.bounty?.claimable === true;
7
+ }
8
+
9
+ // bin/founder-paid-badge.js
10
+ function openPaidIds(index) {
11
+ const jobs = index && index.jobs || [];
12
+ const ids = [];
13
+ for (const j of jobs) {
14
+ if (!j || typeof j.id !== "string") continue;
15
+ if (isPinnedFounderBounty(j)) ids.push(j.id);
16
+ }
17
+ return [...new Set(ids)].sort();
18
+ }
19
+
20
+ // bin/founder-bounty-notify.js
21
+ function nextFounderBountyNotifyState(index, previous) {
22
+ const open = openPaidIds(index);
23
+ const prior = previous && Array.isArray(previous.ids) ? previous.ids : null;
24
+ if (prior === null) {
25
+ return { ids: open, fire: [], seeded: true };
26
+ }
27
+ const seen = new Set(prior);
28
+ const fire = open.filter((id) => !seen.has(id));
29
+ const ids = [.../* @__PURE__ */ new Set([...prior.filter((id) => open.includes(id)), ...fire])].sort();
30
+ return { ids, fire, seeded: false };
31
+ }
32
+ function formatFounderBountyNotifyBody(index, fireIds) {
33
+ const jobs = index && index.jobs || [];
34
+ const byId = /* @__PURE__ */ new Map();
35
+ for (const j of jobs) {
36
+ if (j && typeof j.id === "string") byId.set(j.id, j);
37
+ }
38
+ if (fireIds.length === 1) {
39
+ const j = byId.get(fireIds[0]);
40
+ const amount = j && j.bounty && typeof j.bounty.amountUSD === "number" ? j.bounty.amountUSD : null;
41
+ const price = typeof amount === "number" && Number.isFinite(amount) && amount > 0 ? `$${Math.round(amount)} ` : "";
42
+ return `${price}founder bounty available \u2014 run: terminalhire bounties`;
43
+ }
44
+ return `${fireIds.length} founder bounties available \u2014 run: terminalhire bounties`;
45
+ }
46
+ function escapeAppleScriptString(s) {
47
+ return String(s).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
48
+ }
49
+ function displayLocalNotification({
50
+ title = "Terminalhire",
51
+ body,
52
+ platform = process.platform,
53
+ spawnFn = spawn
54
+ } = {}) {
55
+ if (!body || typeof body !== "string") return false;
56
+ try {
57
+ if (platform === "darwin") {
58
+ const t = escapeAppleScriptString(title);
59
+ const b = escapeAppleScriptString(body);
60
+ const child = spawnFn("osascript", ["-e", `display notification "${b}" with title "${t}"`], {
61
+ detached: true,
62
+ stdio: "ignore"
63
+ });
64
+ if (child && typeof child.unref === "function") child.unref();
65
+ return true;
66
+ }
67
+ if (platform === "win32") {
68
+ const safeTitle = String(title).replace(/'/g, "''");
69
+ const safeBody = String(body).replace(/'/g, "''");
70
+ const ps = `
71
+ Add-Type -AssemblyName System.Windows.Forms
72
+ $n = New-Object System.Windows.Forms.NotifyIcon
73
+ $n.Icon = [System.Drawing.SystemIcons]::Information
74
+ $n.Visible = $true
75
+ $n.ShowBalloonTip(8000, '${safeTitle}', '${safeBody}', [System.Windows.Forms.ToolTipIcon]::Info)
76
+ Start-Sleep -Seconds 9
77
+ $n.Dispose()
78
+ `.trim();
79
+ const child = spawnFn("powershell.exe", ["-NoProfile", "-Command", ps], {
80
+ detached: true,
81
+ stdio: "ignore",
82
+ windowsHide: true
83
+ });
84
+ if (child && typeof child.unref === "function") child.unref();
85
+ return true;
86
+ }
87
+ return false;
88
+ } catch {
89
+ return false;
90
+ }
91
+ }
92
+ function maybeNotifyFounderBounties({
93
+ index,
94
+ previous,
95
+ optedIn,
96
+ display = displayLocalNotification
97
+ } = {}) {
98
+ if (!optedIn) {
99
+ const ids = previous && Array.isArray(previous.ids) ? [...previous.ids].sort() : [];
100
+ return { state: { ids }, fired: [] };
101
+ }
102
+ const next = nextFounderBountyNotifyState(index, previous);
103
+ if (next.fire.length > 0) {
104
+ display({
105
+ title: "Terminalhire",
106
+ body: formatFounderBountyNotifyBody(index, next.fire)
107
+ });
108
+ }
109
+ return { state: { ids: next.ids }, fired: next.fire };
110
+ }
111
+ export {
112
+ displayLocalNotification,
113
+ escapeAppleScriptString,
114
+ formatFounderBountyNotifyBody,
115
+ maybeNotifyFounderBounties,
116
+ nextFounderBountyNotifyState
117
+ };
@@ -11546,6 +11546,7 @@ __export(config_exports, {
11546
11546
  getSurfaceMix: () => getSurfaceMix,
11547
11547
  isBetaOptIn: () => isBetaOptIn,
11548
11548
  isContributeEnabled: () => isContributeEnabled,
11549
+ isFounderBountyNotifyEnabled: () => isFounderBountyNotifyEnabled,
11549
11550
  isInboundNudgeMuted: () => isInboundNudgeMuted,
11550
11551
  isPeerConnectEnabled: () => isPeerConnectEnabled,
11551
11552
  parseNudgeMode: () => parseNudgeMode,
@@ -11630,6 +11631,9 @@ function isContributeEnabled() {
11630
11631
  function isBetaOptIn() {
11631
11632
  return readConfig().betaOptIn === true;
11632
11633
  }
11634
+ function isFounderBountyNotifyEnabled() {
11635
+ return readConfig().founderBountyNotify === true;
11636
+ }
11633
11637
  var TERMINALHIRE_DIR6, CONFIG_FILE, DEFAULT_CONFIG;
11634
11638
  var init_config = __esm({
11635
11639
  "src/config.ts"() {
@@ -11651,7 +11655,8 @@ var init_config = __esm({
11651
11655
  lastFullFeedbackAt: null,
11652
11656
  lastPulseAskAt: null,
11653
11657
  pulseDisclosed: false,
11654
- mix: "balanced"
11658
+ mix: "balanced",
11659
+ founderBountyNotify: false
11655
11660
  };
11656
11661
  }
11657
11662
  });
@@ -4911,7 +4911,8 @@ var init_config = __esm({
4911
4911
  lastFullFeedbackAt: null,
4912
4912
  lastPulseAskAt: null,
4913
4913
  pulseDisclosed: false,
4914
- mix: "balanced"
4914
+ mix: "balanced",
4915
+ founderBountyNotify: false
4915
4916
  };
4916
4917
  }
4917
4918
  });
@@ -4940,7 +4940,8 @@ var init_config = __esm({
4940
4940
  lastFullFeedbackAt: null,
4941
4941
  lastPulseAskAt: null,
4942
4942
  pulseDisclosed: false,
4943
- mix: "balanced"
4943
+ mix: "balanced",
4944
+ founderBountyNotify: false
4944
4945
  };
4945
4946
  }
4946
4947
  });