vibedate 0.7.0 → 0.7.2

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.
Files changed (2) hide show
  1. package/dist/cli.js +397 -57
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -387,30 +387,60 @@ function uninstallDaemonService(opts) {
387
387
  }
388
388
 
389
389
  // src/pairing.ts
390
+ var MAX_BUFFERED = 100;
390
391
  function createPairing() {
391
392
  const queue = [];
392
393
  const matchCbs = /* @__PURE__ */ new Set();
394
+ const msgCbs = /* @__PURE__ */ new Set();
395
+ const queuedCbs = /* @__PURE__ */ new Set();
396
+ const buffers = /* @__PURE__ */ new Map();
393
397
  let current;
394
- const emit = (link) => {
398
+ const emitMatch = (link) => {
395
399
  for (const cb of matchCbs) cb(link);
396
400
  };
401
+ const deliver = (from, m) => {
402
+ for (const cb of msgCbs) cb(from, m);
403
+ };
404
+ const notifyQueued = (from, n) => {
405
+ for (const cb of queuedCbs) cb(from, n);
406
+ };
407
+ const flush = (link) => {
408
+ const buf = buffers.get(link);
409
+ if (buf !== void 0 && buf.length > 0) {
410
+ for (const m of buf) deliver(link.hello.handle, m);
411
+ buffers.set(link, []);
412
+ }
413
+ };
414
+ const setCurrent = (link) => {
415
+ current = link;
416
+ if (link !== void 0) flush(link);
417
+ emitMatch(link);
418
+ };
397
419
  const watch = (link) => {
398
420
  link.onClose(() => {
421
+ buffers.delete(link);
399
422
  if (current === link) {
400
- current = void 0;
401
423
  const nextUp = queue.shift();
402
- if (nextUp !== void 0) {
403
- current = nextUp;
404
- emit(current);
405
- } else {
406
- emit(void 0);
407
- }
424
+ setCurrent(nextUp);
408
425
  } else {
409
426
  const idx = queue.indexOf(link);
410
427
  if (idx >= 0) queue.splice(idx, 1);
411
428
  }
412
429
  });
413
430
  };
431
+ const bindMessages = (link) => {
432
+ link.onMessage((m) => {
433
+ if (link === current) {
434
+ deliver(link.hello.handle, m);
435
+ } else {
436
+ const buf = buffers.get(link) ?? [];
437
+ buf.push(m);
438
+ if (buf.length > MAX_BUFFERED) buf.shift();
439
+ buffers.set(link, buf);
440
+ notifyQueued(link.hello.handle, buf.length);
441
+ }
442
+ });
443
+ };
414
444
  return {
415
445
  get available() {
416
446
  return queue.length;
@@ -420,9 +450,9 @@ function createPairing() {
420
450
  },
421
451
  add(link) {
422
452
  watch(link);
453
+ bindMessages(link);
423
454
  if (current === void 0) {
424
- current = link;
425
- emit(link);
455
+ setCurrent(link);
426
456
  } else {
427
457
  queue.push(link);
428
458
  }
@@ -433,10 +463,7 @@ function createPairing() {
433
463
  current = void 0;
434
464
  }
435
465
  const nextUp = queue.shift();
436
- if (nextUp !== void 0) {
437
- current = nextUp;
438
- }
439
- emit(current);
466
+ setCurrent(nextUp);
440
467
  return current;
441
468
  },
442
469
  open(handle2) {
@@ -447,12 +474,17 @@ function createPairing() {
447
474
  current.close();
448
475
  current = void 0;
449
476
  }
450
- current = link;
451
- emit(current);
477
+ setCurrent(link);
452
478
  return current;
453
479
  },
454
480
  onMatch(cb) {
455
481
  matchCbs.add(cb);
482
+ },
483
+ onMessage(cb) {
484
+ msgCbs.add(cb);
485
+ },
486
+ onQueued(cb) {
487
+ queuedCbs.add(cb);
456
488
  }
457
489
  };
458
490
  }
@@ -914,6 +946,56 @@ var webAppHtml = `<!DOCTYPE html>
914
946
  .incoming-call p{ color: var(--muted); font-size: .85rem; margin: 0 0 18px; }
915
947
  .incoming-call .ic-actions{ display: flex; gap: 10px; }
916
948
 
949
+ /* ---- room full-mesh group video ---- */
950
+ .room-panel{
951
+ position: fixed; right: 18px; bottom: 18px; z-index: 45;
952
+ background: linear-gradient(180deg, var(--bg-card), var(--bg-card-2));
953
+ border: 1px solid var(--border-2); border-radius: 16px; box-shadow: var(--shadow-2);
954
+ padding: 12px 14px; min-width: 240px; max-width: 280px; display: none;
955
+ }
956
+ .room-panel.is-open{ display: block; animation: rise var(--dur-med) var(--ease-out); }
957
+ .room-panel .rp-title{ font-size: .78rem; font-weight: 800; margin-bottom: 6px; display:flex; align-items:center; gap:6px; }
958
+ .room-panel .rp-meta{ font-size: .7rem; color: var(--muted-2); margin-bottom: 10px; line-height: 1.4; word-break: break-all; }
959
+ .room-panel .rp-actions{ display:flex; gap: 8px; }
960
+ .room-panel .rp-btn{
961
+ border: 0; border-radius: 9px; padding: 8px 14px; font-weight: 700; font-size: .78rem;
962
+ background: linear-gradient(180deg, var(--coral), var(--coral-dim)); color: #2a1109;
963
+ flex: 1;
964
+ }
965
+ .room-panel .rp-btn:disabled{ opacity: .4; cursor: not-allowed; }
966
+ .room-panel .rp-btn.is-leave{ background: var(--danger); color: #2a0a0c; }
967
+ .room-video{
968
+ position: fixed; inset: 0; z-index: 68;
969
+ display: none; flex-direction: column; align-items: stretch; justify-content: flex-start;
970
+ background: rgba(12,7,15,.86); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
971
+ padding: 18px 18px 80px;
972
+ }
973
+ .room-video.is-open{ display: flex; }
974
+ .room-video .rv-head{
975
+ display:flex; align-items:center; justify-content: space-between; gap: 12px;
976
+ margin-bottom: 14px; color: var(--fg); font-weight: 800; font-size: .9rem;
977
+ }
978
+ .room-video .rv-grid{
979
+ flex: 1; display: grid; gap: 12px; min-height: 0;
980
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
981
+ align-content: start;
982
+ }
983
+ .room-video .rv-tile{
984
+ display:flex; flex-direction: column; gap: 6px; min-width: 0;
985
+ }
986
+ .room-video .rv-tile video{
987
+ width: 100%; aspect-ratio: 4 / 3; background: #000; border-radius: 14px;
988
+ border: 1px solid var(--border-2); object-fit: cover;
989
+ }
990
+ .room-video .rv-tile .rv-label{
991
+ font-size: .75rem; color: var(--muted); font-weight: 600; word-break: break-all;
992
+ }
993
+ .room-video .rv-leave{
994
+ position: absolute; bottom: 24px; left: 50%; transform: translateX(-50%);
995
+ border: 0; border-radius: 999px; padding: 12px 22px; font-weight: 800; font-size: .9rem;
996
+ background: var(--danger); color: #2a0a0c;
997
+ }
998
+
917
999
  /* ---- live text chat (browser <-> PeerLink over /live/message) ---- */
918
1000
  .live-actions{ display: flex; gap: 6px; flex-shrink: 0; }
919
1001
  .live-row .cbtn{
@@ -1151,6 +1233,24 @@ var webAppHtml = `<!DOCTYPE html>
1151
1233
  <button class="vhangup" id="hangupBtn" type="button">Hang up</button>
1152
1234
  </div>
1153
1235
 
1236
+ <aside class="room-panel" id="roomPanel" aria-label="Room">
1237
+ <div class="rp-title"><span class="lp-dot" aria-hidden="true"></span> Room</div>
1238
+ <div class="rp-meta" id="roomMeta">not in a room</div>
1239
+ <div class="rp-actions">
1240
+ <button class="rp-btn" id="roomJoinBtn" type="button">Join video</button>
1241
+ <button class="rp-btn is-leave" id="roomLeaveBtn" type="button" disabled>Leave</button>
1242
+ </div>
1243
+ </aside>
1244
+
1245
+ <div class="room-video" id="roomVideo" role="dialog" aria-modal="true" aria-label="Room video">
1246
+ <div class="rv-head">
1247
+ <span id="roomVideoTitle">Room video</span>
1248
+ <span id="roomVideoCount" style="font-weight:600;color:var(--muted);font-size:.78rem"></span>
1249
+ </div>
1250
+ <div class="rv-grid" id="roomVideoGrid"></div>
1251
+ <button class="rv-leave" id="roomVideoLeaveBtn" type="button">Leave video</button>
1252
+ </div>
1253
+
1154
1254
  <script>
1155
1255
  (function(){
1156
1256
  "use strict";
@@ -1816,6 +1916,252 @@ var webAppHtml = `<!DOCTYPE html>
1816
1916
  }
1817
1917
  refreshPeers();
1818
1918
  setInterval(refreshPeers, 4000);
1919
+
1920
+ /* ---- Room full-mesh group video ----------------------------------------
1921
+ * When vibedate open --room <name> is attached, /api/room exposes the
1922
+ * roster. Each browser grabs getUserMedia once and opens one
1923
+ * RTCPeerConnection to every other member. Deterministic offerer election
1924
+ * (self < other lexicographic) avoids glare. Signaling rides the local
1925
+ * server's merged mailbox: GET /room/signal?handle=SELF returns
1926
+ * { frame: { from, rtc } }, POST /room/signal { handle: TARGET, frame }
1927
+ * relays one rtc-* frame to that member. */
1928
+ var roomPanel = document.getElementById("roomPanel");
1929
+ var roomMeta = document.getElementById("roomMeta");
1930
+ var roomJoinBtn = document.getElementById("roomJoinBtn");
1931
+ var roomLeaveBtn = document.getElementById("roomLeaveBtn");
1932
+ var roomVideo = document.getElementById("roomVideo");
1933
+ var roomVideoGrid = document.getElementById("roomVideoGrid");
1934
+ var roomVideoTitle = document.getElementById("roomVideoTitle");
1935
+ var roomVideoCount = document.getElementById("roomVideoCount");
1936
+ var roomVideoLeaveBtn = document.getElementById("roomVideoLeaveBtn");
1937
+
1938
+ var roomVid = { localStream: null, peers: {}, self: null, room: null, joined: false, polling: false };
1939
+ var roomLastRoster = [];
1940
+
1941
+ function roomPostSignal(handle, frame){
1942
+ return fetch("/room/signal", {
1943
+ method: "POST",
1944
+ headers: { "content-type": "application/json" },
1945
+ body: JSON.stringify({ handle: handle, frame: frame })
1946
+ }).catch(function(){ /* best effort */ });
1947
+ }
1948
+
1949
+ function roomFetchSignal(selfHandle, timeoutMs){
1950
+ var ctrl = new AbortController();
1951
+ var t = setTimeout(function(){ ctrl.abort(); }, timeoutMs);
1952
+ return fetch("/room/signal?handle=" + encodeURIComponent(selfHandle), { signal: ctrl.signal })
1953
+ .then(function(r){ clearTimeout(t); return r; })
1954
+ .catch(function(e){ clearTimeout(t); throw e; });
1955
+ }
1956
+
1957
+ function makeRoomTile(label, muted){
1958
+ var tile = document.createElement("div");
1959
+ tile.className = "rv-tile";
1960
+ var vid = document.createElement("video");
1961
+ vid.autoplay = true;
1962
+ vid.playsInline = true;
1963
+ if (muted) vid.muted = true;
1964
+ var lab = document.createElement("div");
1965
+ lab.className = "rv-label";
1966
+ lab.textContent = label;
1967
+ tile.appendChild(vid);
1968
+ tile.appendChild(lab);
1969
+ roomVideoGrid.appendChild(tile);
1970
+ return vid;
1971
+ }
1972
+
1973
+ function updateRoomCount(){
1974
+ var n = 0;
1975
+ if (roomVid.localStream) n++;
1976
+ for (var k in roomVid.peers) if (Object.prototype.hasOwnProperty.call(roomVid.peers, k)) n++;
1977
+ roomVideoCount.textContent = n ? (n + " tile" + (n === 1 ? "" : "s")) : "";
1978
+ }
1979
+
1980
+ function connectPeer(handle, role){
1981
+ if (!roomVid.localStream || !roomVid.self) return null;
1982
+ if (roomVid.peers[handle]) return roomVid.peers[handle];
1983
+ var pc = new RTCPeerConnection(rtcConfig());
1984
+ var videoEl = makeRoomTile(handle, false);
1985
+ var peer = { pc: pc, videoEl: videoEl, role: role };
1986
+ roomVid.peers[handle] = peer;
1987
+ updateRoomCount();
1988
+
1989
+ roomVid.localStream.getTracks().forEach(function(t){ pc.addTrack(t, roomVid.localStream); });
1990
+ pc.onicecandidate = function(e){
1991
+ // Trickle ICE to this peer. Empty candidate = end-of-candidates marker.
1992
+ roomPostSignal(handle, {
1993
+ t: "rtc-ice",
1994
+ candidate: e.candidate && e.candidate.candidate ? e.candidate.candidate : ""
1995
+ });
1996
+ };
1997
+ pc.ontrack = function(e){
1998
+ if (e.streams && e.streams[0]) videoEl.srcObject = e.streams[0];
1999
+ };
2000
+ pc.onconnectionstatechange = function(){
2001
+ // Quiet \u2014 closed peers stay in the grid until Leave clears everything.
2002
+ };
2003
+
2004
+ if (role === "offerer") {
2005
+ (async function(){
2006
+ try {
2007
+ var offer = await pc.createOffer();
2008
+ await pc.setLocalDescription(offer);
2009
+ await roomPostSignal(handle, { t: "rtc-offer", sdp: offer.sdp });
2010
+ } catch(e){ /* offer failed \u2014 peer will retry on next roster refresh if still present */ }
2011
+ })();
2012
+ }
2013
+ return peer;
2014
+ }
2015
+
2016
+ async function handleRoomSignal(from, rtc){
2017
+ if (!rtc || !from || from === roomVid.self) return;
2018
+ if (rtc.t === "rtc-offer") {
2019
+ var peer = roomVid.peers[from];
2020
+ if (!peer) peer = connectPeer(from, "answerer");
2021
+ if (!peer) return;
2022
+ try {
2023
+ await peer.pc.setRemoteDescription({ type: "offer", sdp: rtc.sdp });
2024
+ var answer = await peer.pc.createAnswer();
2025
+ await peer.pc.setLocalDescription(answer);
2026
+ await roomPostSignal(from, { t: "rtc-answer", sdp: answer.sdp });
2027
+ } catch(e){}
2028
+ return;
2029
+ }
2030
+ if (rtc.t === "rtc-answer") {
2031
+ var pAns = roomVid.peers[from];
2032
+ if (!pAns) return;
2033
+ try { await pAns.pc.setRemoteDescription({ type: "answer", sdp: rtc.sdp }); } catch(e){}
2034
+ return;
2035
+ }
2036
+ if (rtc.t === "rtc-ice") {
2037
+ var pIce = roomVid.peers[from];
2038
+ if (!pIce) return;
2039
+ // Skip empty candidate (end-of-candidates) \u2014 browsers tolerate either way.
2040
+ if (!rtc.candidate) return;
2041
+ try { await pIce.pc.addIceCandidate({ candidate: rtc.candidate }); } catch(e){}
2042
+ }
2043
+ }
2044
+
2045
+ async function roomSignalLoop(){
2046
+ if (roomVid.polling) return;
2047
+ roomVid.polling = true;
2048
+ while (roomVid.joined && roomVid.self) {
2049
+ var res;
2050
+ try { res = await roomFetchSignal(roomVid.self, 5000); }
2051
+ catch(e){ await sleep(400); continue; }
2052
+ if (!res || res.status !== 200) { await sleep(400); continue; }
2053
+ var data;
2054
+ try { data = await res.json(); } catch(e){ continue; }
2055
+ var f = data && data.frame;
2056
+ if (!f) continue; // timeout empty
2057
+ // Sender-routed shape: { from, rtc } (or a nested frame.frame fallback).
2058
+ var from = f.from;
2059
+ var rtc = f.rtc || f.frame || f;
2060
+ if (from && rtc && rtc.t) await handleRoomSignal(from, rtc);
2061
+ }
2062
+ roomVid.polling = false;
2063
+ }
2064
+
2065
+ function meshWithMembers(members){
2066
+ if (!roomVid.joined || !roomVid.self || !roomVid.localStream) return;
2067
+ var self = roomVid.self;
2068
+ var seen = {};
2069
+ (members || []).forEach(function(m){
2070
+ var h = m && m.handle;
2071
+ if (!h || h === self) return;
2072
+ seen[h] = true;
2073
+ if (roomVid.peers[h]) return;
2074
+ // Deterministic glare avoidance: lower handle is the offerer.
2075
+ if (self < h) connectPeer(h, "offerer");
2076
+ // else wait for their offer via the long-poll loop
2077
+ });
2078
+ }
2079
+
2080
+ async function joinRoomVideo(){
2081
+ if (roomVid.joined) return;
2082
+ if (!roomVid.self || !roomVid.room) return;
2083
+ roomJoinBtn.disabled = true;
2084
+ try {
2085
+ roomVid.localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
2086
+ } catch(e){
2087
+ roomJoinBtn.disabled = false;
2088
+ roomMeta.textContent = (roomVid.room || "room") + " \xB7 camera/mic blocked";
2089
+ return;
2090
+ }
2091
+ roomVid.joined = true;
2092
+ roomVid.peers = {};
2093
+ roomVideoGrid.innerHTML = "";
2094
+ var localEl = makeRoomTile((roomVid.self || "you") + " (you)", true);
2095
+ localEl.srcObject = roomVid.localStream;
2096
+ roomVideo.classList.add("is-open");
2097
+ roomVideoTitle.textContent = "Room \xB7 " + roomVid.room;
2098
+ roomLeaveBtn.disabled = false;
2099
+ updateRoomCount();
2100
+ roomSignalLoop();
2101
+ // Seed mesh against the latest roster, then refresh once more for races.
2102
+ meshWithMembers(roomLastRoster);
2103
+ try {
2104
+ var res = await fetch("/api/room");
2105
+ if (res.status === 200) {
2106
+ var data = await res.json();
2107
+ var members = (data && data.members) || [];
2108
+ roomLastRoster = members;
2109
+ meshWithMembers(members);
2110
+ }
2111
+ } catch(e){}
2112
+ }
2113
+
2114
+ function leaveRoomVideo(){
2115
+ roomVid.joined = false;
2116
+ for (var h in roomVid.peers) {
2117
+ if (!Object.prototype.hasOwnProperty.call(roomVid.peers, h)) continue;
2118
+ try { roomVid.peers[h].pc.close(); } catch(e){}
2119
+ }
2120
+ roomVid.peers = {};
2121
+ if (roomVid.localStream) {
2122
+ roomVid.localStream.getTracks().forEach(function(t){ try { t.stop(); } catch(e){} });
2123
+ roomVid.localStream = null;
2124
+ }
2125
+ roomVideoGrid.innerHTML = "";
2126
+ roomVideo.classList.remove("is-open");
2127
+ roomLeaveBtn.disabled = true;
2128
+ roomJoinBtn.disabled = !roomVid.room;
2129
+ updateRoomCount();
2130
+ }
2131
+
2132
+ roomJoinBtn.addEventListener("click", function(){ joinRoomVideo().catch(function(){ leaveRoomVideo(); }); });
2133
+ roomLeaveBtn.addEventListener("click", leaveRoomVideo);
2134
+ roomVideoLeaveBtn.addEventListener("click", leaveRoomVideo);
2135
+
2136
+ async function refreshRoom(){
2137
+ try {
2138
+ var res = await fetch("/api/room");
2139
+ if (res.status !== 200) return;
2140
+ var data = await res.json();
2141
+ var roomName = data && data.room;
2142
+ if (!roomName) {
2143
+ if (roomVid.joined) leaveRoomVideo();
2144
+ roomVid.room = null;
2145
+ roomVid.self = null;
2146
+ roomLastRoster = [];
2147
+ roomPanel.classList.remove("is-open");
2148
+ return;
2149
+ }
2150
+ roomVid.room = roomName;
2151
+ roomVid.self = data.self || null;
2152
+ roomLastRoster = (data.members) || [];
2153
+ var n = roomLastRoster.length;
2154
+ roomMeta.textContent = roomName +
2155
+ (roomVid.self ? " \xB7 you " + roomVid.self : "") +
2156
+ " \xB7 " + n + " other" + (n === 1 ? "" : "s");
2157
+ roomPanel.classList.add("is-open");
2158
+ roomJoinBtn.disabled = roomVid.joined || !roomVid.self;
2159
+ roomLeaveBtn.disabled = !roomVid.joined;
2160
+ if (roomVid.joined) meshWithMembers(roomLastRoster);
2161
+ } catch(e){}
2162
+ }
2163
+ refreshRoom();
2164
+ setInterval(refreshRoom, 4000);
1819
2165
  })();
1820
2166
  </script>
1821
2167
  </body>
@@ -1898,8 +2244,8 @@ function createRoomBridge(name) {
1898
2244
  let session;
1899
2245
  const messageQueue = [];
1900
2246
  const messageWaiters = [];
1901
- const signalBoxes = /* @__PURE__ */ new Map();
1902
- const signalWaiters = /* @__PURE__ */ new Map();
2247
+ const signalQueue = [];
2248
+ const signalWaiters = [];
1903
2249
  const drainMessage = (m) => {
1904
2250
  const safe = { ...m, text: sanitizePeerText(m.text) };
1905
2251
  const waiter = messageWaiters.shift();
@@ -1913,23 +2259,15 @@ function createRoomBridge(name) {
1913
2259
  }
1914
2260
  };
1915
2261
  const drainSignal = (from, frame) => {
1916
- const waiters = signalWaiters.get(from);
1917
- if (waiters !== void 0 && waiters.length > 0) {
1918
- const w = waiters.shift();
1919
- if (w !== void 0) {
1920
- if (waiters.length === 0) signalWaiters.delete(from);
1921
- w(frame);
1922
- return;
1923
- }
1924
- }
1925
- let box = signalBoxes.get(from);
1926
- if (box === void 0) {
1927
- box = [];
1928
- signalBoxes.set(from, box);
2262
+ const tagged = { from, rtc: frame };
2263
+ const waiter = signalWaiters.shift();
2264
+ if (waiter !== void 0) {
2265
+ waiter(tagged);
2266
+ return;
1929
2267
  }
1930
- box.push(frame);
1931
- if (box.length > MAX_QUEUED_ROOM_SIGNALS) {
1932
- box.splice(0, box.length - MAX_QUEUED_ROOM_SIGNALS);
2268
+ signalQueue.push(tagged);
2269
+ if (signalQueue.length > MAX_QUEUED_ROOM_SIGNALS) {
2270
+ signalQueue.splice(0, signalQueue.length - MAX_QUEUED_ROOM_SIGNALS);
1933
2271
  }
1934
2272
  };
1935
2273
  return {
@@ -1972,27 +2310,17 @@ function createRoomBridge(name) {
1972
2310
  sendSignal(handle2, frame) {
1973
2311
  session?.sendSignal(handle2, frame);
1974
2312
  },
1975
- async pollSignal(handle2, timeoutMs) {
1976
- const box = signalBoxes.get(handle2);
1977
- if (box !== void 0 && box.length > 0) return box.shift() ?? null;
2313
+ async pollSignal(_handle, timeoutMs) {
2314
+ if (signalQueue.length > 0) return signalQueue.shift() ?? null;
1978
2315
  return new Promise((resolve) => {
1979
2316
  const timer = setTimeout(() => {
1980
- const waiters2 = signalWaiters.get(handle2);
1981
- if (waiters2 !== void 0) {
1982
- const idx = waiters2.indexOf(resolve);
1983
- if (idx >= 0) waiters2.splice(idx, 1);
1984
- if (waiters2.length === 0) signalWaiters.delete(handle2);
1985
- }
2317
+ const idx = signalWaiters.indexOf(resolve);
2318
+ if (idx >= 0) signalWaiters.splice(idx, 1);
1986
2319
  resolve(null);
1987
2320
  }, timeoutMs);
1988
- let waiters = signalWaiters.get(handle2);
1989
- if (waiters === void 0) {
1990
- waiters = [];
1991
- signalWaiters.set(handle2, waiters);
1992
- }
1993
- waiters.push((f) => {
2321
+ signalWaiters.push((s) => {
1994
2322
  clearTimeout(timer);
1995
- resolve(f);
2323
+ resolve(s);
1996
2324
  });
1997
2325
  });
1998
2326
  }
@@ -2312,7 +2640,7 @@ async function handle(req, res, opts) {
2312
2640
  }
2313
2641
 
2314
2642
  // src/cli.ts
2315
- var VERSION = "0.7.0";
2643
+ var VERSION = "0.7.2";
2316
2644
  function parsePort(raw) {
2317
2645
  const n = Number(raw);
2318
2646
  if (!Number.isInteger(n) || n < 1 || n > 65535) return void 0;
@@ -2788,10 +3116,16 @@ async function cmdLiveViaRelay(profile, to) {
2788
3116
  ` \xB7 relayed to ${sanitizePeerText(l.hello.handle)} (${l.hello.league}${qual} \xB7 ${l.hello.harness}) ${usageMark(l.hello)}${idMark(l.hello)}
2789
3117
  `
2790
3118
  );
2791
- l.onMessage((m) => {
2792
- process2.stdout.write(` <${sanitizePeerText(l.hello.handle)}> ${sanitizePeerText(m.text)}
3119
+ });
3120
+ pairing.onMessage((from, m) => {
3121
+ process2.stdout.write(` <${sanitizePeerText(from)}> ${sanitizePeerText(m.text)}
2793
3122
  `);
2794
- });
3123
+ });
3124
+ pairing.onQueued((from, n) => {
3125
+ process2.stdout.write(
3126
+ ` \xB7 ${sanitizePeerText(from)} sent a message (${n} queued) \u2014 /open ${sanitizePeerText(from)} to read
3127
+ `
3128
+ );
2795
3129
  });
2796
3130
  pairing.add(link);
2797
3131
  process2.stdout.write(" type to chat \xB7 /quit\n");
@@ -2870,10 +3204,16 @@ async function cmdLive(dating, any, to, keepAlive, viaRelay) {
2870
3204
  ` \xB7 matched ${sanitizePeerText(link.hello.handle)} (${link.hello.league}${qual} \xB7 ${link.hello.harness}) ${usageMark(link.hello)}${idMark(link.hello)}
2871
3205
  `
2872
3206
  );
2873
- link.onMessage((m) => {
2874
- process2.stdout.write(` <${sanitizePeerText(link.hello.handle)}> ${sanitizePeerText(m.text)}
3207
+ });
3208
+ pairing.onMessage((from, m) => {
3209
+ process2.stdout.write(` <${sanitizePeerText(from)}> ${sanitizePeerText(m.text)}
2875
3210
  `);
2876
- });
3211
+ });
3212
+ pairing.onQueued((from, n) => {
3213
+ process2.stdout.write(
3214
+ ` \xB7 ${sanitizePeerText(from)} sent a message (${n} queued) \u2014 /open ${sanitizePeerText(from)} to read
3215
+ `
3216
+ );
2877
3217
  });
2878
3218
  const { topics, acceptLeague } = discoveryScope(profile.league, any);
2879
3219
  const session = await startDiscovery({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibedate",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "Dating by tokens — matched by usage league across agentic CLIs. Raw usage stays local; only your league is shared. CLI + local web app + MCP. Local-first.",
5
5
  "type": "module",
6
6
  "license": "MIT",