terminalhire 0.10.3 → 0.10.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.
@@ -4290,6 +4290,22 @@ function writeReadCursor(login, iso, deps = {}) {
4290
4290
  mkdirSync6(TERMINALHIRE_DIR5, { recursive: true });
4291
4291
  writeFileSync6(READS_FILE, JSON.stringify(cursors, null, 2), { mode: 384, encoding: "utf8" });
4292
4292
  }
4293
+ async function postReadCursor(peerLogin, lastReadAt, deps = {}) {
4294
+ const readCookie = deps.readCookie ?? readWebSessionCookie;
4295
+ const cookie = readCookie();
4296
+ if (!cookie) return;
4297
+ try {
4298
+ await fetch(`${CHAT_BASE3}/api/chat/read-cursor`, {
4299
+ method: "POST",
4300
+ headers: { "Content-Type": "application/json", Cookie: `${GH_SESSION_COOKIE3}=${cookie}` },
4301
+ body: JSON.stringify({ peerLogin, lastReadAt }),
4302
+ // Best-effort cross-device sync — not latency-sensitive, so a short bound
4303
+ // keeps a cold/unreachable server from stalling the reader's exit.
4304
+ signal: AbortSignal.timeout(2500)
4305
+ });
4306
+ } catch {
4307
+ }
4308
+ }
4293
4309
  function formatClock(iso) {
4294
4310
  const d = new Date(iso);
4295
4311
  if (Number.isNaN(d.getTime())) return "--:--";
@@ -4469,6 +4485,7 @@ async function runReadThread(opts = {}) {
4469
4485
  client = createChatClient(),
4470
4486
  resolveConnection = defaultResolveConnection,
4471
4487
  writeCursor = writeReadCursor,
4488
+ syncCursor = postReadCursor,
4472
4489
  ensureDisclosure = ensureChatDisclosure
4473
4490
  } = opts;
4474
4491
  const target = String(login ?? "").replace(/^@/, "").trim();
@@ -4514,6 +4531,7 @@ async function runReadThread(opts = {}) {
4514
4531
  writeCursor(peerLogin, newest.createdAt);
4515
4532
  } catch {
4516
4533
  }
4534
+ await syncCursor(peerLogin, newest.createdAt);
4517
4535
  }
4518
4536
  }
4519
4537
  return { ok: true, shown: shownMessages.length, total };
@@ -4561,12 +4579,14 @@ async function runSend(opts = {}) {
4561
4579
  );
4562
4580
  return { ok: true };
4563
4581
  }
4564
- var CHAT_BASE3, TERMINALHIRE_DIR5, READS_FILE;
4582
+ var CHAT_BASE3, GH_SESSION_COOKIE3, TERMINALHIRE_DIR5, READS_FILE;
4565
4583
  var init_jpi_chat_read = __esm({
4566
4584
  "bin/jpi-chat-read.js"() {
4567
4585
  init_chat_client();
4586
+ init_web_session();
4568
4587
  init_jpi_chat();
4569
4588
  CHAT_BASE3 = process.env["TERMINALHIRE_API_URL"] || "https://www.terminalhire.com";
4589
+ GH_SESSION_COOKIE3 = "__jpi_gh_session";
4570
4590
  TERMINALHIRE_DIR5 = join7(homedir6(), ".terminalhire");
4571
4591
  READS_FILE = join7(TERMINALHIRE_DIR5, "chat-reads.json");
4572
4592
  }
@@ -4575,6 +4595,7 @@ init_jpi_chat_read();
4575
4595
  export {
4576
4596
  formatClock,
4577
4597
  formatStamp,
4598
+ postReadCursor,
4578
4599
  readReadCursors,
4579
4600
  renderInbox,
4580
4601
  renderThread,
@@ -4167,6 +4167,7 @@ var jpi_chat_read_exports = {};
4167
4167
  __export(jpi_chat_read_exports, {
4168
4168
  formatClock: () => formatClock,
4169
4169
  formatStamp: () => formatStamp,
4170
+ postReadCursor: () => postReadCursor,
4170
4171
  readReadCursors: () => readReadCursors,
4171
4172
  renderInbox: () => renderInbox,
4172
4173
  renderThread: () => renderThread,
@@ -4199,6 +4200,22 @@ function writeReadCursor(login, iso, deps = {}) {
4199
4200
  mkdirSync6(TERMINALHIRE_DIR5, { recursive: true });
4200
4201
  writeFileSync6(READS_FILE, JSON.stringify(cursors, null, 2), { mode: 384, encoding: "utf8" });
4201
4202
  }
4203
+ async function postReadCursor(peerLogin, lastReadAt, deps = {}) {
4204
+ const readCookie = deps.readCookie ?? readWebSessionCookie;
4205
+ const cookie = readCookie();
4206
+ if (!cookie) return;
4207
+ try {
4208
+ await fetch(`${CHAT_BASE2}/api/chat/read-cursor`, {
4209
+ method: "POST",
4210
+ headers: { "Content-Type": "application/json", Cookie: `${GH_SESSION_COOKIE2}=${cookie}` },
4211
+ body: JSON.stringify({ peerLogin, lastReadAt }),
4212
+ // Best-effort cross-device sync — not latency-sensitive, so a short bound
4213
+ // keeps a cold/unreachable server from stalling the reader's exit.
4214
+ signal: AbortSignal.timeout(2500)
4215
+ });
4216
+ } catch {
4217
+ }
4218
+ }
4202
4219
  function formatClock(iso) {
4203
4220
  const d = new Date(iso);
4204
4221
  if (Number.isNaN(d.getTime())) return "--:--";
@@ -4378,6 +4395,7 @@ async function runReadThread(opts = {}) {
4378
4395
  client = createChatClient(),
4379
4396
  resolveConnection = defaultResolveConnection,
4380
4397
  writeCursor = writeReadCursor,
4398
+ syncCursor = postReadCursor,
4381
4399
  ensureDisclosure = ensureChatDisclosure
4382
4400
  } = opts;
4383
4401
  const target = String(login ?? "").replace(/^@/, "").trim();
@@ -4423,6 +4441,7 @@ async function runReadThread(opts = {}) {
4423
4441
  writeCursor(peerLogin, newest.createdAt);
4424
4442
  } catch {
4425
4443
  }
4444
+ await syncCursor(peerLogin, newest.createdAt);
4426
4445
  }
4427
4446
  }
4428
4447
  return { ok: true, shown: shownMessages.length, total };
@@ -4470,13 +4489,15 @@ async function runSend(opts = {}) {
4470
4489
  );
4471
4490
  return { ok: true };
4472
4491
  }
4473
- var CHAT_BASE2, TERMINALHIRE_DIR5, READS_FILE;
4492
+ var CHAT_BASE2, GH_SESSION_COOKIE2, TERMINALHIRE_DIR5, READS_FILE;
4474
4493
  var init_jpi_chat_read = __esm({
4475
4494
  "bin/jpi-chat-read.js"() {
4476
4495
  "use strict";
4477
4496
  init_chat_client();
4497
+ init_web_session();
4478
4498
  init_jpi_chat();
4479
4499
  CHAT_BASE2 = process.env["TERMINALHIRE_API_URL"] || "https://www.terminalhire.com";
4500
+ GH_SESSION_COOKIE2 = "__jpi_gh_session";
4480
4501
  TERMINALHIRE_DIR5 = join7(homedir6(), ".terminalhire");
4481
4502
  READS_FILE = join7(TERMINALHIRE_DIR5, "chat-reads.json");
4482
4503
  }
@@ -4538,7 +4559,7 @@ async function fetchIntroList(deps = {}) {
4538
4559
  try {
4539
4560
  res = await fetchImpl(`${CHAT_BASE3}/api/intro/list`, {
4540
4561
  method: "GET",
4541
- headers: { Cookie: `${GH_SESSION_COOKIE2}=${cookie}` },
4562
+ headers: { Cookie: `${GH_SESSION_COOKIE3}=${cookie}` },
4542
4563
  signal: AbortSignal.timeout(1e4)
4543
4564
  });
4544
4565
  } catch (err) {
@@ -4699,9 +4720,9 @@ async function runChatPane(opts = {}) {
4699
4720
  output.write(
4700
4721
  `
4701
4722
  @${peerLogin} isn't reachable for chat yet.
4702
- They may not have opened chat (no encryption key published), or the
4703
- connection is no longer active. Once they run
4704
- terminalhire chat ${target}
4723
+ Chat is end-to-end encrypted, so they need to open chat once to
4724
+ publish their key. As soon as they run
4725
+ terminalhire chat
4705
4726
  on their side, messages will flow.
4706
4727
 
4707
4728
  `
@@ -5080,14 +5101,14 @@ async function run() {
5080
5101
  process.exit(1);
5081
5102
  }
5082
5103
  }
5083
- var CHAT_BASE3, GH_SESSION_COOKIE2, HIDE_CURSOR, SHOW_CURSOR, CLEAR, KEY_CTRL_C, KEY_CTRL_S, KEY_ENTER_A, KEY_ENTER_B, KEY_BACKSPACE_A, KEY_BACKSPACE_B, MAX_INPUT_LEN, ANSI_CSI, ANSI_OSC, ANSI_OTHER, C0_C1_DEL, CHAT_DISCLOSURE, CHAT_AT_REST, CHAT_CODE_OF_CONDUCT, CHAT_MIN_AGE, DEPOSIT_CTA;
5104
+ var CHAT_BASE3, GH_SESSION_COOKIE3, HIDE_CURSOR, SHOW_CURSOR, CLEAR, KEY_CTRL_C, KEY_CTRL_S, KEY_ENTER_A, KEY_ENTER_B, KEY_BACKSPACE_A, KEY_BACKSPACE_B, MAX_INPUT_LEN, ANSI_CSI, ANSI_OSC, ANSI_OTHER, C0_C1_DEL, CHAT_DISCLOSURE, CHAT_AT_REST, CHAT_CODE_OF_CONDUCT, CHAT_MIN_AGE, DEPOSIT_CTA;
5084
5105
  var init_jpi_chat = __esm({
5085
5106
  "bin/jpi-chat.js"() {
5086
5107
  init_chat_client();
5087
5108
  init_config();
5088
5109
  init_web_session();
5089
5110
  CHAT_BASE3 = process.env["TERMINALHIRE_API_URL"] || "https://www.terminalhire.com";
5090
- GH_SESSION_COOKIE2 = "__jpi_gh_session";
5111
+ GH_SESSION_COOKIE3 = "__jpi_gh_session";
5091
5112
  HIDE_CURSOR = "\x1B[?25l";
5092
5113
  SHOW_CURSOR = "\x1B[?25h";
5093
5114
  CLEAR = "\x1B[2J\x1B[H";
@@ -9634,7 +9634,7 @@ function defaultIntroDeps() {
9634
9634
  try {
9635
9635
  const { readProfile: readProfile2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
9636
9636
  const profile = await readProfile2();
9637
- return { displayName: profile?.displayName, contactEmail: profile?.contactEmail };
9637
+ return { displayName: profile?.displayName };
9638
9638
  } catch {
9639
9639
  return {};
9640
9640
  }
@@ -9704,7 +9704,7 @@ async function runIntroRequest(args5, overrides) {
9704
9704
  }
9705
9705
  const profile = await deps.readProfileContact();
9706
9706
  const displayName = (args5.name ?? profile.displayName ?? requesterLogin).trim();
9707
- const explicitContact = (args5.contact ?? profile.contactEmail ?? "").trim();
9707
+ const explicitContact = (args5.contact ?? "").trim();
9708
9708
  const contact = explicitContact || `@${requesterLogin}`;
9709
9709
  const payload = buildIntroPayload({
9710
9710
  requesterLogin,
@@ -9835,8 +9835,7 @@ async function runIntroDecision(args5, overrides) {
9835
9835
  let contact = "";
9836
9836
  let shareHandle = false;
9837
9837
  if (args5.action === "accept") {
9838
- const profile = await deps.readProfileContact();
9839
- contact = (args5.contact ?? profile.contactEmail ?? "").trim();
9838
+ contact = (args5.contact ?? "").trim();
9840
9839
  shareHandle = contact.length === 0;
9841
9840
  let shareLabel = contact;
9842
9841
  if (shareHandle) {
@@ -9906,8 +9905,11 @@ async function runIntroDecision(args5, overrides) {
9906
9905
  deps.log("\n Declined \u2014 no contact was shared.\n");
9907
9906
  return;
9908
9907
  }
9909
- deps.log("\n Accepted. Your contact was shared with the requester.");
9910
- if (data.contact) deps.log(` You can reach them at: ${data.contact}`);
9908
+ const peer = data.counterpartyLogin;
9909
+ deps.log(`
9910
+ \u2713 Connected${peer ? ` with @${peer}` : ""}.`);
9911
+ if (peer) deps.log(` Message them any time: terminalhire chat @${peer}`);
9912
+ if (data.contact) deps.log(` They also shared: ${data.contact}`);
9911
9913
  deps.log("");
9912
9914
  }
9913
9915
  async function runIntroList(overrides) {
@@ -10287,6 +10289,7 @@ var jpi_chat_read_exports = {};
10287
10289
  __export(jpi_chat_read_exports, {
10288
10290
  formatClock: () => formatClock,
10289
10291
  formatStamp: () => formatStamp,
10292
+ postReadCursor: () => postReadCursor,
10290
10293
  readReadCursors: () => readReadCursors,
10291
10294
  renderInbox: () => renderInbox,
10292
10295
  renderThread: () => renderThread,
@@ -10319,6 +10322,22 @@ function writeReadCursor(login, iso, deps = {}) {
10319
10322
  mkdirSync13(TERMINALHIRE_DIR12, { recursive: true });
10320
10323
  writeFileSync13(READS_FILE, JSON.stringify(cursors, null, 2), { mode: 384, encoding: "utf8" });
10321
10324
  }
10325
+ async function postReadCursor(peerLogin, lastReadAt, deps = {}) {
10326
+ const readCookie = deps.readCookie ?? readWebSessionCookie;
10327
+ const cookie = readCookie();
10328
+ if (!cookie) return;
10329
+ try {
10330
+ await fetch(`${CHAT_BASE2}/api/chat/read-cursor`, {
10331
+ method: "POST",
10332
+ headers: { "Content-Type": "application/json", Cookie: `${GH_SESSION_COOKIE4}=${cookie}` },
10333
+ body: JSON.stringify({ peerLogin, lastReadAt }),
10334
+ // Best-effort cross-device sync — not latency-sensitive, so a short bound
10335
+ // keeps a cold/unreachable server from stalling the reader's exit.
10336
+ signal: AbortSignal.timeout(2500)
10337
+ });
10338
+ } catch {
10339
+ }
10340
+ }
10322
10341
  function formatClock(iso) {
10323
10342
  const d = new Date(iso);
10324
10343
  if (Number.isNaN(d.getTime())) return "--:--";
@@ -10498,6 +10517,7 @@ async function runReadThread(opts = {}) {
10498
10517
  client = createChatClient(),
10499
10518
  resolveConnection = defaultResolveConnection,
10500
10519
  writeCursor = writeReadCursor,
10520
+ syncCursor = postReadCursor,
10501
10521
  ensureDisclosure = ensureChatDisclosure
10502
10522
  } = opts;
10503
10523
  const target = String(login ?? "").replace(/^@/, "").trim();
@@ -10543,6 +10563,7 @@ async function runReadThread(opts = {}) {
10543
10563
  writeCursor(peerLogin, newest.createdAt);
10544
10564
  } catch {
10545
10565
  }
10566
+ await syncCursor(peerLogin, newest.createdAt);
10546
10567
  }
10547
10568
  }
10548
10569
  return { ok: true, shown: shownMessages.length, total };
@@ -10590,13 +10611,15 @@ async function runSend(opts = {}) {
10590
10611
  );
10591
10612
  return { ok: true };
10592
10613
  }
10593
- var CHAT_BASE2, TERMINALHIRE_DIR12, READS_FILE;
10614
+ var CHAT_BASE2, GH_SESSION_COOKIE4, TERMINALHIRE_DIR12, READS_FILE;
10594
10615
  var init_jpi_chat_read = __esm({
10595
10616
  "bin/jpi-chat-read.js"() {
10596
10617
  "use strict";
10597
10618
  init_chat_client();
10619
+ init_web_session();
10598
10620
  init_jpi_chat();
10599
10621
  CHAT_BASE2 = process.env["TERMINALHIRE_API_URL"] || "https://www.terminalhire.com";
10622
+ GH_SESSION_COOKIE4 = "__jpi_gh_session";
10600
10623
  TERMINALHIRE_DIR12 = join16(homedir15(), ".terminalhire");
10601
10624
  READS_FILE = join16(TERMINALHIRE_DIR12, "chat-reads.json");
10602
10625
  }
@@ -10673,7 +10696,7 @@ async function fetchIntroList(deps = {}) {
10673
10696
  try {
10674
10697
  res = await fetchImpl(`${CHAT_BASE3}/api/intro/list`, {
10675
10698
  method: "GET",
10676
- headers: { Cookie: `${GH_SESSION_COOKIE4}=${cookie}` },
10699
+ headers: { Cookie: `${GH_SESSION_COOKIE5}=${cookie}` },
10677
10700
  signal: AbortSignal.timeout(1e4)
10678
10701
  });
10679
10702
  } catch (err) {
@@ -10834,9 +10857,9 @@ async function runChatPane(opts = {}) {
10834
10857
  output.write(
10835
10858
  `
10836
10859
  @${peerLogin} isn't reachable for chat yet.
10837
- They may not have opened chat (no encryption key published), or the
10838
- connection is no longer active. Once they run
10839
- terminalhire chat ${target}
10860
+ Chat is end-to-end encrypted, so they need to open chat once to
10861
+ publish their key. As soon as they run
10862
+ terminalhire chat
10840
10863
  on their side, messages will flow.
10841
10864
 
10842
10865
  `
@@ -11215,7 +11238,7 @@ async function run9() {
11215
11238
  process.exit(1);
11216
11239
  }
11217
11240
  }
11218
- var CHAT_BASE3, GH_SESSION_COOKIE4, HIDE_CURSOR, SHOW_CURSOR, CLEAR, KEY_CTRL_C, KEY_CTRL_S, KEY_ENTER_A, KEY_ENTER_B, KEY_BACKSPACE_A, KEY_BACKSPACE_B, MAX_INPUT_LEN, ANSI_CSI, ANSI_OSC, ANSI_OTHER, C0_C1_DEL, CHAT_DISCLOSURE, CHAT_AT_REST, CHAT_CODE_OF_CONDUCT, CHAT_MIN_AGE, DEPOSIT_CTA;
11241
+ var CHAT_BASE3, GH_SESSION_COOKIE5, HIDE_CURSOR, SHOW_CURSOR, CLEAR, KEY_CTRL_C, KEY_CTRL_S, KEY_ENTER_A, KEY_ENTER_B, KEY_BACKSPACE_A, KEY_BACKSPACE_B, MAX_INPUT_LEN, ANSI_CSI, ANSI_OSC, ANSI_OTHER, C0_C1_DEL, CHAT_DISCLOSURE, CHAT_AT_REST, CHAT_CODE_OF_CONDUCT, CHAT_MIN_AGE, DEPOSIT_CTA;
11219
11242
  var init_jpi_chat = __esm({
11220
11243
  "bin/jpi-chat.js"() {
11221
11244
  "use strict";
@@ -11223,7 +11246,7 @@ var init_jpi_chat = __esm({
11223
11246
  init_config();
11224
11247
  init_web_session();
11225
11248
  CHAT_BASE3 = process.env["TERMINALHIRE_API_URL"] || "https://www.terminalhire.com";
11226
- GH_SESSION_COOKIE4 = "__jpi_gh_session";
11249
+ GH_SESSION_COOKIE5 = "__jpi_gh_session";
11227
11250
  HIDE_CURSOR = "\x1B[?25l";
11228
11251
  SHOW_CURSOR = "\x1B[?25h";
11229
11252
  CLEAR = "\x1B[2J\x1B[H";
@@ -11429,7 +11452,7 @@ async function runLinkLogout(overrides) {
11429
11452
  try {
11430
11453
  const res = await deps.fetchImpl(`${LINK_BASE3}/api/auth/session`, {
11431
11454
  method: "DELETE",
11432
- headers: { Cookie: `${GH_SESSION_COOKIE5}=${token}` },
11455
+ headers: { Cookie: `${GH_SESSION_COOKIE6}=${token}` },
11433
11456
  signal: AbortSignal.timeout(1e4)
11434
11457
  });
11435
11458
  revoked = res.ok;
@@ -11444,14 +11467,14 @@ async function runLinkLogout(overrides) {
11444
11467
  }
11445
11468
  deps.exit(0);
11446
11469
  }
11447
- var LINK_BASE3, GH_SESSION_COOKIE5, LINK_TIMEOUT_MS, LINKED_HTML, FAILED_HTML;
11470
+ var LINK_BASE3, GH_SESSION_COOKIE6, LINK_TIMEOUT_MS, LINKED_HTML, FAILED_HTML;
11448
11471
  var init_link = __esm({
11449
11472
  "src/link.ts"() {
11450
11473
  "use strict";
11451
11474
  init_web_session();
11452
11475
  init_config();
11453
11476
  LINK_BASE3 = "https://www.terminalhire.com";
11454
- GH_SESSION_COOKIE5 = "__jpi_gh_session";
11477
+ GH_SESSION_COOKIE6 = "__jpi_gh_session";
11455
11478
  LINK_TIMEOUT_MS = 12e4;
11456
11479
  LINKED_HTML = `<!doctype html><html><head><meta charset="utf-8"><title>terminalhire</title></head>
11457
11480
  <body style="font-family:system-ui;padding:2rem;background:#0b0d10;color:#e6e6e6">
@@ -13069,7 +13092,7 @@ async function run18() {
13069
13092
  if (sessionCookie && !isInboundNudgeMuted()) try {
13070
13093
  const res = await fetch(`${API_URL6}/api/intro/list`, {
13071
13094
  method: "GET",
13072
- headers: { Cookie: `${GH_SESSION_COOKIE6}=${sessionCookie}` },
13095
+ headers: { Cookie: `${GH_SESSION_COOKIE7}=${sessionCookie}` },
13073
13096
  signal: AbortSignal.timeout(1e4)
13074
13097
  });
13075
13098
  if (res.ok) {
@@ -13080,6 +13103,24 @@ async function run18() {
13080
13103
  }
13081
13104
  } catch {
13082
13105
  }
13106
+ let unreadChat = { count: 0 };
13107
+ if (sessionCookie && !isInboundNudgeMuted()) try {
13108
+ const res = await fetch(`${API_URL6}/api/chat/inbox`, {
13109
+ method: "GET",
13110
+ headers: { Cookie: `${GH_SESSION_COOKIE7}=${sessionCookie}` },
13111
+ signal: AbortSignal.timeout(1e4)
13112
+ });
13113
+ if (res.ok) {
13114
+ const body = await res.json();
13115
+ const inbox = Array.isArray(body?.inbox) ? body.inbox : [];
13116
+ const total = inbox.reduce(
13117
+ (sum, it) => sum + (it && typeof it.unreadCount === "number" && it.unreadCount > 0 ? it.unreadCount : 0),
13118
+ 0
13119
+ );
13120
+ unreadChat = { count: total };
13121
+ }
13122
+ } catch {
13123
+ }
13083
13124
  mkdirSync17(TERMINALHIRE_DIR14, { recursive: true });
13084
13125
  const cacheEntry = {
13085
13126
  ts: Date.now(),
@@ -13087,7 +13128,8 @@ async function run18() {
13087
13128
  matchCount,
13088
13129
  topMatches,
13089
13130
  topPeers,
13090
- incomingPending
13131
+ incomingPending,
13132
+ unreadChat
13091
13133
  };
13092
13134
  writeFileSync17(INDEX_CACHE_FILE5, JSON.stringify(cacheEntry), "utf8");
13093
13135
  try {
@@ -13140,14 +13182,14 @@ async function run18() {
13140
13182
  process.exit(1);
13141
13183
  }
13142
13184
  }
13143
- var GH_SESSION_COOKIE6, __dirname4, TERMINALHIRE_DIR14, INDEX_CACHE_FILE5, API_URL6;
13185
+ var GH_SESSION_COOKIE7, __dirname4, TERMINALHIRE_DIR14, INDEX_CACHE_FILE5, API_URL6;
13144
13186
  var init_jpi_refresh = __esm({
13145
13187
  "bin/jpi-refresh.js"() {
13146
13188
  "use strict";
13147
13189
  init_directory2();
13148
13190
  init_config();
13149
13191
  init_web_session();
13150
- GH_SESSION_COOKIE6 = "__jpi_gh_session";
13192
+ GH_SESSION_COOKIE7 = "__jpi_gh_session";
13151
13193
  __dirname4 = fileURLToPath5(new URL(".", import.meta.url));
13152
13194
  TERMINALHIRE_DIR14 = join23(homedir21(), ".terminalhire");
13153
13195
  INDEX_CACHE_FILE5 = join23(TERMINALHIRE_DIR14, "index-cache.json");
@@ -1108,7 +1108,7 @@ function defaultIntroDeps() {
1108
1108
  try {
1109
1109
  const { readProfile: readProfile2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
1110
1110
  const profile = await readProfile2();
1111
- return { displayName: profile?.displayName, contactEmail: profile?.contactEmail };
1111
+ return { displayName: profile?.displayName };
1112
1112
  } catch {
1113
1113
  return {};
1114
1114
  }
@@ -1178,7 +1178,7 @@ async function runIntroRequest(args, overrides) {
1178
1178
  }
1179
1179
  const profile = await deps.readProfileContact();
1180
1180
  const displayName = (args.name ?? profile.displayName ?? requesterLogin).trim();
1181
- const explicitContact = (args.contact ?? profile.contactEmail ?? "").trim();
1181
+ const explicitContact = (args.contact ?? "").trim();
1182
1182
  const contact = explicitContact || `@${requesterLogin}`;
1183
1183
  const payload = buildIntroPayload({
1184
1184
  requesterLogin,
@@ -1309,8 +1309,7 @@ async function runIntroDecision(args, overrides) {
1309
1309
  let contact = "";
1310
1310
  let shareHandle = false;
1311
1311
  if (args.action === "accept") {
1312
- const profile = await deps.readProfileContact();
1313
- contact = (args.contact ?? profile.contactEmail ?? "").trim();
1312
+ contact = (args.contact ?? "").trim();
1314
1313
  shareHandle = contact.length === 0;
1315
1314
  let shareLabel = contact;
1316
1315
  if (shareHandle) {
@@ -1380,8 +1379,11 @@ async function runIntroDecision(args, overrides) {
1380
1379
  deps.log("\n Declined \u2014 no contact was shared.\n");
1381
1380
  return;
1382
1381
  }
1383
- deps.log("\n Accepted. Your contact was shared with the requester.");
1384
- if (data.contact) deps.log(` You can reach them at: ${data.contact}`);
1382
+ const peer = data.counterpartyLogin;
1383
+ deps.log(`
1384
+ \u2713 Connected${peer ? ` with @${peer}` : ""}.`);
1385
+ if (peer) deps.log(` Message them any time: terminalhire chat @${peer}`);
1386
+ if (data.contact) deps.log(` They also shared: ${data.contact}`);
1385
1387
  deps.log("");
1386
1388
  }
1387
1389
  async function runIntroList(overrides) {
@@ -7094,6 +7094,24 @@ async function run() {
7094
7094
  }
7095
7095
  } catch {
7096
7096
  }
7097
+ let unreadChat = { count: 0 };
7098
+ if (sessionCookie && !isInboundNudgeMuted()) try {
7099
+ const res = await fetch(`${API_URL2}/api/chat/inbox`, {
7100
+ method: "GET",
7101
+ headers: { Cookie: `${GH_SESSION_COOKIE}=${sessionCookie}` },
7102
+ signal: AbortSignal.timeout(1e4)
7103
+ });
7104
+ if (res.ok) {
7105
+ const body = await res.json();
7106
+ const inbox = Array.isArray(body?.inbox) ? body.inbox : [];
7107
+ const total = inbox.reduce(
7108
+ (sum, it) => sum + (it && typeof it.unreadCount === "number" && it.unreadCount > 0 ? it.unreadCount : 0),
7109
+ 0
7110
+ );
7111
+ unreadChat = { count: total };
7112
+ }
7113
+ } catch {
7114
+ }
7097
7115
  mkdirSync6(TERMINALHIRE_DIR4, { recursive: true });
7098
7116
  const cacheEntry = {
7099
7117
  ts: Date.now(),
@@ -7101,7 +7119,8 @@ async function run() {
7101
7119
  matchCount,
7102
7120
  topMatches,
7103
7121
  topPeers,
7104
- incomingPending
7122
+ incomingPending,
7123
+ unreadChat
7105
7124
  };
7106
7125
  writeFileSync6(INDEX_CACHE_FILE2, JSON.stringify(cacheEntry), "utf8");
7107
7126
  try {
package/dist/bin/jpi.js CHANGED
@@ -94,6 +94,17 @@ function getCachedIncomingCount() {
94
94
  return 0;
95
95
  }
96
96
  }
97
+ function getCachedUnreadChatCount() {
98
+ try {
99
+ const raw = readFileSync(INDEX_CACHE_FILE, "utf8");
100
+ const entry = JSON.parse(raw);
101
+ if (Date.now() - entry.ts > INDEX_CACHE_TTL_MS) return 0;
102
+ const n = entry.unreadChat && entry.unreadChat.count;
103
+ return typeof n === "number" && n > 0 ? n : 0;
104
+ } catch {
105
+ return 0;
106
+ }
107
+ }
97
108
  function getNudgeMode() {
98
109
  const envVal = process.env["TERMINALHIRE_NUDGE"];
99
110
  if (envVal) {
@@ -166,8 +177,9 @@ try {
166
177
  }
167
178
  const matchCount = getCachedMatchCount();
168
179
  const incomingCount = getCachedIncomingCount();
180
+ const unreadChatCount = getCachedUnreadChatCount();
169
181
  const haveRoles = matchCount !== null && matchCount > 0;
170
- if (!haveRoles && incomingCount === 0) process.exit(0);
182
+ if (!haveRoles && incomingCount === 0 && unreadChatCount === 0) process.exit(0);
171
183
  const nudgeMode = getNudgeMode();
172
184
  if (!shouldNudge(nudgeMode, sessionId)) process.exit(0);
173
185
  let line;
@@ -175,8 +187,12 @@ try {
175
187
  const plural = matchCount === 1 ? "role" : "roles";
176
188
  line = `\u2726 ${matchCount} ${plural} match your current work \u2014 run: terminalhire jobs`;
177
189
  if (incomingCount > 0) line += ` \xB7 \u2709 ${incomingCount} waiting to connect`;
178
- } else {
190
+ if (unreadChatCount > 0) line += ` \xB7 \u{1F4AC} ${unreadChatCount} unread`;
191
+ } else if (incomingCount > 0) {
179
192
  line = `\u2709 ${incomingCount} waiting to connect \u2014 run: terminalhire intro --list`;
193
+ if (unreadChatCount > 0) line += ` \xB7 \u{1F4AC} ${unreadChatCount} unread`;
194
+ } else {
195
+ line = `\u{1F4AC} ${unreadChatCount} unread \u2014 run: terminalhire chat`;
180
196
  }
181
197
  process.stdout.write(line + "\n");
182
198
  if (nudgeMode === "session") {
package/dist/src/intro.js CHANGED
@@ -1101,7 +1101,7 @@ function defaultIntroDeps() {
1101
1101
  try {
1102
1102
  const { readProfile: readProfile2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
1103
1103
  const profile = await readProfile2();
1104
- return { displayName: profile?.displayName, contactEmail: profile?.contactEmail };
1104
+ return { displayName: profile?.displayName };
1105
1105
  } catch {
1106
1106
  return {};
1107
1107
  }
@@ -1171,7 +1171,7 @@ async function runIntroRequest(args, overrides) {
1171
1171
  }
1172
1172
  const profile = await deps.readProfileContact();
1173
1173
  const displayName = (args.name ?? profile.displayName ?? requesterLogin).trim();
1174
- const explicitContact = (args.contact ?? profile.contactEmail ?? "").trim();
1174
+ const explicitContact = (args.contact ?? "").trim();
1175
1175
  const contact = explicitContact || `@${requesterLogin}`;
1176
1176
  const payload = buildIntroPayload({
1177
1177
  requesterLogin,
@@ -1303,8 +1303,7 @@ async function runIntroDecision(args, overrides) {
1303
1303
  let contact = "";
1304
1304
  let shareHandle = false;
1305
1305
  if (args.action === "accept") {
1306
- const profile = await deps.readProfileContact();
1307
- contact = (args.contact ?? profile.contactEmail ?? "").trim();
1306
+ contact = (args.contact ?? "").trim();
1308
1307
  shareHandle = contact.length === 0;
1309
1308
  let shareLabel = contact;
1310
1309
  if (shareHandle) {
@@ -1374,8 +1373,11 @@ async function runIntroDecision(args, overrides) {
1374
1373
  deps.log("\n Declined \u2014 no contact was shared.\n");
1375
1374
  return;
1376
1375
  }
1377
- deps.log("\n Accepted. Your contact was shared with the requester.");
1378
- if (data.contact) deps.log(` You can reach them at: ${data.contact}`);
1376
+ const peer = data.counterpartyLogin;
1377
+ deps.log(`
1378
+ \u2713 Connected${peer ? ` with @${peer}` : ""}.`);
1379
+ if (peer) deps.log(` Message them any time: terminalhire chat @${peer}`);
1380
+ if (data.contact) deps.log(` They also shared: ${data.contact}`);
1379
1381
  deps.log("");
1380
1382
  }
1381
1383
  async function runIntroList(overrides) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "terminalhire",
3
- "version": "0.10.3",
3
+ "version": "0.10.5",
4
4
  "description": "Local-first job matching for developers — ambient job matches in the Claude Code spinner. Matching runs on your machine; your profile never leaves it.",
5
5
  "repository": {
6
6
  "type": "git",