terminalhire 0.10.11 → 0.11.1

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.
@@ -5840,6 +5840,53 @@ var init_chatCrypto = __esm({
5840
5840
  }
5841
5841
  });
5842
5842
 
5843
+ // ../../packages/core/src/job-status.ts
5844
+ function recordClick(map, id) {
5845
+ const prev = map[id];
5846
+ if (prev?.clicked === true) return map;
5847
+ return { ...map, [id]: { ...prev, clicked: true } };
5848
+ }
5849
+ function setStatus(map, id, s, markedAt = (/* @__PURE__ */ new Date()).toISOString()) {
5850
+ const prev = map[id];
5851
+ return { ...map, [id]: { ...prev, status: s, markedAt } };
5852
+ }
5853
+ function funnelCounts(map) {
5854
+ const counts = { clicked: 0, applied: 0, saved: 0, dismissed: 0 };
5855
+ for (const key of Object.keys(map)) {
5856
+ const rec = map[key];
5857
+ if (rec.clicked === true) counts.clicked++;
5858
+ if (rec.status) counts[rec.status]++;
5859
+ }
5860
+ return counts;
5861
+ }
5862
+ function pageMatches(items, page, limit) {
5863
+ const lim = Math.max(1, Math.floor(limit));
5864
+ const total = items.length;
5865
+ const totalPages = Math.max(1, Math.ceil(total / lim));
5866
+ const clamped = Math.min(Math.max(1, Math.floor(page)), totalPages);
5867
+ const start = (clamped - 1) * lim;
5868
+ return {
5869
+ items: items.slice(start, start + lim),
5870
+ page: clamped,
5871
+ limit: lim,
5872
+ total,
5873
+ totalPages,
5874
+ hasPrev: clamped > 1,
5875
+ hasNext: clamped < totalPages
5876
+ };
5877
+ }
5878
+ function decorate(matches, statusMap) {
5879
+ return matches.map((m) => {
5880
+ const rec = statusMap[m.job.id];
5881
+ return rec ? { ...m, jobStatus: rec } : { ...m };
5882
+ });
5883
+ }
5884
+ var init_job_status = __esm({
5885
+ "../../packages/core/src/job-status.ts"() {
5886
+ "use strict";
5887
+ }
5888
+ });
5889
+
5843
5890
  // ../../packages/core/src/index.ts
5844
5891
  var src_exports = {};
5845
5892
  __export(src_exports, {
@@ -5881,6 +5928,7 @@ __export(src_exports, {
5881
5928
  computeAcceptanceCredential: () => computeAcceptanceCredential,
5882
5929
  computeAcceptanceCredentialPublic: () => computeAcceptanceCredentialPublic,
5883
5930
  coreTagsFromTitle: () => coreTagsFromTitle,
5931
+ decorate: () => decorate,
5884
5932
  decryptMessage: () => decryptMessage,
5885
5933
  deriveResumeTrend: () => deriveResumeTrend,
5886
5934
  deriveSharedKey: () => deriveSharedKey,
@@ -5891,6 +5939,7 @@ __export(src_exports, {
5891
5939
  fetchOwnedRepoTraction: () => fetchOwnedRepoTraction,
5892
5940
  fetchRepoRecency: () => fetchRepoRecency,
5893
5941
  flattenTiers: () => flattenTiers,
5942
+ funnelCounts: () => funnelCounts,
5894
5943
  generateIdentityKeypair: () => generateIdentityKeypair,
5895
5944
  getBuyer: () => getBuyer,
5896
5945
  githubBounties: () => githubBounties,
@@ -5909,12 +5958,15 @@ __export(src_exports, {
5909
5958
  match: () => match,
5910
5959
  normalize: () => normalize,
5911
5960
  opire: () => opire,
5961
+ pageMatches: () => pageMatches,
5912
5962
  passesMaturityGate: () => passesMaturityGate,
5913
5963
  personCardToJob: () => personCardToJob,
5914
5964
  projectCardToJob: () => projectCardToJob,
5965
+ recordClick: () => recordClick,
5915
5966
  rejectExtraIntroFields: () => rejectExtraIntroFields,
5916
5967
  revealIntroContacts: () => revealIntroContacts,
5917
5968
  safetyNumber: () => safetyNumber,
5969
+ setStatus: () => setStatus,
5918
5970
  tokenize: () => tokenize,
5919
5971
  validateGraph: () => validateGraph,
5920
5972
  validateIntroPayload: () => validateIntroPayload,
@@ -5935,6 +5987,7 @@ var init_src = __esm({
5935
5987
  init_intro();
5936
5988
  init_directoryThreshold();
5937
5989
  init_chatCrypto();
5990
+ init_job_status();
5938
5991
  }
5939
5992
  });
5940
5993
 
@@ -3749,6 +3749,13 @@ var init_chatCrypto = __esm({
3749
3749
  }
3750
3750
  });
3751
3751
 
3752
+ // ../../packages/core/src/job-status.ts
3753
+ var init_job_status = __esm({
3754
+ "../../packages/core/src/job-status.ts"() {
3755
+ "use strict";
3756
+ }
3757
+ });
3758
+
3752
3759
  // ../../packages/core/src/index.ts
3753
3760
  var init_src = __esm({
3754
3761
  "../../packages/core/src/index.ts"() {
@@ -3763,6 +3770,7 @@ var init_src = __esm({
3763
3770
  init_intro();
3764
3771
  init_directoryThreshold();
3765
3772
  init_chatCrypto();
3773
+ init_job_status();
3766
3774
  }
3767
3775
  });
3768
3776
 
@@ -4270,6 +4278,35 @@ async function defaultListPendingInvites(deps = {}) {
4270
4278
  const invites = listed.intros.filter((it) => it && it.role === "incoming" && it.status === "pending" && it.counterpartyLogin).map((it) => ({ login: it.counterpartyLogin }));
4271
4279
  return { status: "ok", invites };
4272
4280
  }
4281
+ function relativeTime(then, now = /* @__PURE__ */ new Date()) {
4282
+ const t = new Date(then).getTime();
4283
+ if (Number.isNaN(t)) return "";
4284
+ const deltaMs = Math.max(0, now.getTime() - t);
4285
+ const sec = Math.floor(deltaMs / 1e3);
4286
+ if (sec < 60) return "just now";
4287
+ const min = Math.floor(sec / 60);
4288
+ if (min < 60) return `${Math.max(1, min)}m ago`;
4289
+ const hr = Math.floor(min / 60);
4290
+ if (hr < 24) return `${hr}h ago`;
4291
+ const day = Math.floor(hr / 24);
4292
+ if (day < 7) return `${day}d ago`;
4293
+ const wk = Math.floor(day / 7);
4294
+ if (wk < 5) return `${wk}w ago`;
4295
+ return "a while ago";
4296
+ }
4297
+ function formatPresence(presence, now = /* @__PURE__ */ new Date()) {
4298
+ if (!presence) return "\u25CB not on chat yet";
4299
+ const share = presence.shareActivity === true;
4300
+ const seenMs = share && presence.lastSeen ? new Date(presence.lastSeen).getTime() : NaN;
4301
+ const hasSeen = !Number.isNaN(seenMs);
4302
+ const fresh = hasSeen && now.getTime() - seenMs <= ACTIVE_WINDOW_MS;
4303
+ if (share && presence.optin === true && fresh) return "\u25CF active now";
4304
+ if (share && hasSeen) {
4305
+ const rel = relativeTime(presence.lastSeen, now);
4306
+ return rel ? `\u25D0 reachable \xB7 seen ${rel}` : "\u25D0 reachable";
4307
+ }
4308
+ return "\u25D0 reachable";
4309
+ }
4273
4310
  var CHAT_BASE2, GH_SESSION_COOKIE2, ANSI_CSI, ANSI_OSC, ANSI_OTHER, C0_C1_DEL, CHAT_DISCLOSURE, CHAT_AT_REST, CHAT_CODE_OF_CONDUCT, CHAT_MIN_AGE, ACTIVE_WINDOW_MS;
4274
4311
  var init_jpi_chat = __esm({
4275
4312
  "bin/jpi-chat.js"() {
@@ -4396,7 +4433,7 @@ function renderInbox(items, invites = []) {
4396
4433
  lines.push(" (no accepted connections yet \u2014 request one: terminalhire intro <login>)");
4397
4434
  } else {
4398
4435
  for (const it of items) {
4399
- const dot = it.online ? "\u25CF" : "\u25CB";
4436
+ const dot = formatPresence(it.presence).charAt(0);
4400
4437
  const unread = it.unread > 0 ? `\u2709 ${it.unread}` : "\u2014";
4401
4438
  const login = `@${sanitizeLine(it.login)}`;
4402
4439
  const stamp = sanitizeLine(it.lastStamp || "");
@@ -4411,13 +4448,12 @@ function renderInbox(items, invites = []) {
4411
4448
  return lines.join("\n") + "\n";
4412
4449
  }
4413
4450
  function renderThread(state) {
4414
- const { peerLogin, online, safety, messages, total } = state;
4451
+ const { peerLogin, presence, safety, messages, total } = state;
4415
4452
  const safePeer = sanitizeLine(peerLogin);
4416
- const dot = online ? "\u25CF" : "\u25CB";
4417
- const presence = online ? "online" : "offline";
4453
+ const status = formatPresence(presence);
4418
4454
  const lines = [];
4419
4455
  lines.push(
4420
- ` @${safePeer} ${dot} ${presence}` + (safety ? ` \xB7 safety# ${sanitizeLine(safety)}` : "")
4456
+ ` @${safePeer} ${status}` + (safety ? ` \xB7 safety# ${sanitizeLine(safety)}` : "")
4421
4457
  );
4422
4458
  lines.push(" " + "\u2500".repeat(64));
4423
4459
  if (!messages || messages.length === 0) {
@@ -4467,21 +4503,6 @@ function writeProblem(output, result, target) {
4467
4503
  return "error";
4468
4504
  }
4469
4505
  }
4470
- async function readPresence(client, peerLogin) {
4471
- if (typeof client.getPeerPresence !== "function") return false;
4472
- try {
4473
- return await client.getPeerPresence(peerLogin) != null;
4474
- } catch {
4475
- return false;
4476
- }
4477
- }
4478
- async function clearPresence(client) {
4479
- if (typeof client.heartbeat !== "function") return;
4480
- try {
4481
- await client.heartbeat(false);
4482
- } catch {
4483
- }
4484
- }
4485
4506
  async function gateDisclosure(ensureDisclosure, input, output) {
4486
4507
  const ack = await ensureDisclosure({ input, output });
4487
4508
  if (!ack.acknowledged) {
@@ -4529,16 +4550,14 @@ async function runInbox(opts = {}) {
4529
4550
  (m) => m.senderLogin === conn.peerLogin && (!cursor || m.createdAt > cursor)
4530
4551
  ).length;
4531
4552
  const last = messages.length > 0 ? messages[messages.length - 1] : null;
4532
- const online = await readPresence(client, conn.peerLogin);
4533
4553
  items.push({
4534
4554
  login: conn.peerLogin,
4535
- online,
4555
+ presence: REACHABLE_DISPLAY,
4536
4556
  unread,
4537
4557
  lastStamp: last ? formatStamp(last.createdAt) : "",
4538
4558
  preview: last ? last.plaintext : ""
4539
4559
  });
4540
4560
  }
4541
- await clearPresence(client);
4542
4561
  output.write(renderInbox(items, invites));
4543
4562
  return { ok: true, count: items.length, invites: invites.length };
4544
4563
  }
@@ -4581,7 +4600,6 @@ async function runReadThread(opts = {}) {
4581
4600
  }
4582
4601
  const total = messages.length;
4583
4602
  const shownMessages = all ? messages : messages.slice(Math.max(0, total - limit));
4584
- const online = await readPresence(client, peerLogin);
4585
4603
  let safety = "";
4586
4604
  if (typeof client.getSafetyNumber === "function") {
4587
4605
  try {
@@ -4590,8 +4608,7 @@ async function runReadThread(opts = {}) {
4590
4608
  safety = "";
4591
4609
  }
4592
4610
  }
4593
- await clearPresence(client);
4594
- output.write(renderThread({ peerLogin, online, safety, messages: shownMessages, total }));
4611
+ output.write(renderThread({ peerLogin, presence: REACHABLE_DISPLAY, safety, messages: shownMessages, total }));
4595
4612
  if (total > 0) {
4596
4613
  const newest = messages[total - 1];
4597
4614
  if (newest && newest.createdAt) {
@@ -4664,7 +4681,7 @@ async function runSend(opts = {}) {
4664
4681
  );
4665
4682
  return { ok: true };
4666
4683
  }
4667
- var CHAT_BASE3, GH_SESSION_COOKIE3, TERMINALHIRE_DIR5, READS_FILE, INDEX_CACHE_FILE;
4684
+ var CHAT_BASE3, GH_SESSION_COOKIE3, TERMINALHIRE_DIR5, READS_FILE, INDEX_CACHE_FILE, REACHABLE_DISPLAY;
4668
4685
  var init_jpi_chat_read = __esm({
4669
4686
  "bin/jpi-chat-read.js"() {
4670
4687
  init_chat_client();
@@ -4675,6 +4692,7 @@ var init_jpi_chat_read = __esm({
4675
4692
  TERMINALHIRE_DIR5 = join8(homedir7(), ".terminalhire");
4676
4693
  READS_FILE = join8(TERMINALHIRE_DIR5, "chat-reads.json");
4677
4694
  INDEX_CACHE_FILE = join8(TERMINALHIRE_DIR5, "index-cache.json");
4695
+ REACHABLE_DISPLAY = { shareActivity: false, optin: false, lastSeen: null };
4678
4696
  }
4679
4697
  });
4680
4698
  init_jpi_chat_read();
@@ -3767,6 +3767,13 @@ var init_chatCrypto = __esm({
3767
3767
  }
3768
3768
  });
3769
3769
 
3770
+ // ../../packages/core/src/job-status.ts
3771
+ var init_job_status = __esm({
3772
+ "../../packages/core/src/job-status.ts"() {
3773
+ "use strict";
3774
+ }
3775
+ });
3776
+
3770
3777
  // ../../packages/core/src/index.ts
3771
3778
  var init_src = __esm({
3772
3779
  "../../packages/core/src/index.ts"() {
@@ -3781,6 +3788,7 @@ var init_src = __esm({
3781
3788
  init_intro();
3782
3789
  init_directoryThreshold();
3783
3790
  init_chatCrypto();
3791
+ init_job_status();
3784
3792
  }
3785
3793
  });
3786
3794
 
@@ -4550,7 +4558,7 @@ function renderInbox(items, invites = []) {
4550
4558
  lines.push(" (no accepted connections yet \u2014 request one: terminalhire intro <login>)");
4551
4559
  } else {
4552
4560
  for (const it of items) {
4553
- const dot = it.online ? "\u25CF" : "\u25CB";
4561
+ const dot = formatPresence(it.presence).charAt(0);
4554
4562
  const unread = it.unread > 0 ? `\u2709 ${it.unread}` : "\u2014";
4555
4563
  const login = `@${sanitizeLine(it.login)}`;
4556
4564
  const stamp = sanitizeLine(it.lastStamp || "");
@@ -4565,13 +4573,12 @@ function renderInbox(items, invites = []) {
4565
4573
  return lines.join("\n") + "\n";
4566
4574
  }
4567
4575
  function renderThread(state) {
4568
- const { peerLogin, online, safety, messages, total } = state;
4576
+ const { peerLogin, presence, safety, messages, total } = state;
4569
4577
  const safePeer = sanitizeLine(peerLogin);
4570
- const dot = online ? "\u25CF" : "\u25CB";
4571
- const presence = online ? "online" : "offline";
4578
+ const status = formatPresence(presence);
4572
4579
  const lines = [];
4573
4580
  lines.push(
4574
- ` @${safePeer} ${dot} ${presence}` + (safety ? ` \xB7 safety# ${sanitizeLine(safety)}` : "")
4581
+ ` @${safePeer} ${status}` + (safety ? ` \xB7 safety# ${sanitizeLine(safety)}` : "")
4575
4582
  );
4576
4583
  lines.push(" " + "\u2500".repeat(64));
4577
4584
  if (!messages || messages.length === 0) {
@@ -4621,21 +4628,6 @@ function writeProblem(output, result, target) {
4621
4628
  return "error";
4622
4629
  }
4623
4630
  }
4624
- async function readPresence(client, peerLogin) {
4625
- if (typeof client.getPeerPresence !== "function") return false;
4626
- try {
4627
- return await client.getPeerPresence(peerLogin) != null;
4628
- } catch {
4629
- return false;
4630
- }
4631
- }
4632
- async function clearPresence(client) {
4633
- if (typeof client.heartbeat !== "function") return;
4634
- try {
4635
- await client.heartbeat(false);
4636
- } catch {
4637
- }
4638
- }
4639
4631
  async function gateDisclosure(ensureDisclosure, input, output) {
4640
4632
  const ack = await ensureDisclosure({ input, output });
4641
4633
  if (!ack.acknowledged) {
@@ -4683,16 +4675,14 @@ async function runInbox(opts = {}) {
4683
4675
  (m) => m.senderLogin === conn.peerLogin && (!cursor || m.createdAt > cursor)
4684
4676
  ).length;
4685
4677
  const last = messages.length > 0 ? messages[messages.length - 1] : null;
4686
- const online = await readPresence(client, conn.peerLogin);
4687
4678
  items.push({
4688
4679
  login: conn.peerLogin,
4689
- online,
4680
+ presence: REACHABLE_DISPLAY,
4690
4681
  unread,
4691
4682
  lastStamp: last ? formatStamp(last.createdAt) : "",
4692
4683
  preview: last ? last.plaintext : ""
4693
4684
  });
4694
4685
  }
4695
- await clearPresence(client);
4696
4686
  output.write(renderInbox(items, invites));
4697
4687
  return { ok: true, count: items.length, invites: invites.length };
4698
4688
  }
@@ -4735,7 +4725,6 @@ async function runReadThread(opts = {}) {
4735
4725
  }
4736
4726
  const total = messages.length;
4737
4727
  const shownMessages = all ? messages : messages.slice(Math.max(0, total - limit));
4738
- const online = await readPresence(client, peerLogin);
4739
4728
  let safety = "";
4740
4729
  if (typeof client.getSafetyNumber === "function") {
4741
4730
  try {
@@ -4744,8 +4733,7 @@ async function runReadThread(opts = {}) {
4744
4733
  safety = "";
4745
4734
  }
4746
4735
  }
4747
- await clearPresence(client);
4748
- output.write(renderThread({ peerLogin, online, safety, messages: shownMessages, total }));
4736
+ output.write(renderThread({ peerLogin, presence: REACHABLE_DISPLAY, safety, messages: shownMessages, total }));
4749
4737
  if (total > 0) {
4750
4738
  const newest = messages[total - 1];
4751
4739
  if (newest && newest.createdAt) {
@@ -4818,7 +4806,7 @@ async function runSend(opts = {}) {
4818
4806
  );
4819
4807
  return { ok: true };
4820
4808
  }
4821
- var CHAT_BASE2, GH_SESSION_COOKIE2, TERMINALHIRE_DIR6, READS_FILE, INDEX_CACHE_FILE;
4809
+ var CHAT_BASE2, GH_SESSION_COOKIE2, TERMINALHIRE_DIR6, READS_FILE, INDEX_CACHE_FILE, REACHABLE_DISPLAY;
4822
4810
  var init_jpi_chat_read = __esm({
4823
4811
  "bin/jpi-chat-read.js"() {
4824
4812
  "use strict";
@@ -4830,6 +4818,7 @@ var init_jpi_chat_read = __esm({
4830
4818
  TERMINALHIRE_DIR6 = join8(homedir7(), ".terminalhire");
4831
4819
  READS_FILE = join8(TERMINALHIRE_DIR6, "chat-reads.json");
4832
4820
  INDEX_CACHE_FILE = join8(TERMINALHIRE_DIR6, "index-cache.json");
4821
+ REACHABLE_DISPLAY = { shareActivity: false, optin: false, lastSeen: null };
4833
4822
  }
4834
4823
  });
4835
4824
 
@@ -5840,6 +5840,53 @@ var init_chatCrypto = __esm({
5840
5840
  }
5841
5841
  });
5842
5842
 
5843
+ // ../../packages/core/src/job-status.ts
5844
+ function recordClick(map, id) {
5845
+ const prev = map[id];
5846
+ if (prev?.clicked === true) return map;
5847
+ return { ...map, [id]: { ...prev, clicked: true } };
5848
+ }
5849
+ function setStatus(map, id, s, markedAt = (/* @__PURE__ */ new Date()).toISOString()) {
5850
+ const prev = map[id];
5851
+ return { ...map, [id]: { ...prev, status: s, markedAt } };
5852
+ }
5853
+ function funnelCounts(map) {
5854
+ const counts = { clicked: 0, applied: 0, saved: 0, dismissed: 0 };
5855
+ for (const key of Object.keys(map)) {
5856
+ const rec = map[key];
5857
+ if (rec.clicked === true) counts.clicked++;
5858
+ if (rec.status) counts[rec.status]++;
5859
+ }
5860
+ return counts;
5861
+ }
5862
+ function pageMatches(items, page, limit) {
5863
+ const lim = Math.max(1, Math.floor(limit));
5864
+ const total = items.length;
5865
+ const totalPages = Math.max(1, Math.ceil(total / lim));
5866
+ const clamped = Math.min(Math.max(1, Math.floor(page)), totalPages);
5867
+ const start = (clamped - 1) * lim;
5868
+ return {
5869
+ items: items.slice(start, start + lim),
5870
+ page: clamped,
5871
+ limit: lim,
5872
+ total,
5873
+ totalPages,
5874
+ hasPrev: clamped > 1,
5875
+ hasNext: clamped < totalPages
5876
+ };
5877
+ }
5878
+ function decorate(matches, statusMap) {
5879
+ return matches.map((m) => {
5880
+ const rec = statusMap[m.job.id];
5881
+ return rec ? { ...m, jobStatus: rec } : { ...m };
5882
+ });
5883
+ }
5884
+ var init_job_status = __esm({
5885
+ "../../packages/core/src/job-status.ts"() {
5886
+ "use strict";
5887
+ }
5888
+ });
5889
+
5843
5890
  // ../../packages/core/src/index.ts
5844
5891
  var src_exports = {};
5845
5892
  __export(src_exports, {
@@ -5881,6 +5928,7 @@ __export(src_exports, {
5881
5928
  computeAcceptanceCredential: () => computeAcceptanceCredential,
5882
5929
  computeAcceptanceCredentialPublic: () => computeAcceptanceCredentialPublic,
5883
5930
  coreTagsFromTitle: () => coreTagsFromTitle,
5931
+ decorate: () => decorate,
5884
5932
  decryptMessage: () => decryptMessage,
5885
5933
  deriveResumeTrend: () => deriveResumeTrend,
5886
5934
  deriveSharedKey: () => deriveSharedKey,
@@ -5891,6 +5939,7 @@ __export(src_exports, {
5891
5939
  fetchOwnedRepoTraction: () => fetchOwnedRepoTraction,
5892
5940
  fetchRepoRecency: () => fetchRepoRecency,
5893
5941
  flattenTiers: () => flattenTiers,
5942
+ funnelCounts: () => funnelCounts,
5894
5943
  generateIdentityKeypair: () => generateIdentityKeypair,
5895
5944
  getBuyer: () => getBuyer,
5896
5945
  githubBounties: () => githubBounties,
@@ -5909,12 +5958,15 @@ __export(src_exports, {
5909
5958
  match: () => match,
5910
5959
  normalize: () => normalize,
5911
5960
  opire: () => opire,
5961
+ pageMatches: () => pageMatches,
5912
5962
  passesMaturityGate: () => passesMaturityGate,
5913
5963
  personCardToJob: () => personCardToJob,
5914
5964
  projectCardToJob: () => projectCardToJob,
5965
+ recordClick: () => recordClick,
5915
5966
  rejectExtraIntroFields: () => rejectExtraIntroFields,
5916
5967
  revealIntroContacts: () => revealIntroContacts,
5917
5968
  safetyNumber: () => safetyNumber,
5969
+ setStatus: () => setStatus,
5918
5970
  tokenize: () => tokenize,
5919
5971
  validateGraph: () => validateGraph,
5920
5972
  validateIntroPayload: () => validateIntroPayload,
@@ -5935,6 +5987,7 @@ var init_src = __esm({
5935
5987
  init_intro();
5936
5988
  init_directoryThreshold();
5937
5989
  init_chatCrypto();
5990
+ init_job_status();
5938
5991
  }
5939
5992
  });
5940
5993