vibedate 0.3.1 → 0.4.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.
- package/README.md +16 -10
- package/dist/{chunk-3VT4EOBX.js → chunk-43IYNWES.js} +401 -212
- package/dist/{chunk-OVO2CQ7X.js → chunk-ZB56ZBAR.js} +2 -2
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +485 -73
- package/dist/index.d.ts +130 -31
- package/dist/index.js +13 -3
- package/dist/mcp.js +2 -2
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runMcp
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ZB56ZBAR.js";
|
|
5
5
|
import {
|
|
6
6
|
CANDIDATES,
|
|
7
7
|
LIVE_NOTICE,
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
leagueTopic,
|
|
18
18
|
leaguesWithin,
|
|
19
19
|
loadBlocklist,
|
|
20
|
+
loadOrCreateIdentity,
|
|
20
21
|
loadPeers,
|
|
21
22
|
loadProfile,
|
|
22
23
|
matches,
|
|
@@ -27,8 +28,9 @@ import {
|
|
|
27
28
|
resolveHandle,
|
|
28
29
|
sameHandle,
|
|
29
30
|
saveHandle,
|
|
31
|
+
signHelloClaims,
|
|
30
32
|
startDiscovery
|
|
31
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-43IYNWES.js";
|
|
32
34
|
|
|
33
35
|
// src/cli.ts
|
|
34
36
|
import readline from "readline";
|
|
@@ -108,6 +110,7 @@ function createPairing() {
|
|
|
108
110
|
|
|
109
111
|
// src/server.ts
|
|
110
112
|
import http from "http";
|
|
113
|
+
import { randomUUID } from "crypto";
|
|
111
114
|
import { makeEvent, notify as vibeCoreNotify } from "@pooriaarab/vibe-core";
|
|
112
115
|
|
|
113
116
|
// src/web-app-html.ts
|
|
@@ -543,6 +546,80 @@ var webAppHtml = `<!DOCTYPE html>
|
|
|
543
546
|
background: var(--danger); color: #2a0a0c;
|
|
544
547
|
}
|
|
545
548
|
.video-modal .vhangup:hover{ filter: brightness(1.06); }
|
|
549
|
+
.live-panel .lp-legend{ font-size: .66rem; color: var(--muted-2); margin-top: 8px; line-height: 1.4; }
|
|
550
|
+
.incoming-call{
|
|
551
|
+
position: fixed; inset: 0; z-index: 65;
|
|
552
|
+
display: flex; align-items: center; justify-content: center;
|
|
553
|
+
background: rgba(12,7,15,.6); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
|
|
554
|
+
opacity: 0; pointer-events: none; transition: opacity var(--dur-med) ease;
|
|
555
|
+
}
|
|
556
|
+
.incoming-call.is-open{ opacity: 1; pointer-events: auto; }
|
|
557
|
+
.incoming-call .ic-card{
|
|
558
|
+
background: linear-gradient(165deg, var(--bg-card), var(--bg-card-2));
|
|
559
|
+
border: 1px solid var(--border-2); border-radius: 24px; box-shadow: var(--shadow-3);
|
|
560
|
+
padding: 30px 28px; max-width: 320px; text-align: center;
|
|
561
|
+
transform: scale(.85) translateY(10px); transition: transform var(--dur-med) var(--ease-bounce);
|
|
562
|
+
}
|
|
563
|
+
.incoming-call.is-open .ic-card{ transform: scale(1) translateY(0); }
|
|
564
|
+
.incoming-call h3{ margin: 0 0 6px; font-size: 1.15rem; font-weight: 800; word-break: break-all; }
|
|
565
|
+
.incoming-call p{ color: var(--muted); font-size: .85rem; margin: 0 0 18px; }
|
|
566
|
+
.incoming-call .ic-actions{ display: flex; gap: 10px; }
|
|
567
|
+
|
|
568
|
+
/* ---- live text chat (browser <-> PeerLink over /live/message) ---- */
|
|
569
|
+
.live-actions{ display: flex; gap: 6px; flex-shrink: 0; }
|
|
570
|
+
.live-row .cbtn{
|
|
571
|
+
border: 1px solid var(--border-2); border-radius: 9px; padding: 7px 12px;
|
|
572
|
+
font-weight: 700; font-size: .76rem; background: transparent; color: var(--muted);
|
|
573
|
+
transition: color var(--dur-fast) ease, border-color var(--dur-fast) ease, transform var(--dur-fast) var(--ease-out);
|
|
574
|
+
flex-shrink: 0;
|
|
575
|
+
}
|
|
576
|
+
.live-row .cbtn:hover{ color: var(--fg); border-color: var(--muted-2); }
|
|
577
|
+
.live-row .cbtn:active{ transform: scale(.95); }
|
|
578
|
+
.live-row .cbtn.has-unread{ color: var(--coral); border-color: var(--coral); }
|
|
579
|
+
.chat-panel{
|
|
580
|
+
position: fixed; right: 18px; bottom: 18px; z-index: 45;
|
|
581
|
+
width: min(320px, calc(100vw - 36px));
|
|
582
|
+
background: linear-gradient(180deg, var(--bg-card), var(--bg-card-2));
|
|
583
|
+
border: 1px solid var(--border-2); border-radius: 16px; box-shadow: var(--shadow-2);
|
|
584
|
+
display: none; flex-direction: column; overflow: hidden;
|
|
585
|
+
}
|
|
586
|
+
.chat-panel.is-open{ display: flex; animation: rise var(--dur-med) var(--ease-out); }
|
|
587
|
+
.chat-panel .cp-head{
|
|
588
|
+
display: flex; align-items: center; justify-content: space-between; gap: 10px;
|
|
589
|
+
padding: 11px 14px; border-bottom: 1px solid var(--border);
|
|
590
|
+
}
|
|
591
|
+
.chat-panel .cp-title{ font-size: .84rem; font-weight: 700; word-break: break-all; }
|
|
592
|
+
.chat-panel .cp-sub{ font-size: .68rem; color: var(--muted-2); margin-top: 2px; }
|
|
593
|
+
.chat-panel .cp-close{
|
|
594
|
+
border: 0; background: transparent; color: var(--muted-2);
|
|
595
|
+
font-size: 1.15rem; line-height: 1; padding: 4px; flex-shrink: 0;
|
|
596
|
+
}
|
|
597
|
+
.chat-panel .cp-close:hover{ color: var(--fg); }
|
|
598
|
+
.chat-panel .cp-msgs{
|
|
599
|
+
padding: 12px 14px; min-height: 120px; max-height: 260px; overflow-y: auto;
|
|
600
|
+
display: flex; flex-direction: column; gap: 7px;
|
|
601
|
+
}
|
|
602
|
+
.chat-panel .cp-empty{ font-size: .76rem; color: var(--muted-2); text-align: center; margin: auto; }
|
|
603
|
+
.cp-msg{
|
|
604
|
+
max-width: 85%; padding: 7px 11px; border-radius: 13px;
|
|
605
|
+
font-size: .82rem; line-height: 1.4; word-break: break-word; white-space: pre-wrap;
|
|
606
|
+
}
|
|
607
|
+
.cp-msg.them{ align-self: flex-start; background: rgba(248,239,232,.07); border: 1px solid var(--border); }
|
|
608
|
+
.cp-msg.you{ align-self: flex-end; background: rgba(255,122,104,.16); border: 1px solid rgba(255,122,104,.32); }
|
|
609
|
+
.cp-msg.sys{ align-self: center; background: transparent; border: 0; color: var(--muted-2); font-size: .72rem; padding: 2px 6px; }
|
|
610
|
+
.chat-panel .cp-inputrow{ display: flex; gap: 8px; padding: 10px; border-top: 1px solid var(--border); }
|
|
611
|
+
.chat-panel .cp-inputrow input{
|
|
612
|
+
flex: 1; border: 1px solid var(--border-2); border-radius: 10px; background: rgba(0,0,0,.22);
|
|
613
|
+
color: var(--fg); padding: 9px 11px; font: inherit; font-size: .82rem; outline: none; min-width: 0;
|
|
614
|
+
}
|
|
615
|
+
.chat-panel .cp-inputrow input:focus{ border-color: var(--coral); }
|
|
616
|
+
.chat-panel .cp-send{
|
|
617
|
+
border: 0; border-radius: 10px; padding: 9px 14px; font-weight: 700; font-size: .8rem;
|
|
618
|
+
background: linear-gradient(180deg, var(--coral), var(--coral-dim)); color: #2a1109;
|
|
619
|
+
transition: filter var(--dur-fast) ease, transform var(--dur-fast) var(--ease-out);
|
|
620
|
+
}
|
|
621
|
+
.chat-panel .cp-send:hover{ filter: brightness(1.06); }
|
|
622
|
+
.chat-panel .cp-send:active{ transform: scale(.95); }
|
|
546
623
|
</style>
|
|
547
624
|
</head>
|
|
548
625
|
<body>
|
|
@@ -687,9 +764,36 @@ var webAppHtml = `<!DOCTYPE html>
|
|
|
687
764
|
</div>
|
|
688
765
|
</div>
|
|
689
766
|
|
|
690
|
-
<aside class="live-panel" id="livePanel" aria-label="Live
|
|
691
|
-
<div class="lp-title"><span class="lp-dot" aria-hidden="true"></span> Live
|
|
767
|
+
<aside class="live-panel" id="livePanel" aria-label="Live peers">
|
|
768
|
+
<div class="lp-title"><span class="lp-dot" aria-hidden="true"></span> Live peers</div>
|
|
692
769
|
<div id="liveRows"></div>
|
|
770
|
+
<div class="lp-legend">✓ usage verified · 🔑 identity verified — call someone, or wait for a call</div>
|
|
771
|
+
</aside>
|
|
772
|
+
|
|
773
|
+
<div class="incoming-call" id="incomingCall" role="dialog" aria-modal="true" aria-label="Incoming call">
|
|
774
|
+
<div class="ic-card">
|
|
775
|
+
<h3 id="incomingCaller">@peer</h3>
|
|
776
|
+
<p>is calling you — video chat</p>
|
|
777
|
+
<div class="ic-actions">
|
|
778
|
+
<button class="btn btn-ghost" id="declineBtn" type="button">Decline</button>
|
|
779
|
+
<button class="btn btn-primary" id="acceptBtn" type="button">Accept</button>
|
|
780
|
+
</div>
|
|
781
|
+
</div>
|
|
782
|
+
</div>
|
|
783
|
+
|
|
784
|
+
<aside class="chat-panel" id="chatPanel" aria-label="Text chat">
|
|
785
|
+
<div class="cp-head">
|
|
786
|
+
<div>
|
|
787
|
+
<div class="cp-title" id="chatTitle">@peer</div>
|
|
788
|
+
<div class="cp-sub" id="chatSub">live over the P2P link · text only</div>
|
|
789
|
+
</div>
|
|
790
|
+
<button class="cp-close" id="chatClose" type="button" aria-label="Close chat">×</button>
|
|
791
|
+
</div>
|
|
792
|
+
<div class="cp-msgs" id="chatMsgs"></div>
|
|
793
|
+
<div class="cp-inputrow">
|
|
794
|
+
<input id="chatInput" type="text" maxlength="4000" placeholder="message…" autocomplete="off">
|
|
795
|
+
<button class="cp-send" id="chatSend" type="button">Send</button>
|
|
796
|
+
</div>
|
|
693
797
|
</aside>
|
|
694
798
|
|
|
695
799
|
<div class="video-modal" id="videoModal" role="dialog" aria-modal="true" aria-label="Video call">
|
|
@@ -996,9 +1100,15 @@ var webAppHtml = `<!DOCTYPE html>
|
|
|
996
1100
|
var remoteVideo = document.getElementById("remoteVideo");
|
|
997
1101
|
var remoteLabel = document.getElementById("remoteLabel");
|
|
998
1102
|
var hangupBtn = document.getElementById("hangupBtn");
|
|
1103
|
+
var incomingCallEl = document.getElementById("incomingCall");
|
|
1104
|
+
var incomingCaller = document.getElementById("incomingCaller");
|
|
1105
|
+
var acceptBtn = document.getElementById("acceptBtn");
|
|
1106
|
+
var declineBtn = document.getElementById("declineBtn");
|
|
999
1107
|
|
|
1000
1108
|
// One in-flight call's state. remoteHandle === null means idle.
|
|
1001
1109
|
var rtc = { pc: null, localStream: null, remoteHandle: null, role: null };
|
|
1110
|
+
// An incoming offer awaiting the user's accept/decline (never auto-answered).
|
|
1111
|
+
var pendingOffer = null;
|
|
1002
1112
|
var knownPeers = [];
|
|
1003
1113
|
var idleIdx = 0;
|
|
1004
1114
|
|
|
@@ -1115,7 +1225,8 @@ var webAppHtml = `<!DOCTYPE html>
|
|
|
1115
1225
|
}
|
|
1116
1226
|
|
|
1117
1227
|
// Idle listener: when not in a call, watch known peers round-robin so an
|
|
1118
|
-
// incoming offer (the peer clicked THEIR
|
|
1228
|
+
// incoming offer (the peer clicked THEIR Call button) is noticed. Offers are
|
|
1229
|
+
// never auto-answered \u2014 the caller is named and the user accepts or declines.
|
|
1119
1230
|
async function idleLoop(){
|
|
1120
1231
|
while (true) {
|
|
1121
1232
|
if (rtc.remoteHandle || knownPeers.length === 0) { await sleep(1000); continue; }
|
|
@@ -1127,7 +1238,12 @@ var webAppHtml = `<!DOCTYPE html>
|
|
|
1127
1238
|
var data = await res.json();
|
|
1128
1239
|
var f = data && data.frame;
|
|
1129
1240
|
if (f && f.t === "rtc-offer" && !rtc.pc && !rtc.remoteHandle) {
|
|
1130
|
-
|
|
1241
|
+
// One prompt at a time: a re-offer from the SAME caller refreshes
|
|
1242
|
+
// the stored sdp; an offer from someone else while a prompt is up
|
|
1243
|
+
// is dropped (their call simply fails to connect).
|
|
1244
|
+
if (pendingOffer && pendingOffer.handle !== peer.handle) continue;
|
|
1245
|
+
pendingOffer = { handle: peer.handle, sdp: f.sdp };
|
|
1246
|
+
showIncomingPrompt(peer.handle);
|
|
1131
1247
|
}
|
|
1132
1248
|
}
|
|
1133
1249
|
} catch(e){ /* abort/timeout \u2014 loop to next peer */ }
|
|
@@ -1135,6 +1251,22 @@ var webAppHtml = `<!DOCTYPE html>
|
|
|
1135
1251
|
}
|
|
1136
1252
|
idleLoop();
|
|
1137
1253
|
|
|
1254
|
+
function showIncomingPrompt(handle){
|
|
1255
|
+
incomingCaller.textContent = handle;
|
|
1256
|
+
incomingCallEl.classList.add("is-open");
|
|
1257
|
+
}
|
|
1258
|
+
function clearIncomingPrompt(){
|
|
1259
|
+
pendingOffer = null;
|
|
1260
|
+
incomingCallEl.classList.remove("is-open");
|
|
1261
|
+
}
|
|
1262
|
+
acceptBtn.addEventListener("click", function(){
|
|
1263
|
+
var p = pendingOffer;
|
|
1264
|
+
clearIncomingPrompt();
|
|
1265
|
+
if (!p) return;
|
|
1266
|
+
answerIncomingOffer(p.handle, p.sdp).catch(function(){ hangup(); });
|
|
1267
|
+
});
|
|
1268
|
+
declineBtn.addEventListener("click", clearIncomingPrompt);
|
|
1269
|
+
|
|
1138
1270
|
function hangup(){
|
|
1139
1271
|
try { if (rtc.pc) rtc.pc.close(); } catch(e){}
|
|
1140
1272
|
rtc.pc = null;
|
|
@@ -1150,7 +1282,171 @@ var webAppHtml = `<!DOCTYPE html>
|
|
|
1150
1282
|
}
|
|
1151
1283
|
hangupBtn.addEventListener("click", hangup);
|
|
1152
1284
|
|
|
1153
|
-
|
|
1285
|
+
/* ---- Live text chat: msg frames over the same PeerLinks, via /live/message ----
|
|
1286
|
+
* One long-poll loop per connected peer surfaces incoming texts immediately,
|
|
1287
|
+
* no matter which conversation is open. Outbound texts POST {handle, text};
|
|
1288
|
+
* the server re-validates through the frame allowlist and mints id/at. */
|
|
1289
|
+
var chatPanel = document.getElementById("chatPanel");
|
|
1290
|
+
var chatTitle = document.getElementById("chatTitle");
|
|
1291
|
+
var chatSub = document.getElementById("chatSub");
|
|
1292
|
+
var chatMsgs = document.getElementById("chatMsgs");
|
|
1293
|
+
var chatInput = document.getElementById("chatInput");
|
|
1294
|
+
var chatSend = document.getElementById("chatSend");
|
|
1295
|
+
var chatClose = document.getElementById("chatClose");
|
|
1296
|
+
|
|
1297
|
+
var chatWith = null; // handle of the open conversation (null = closed)
|
|
1298
|
+
var conversations = {}; // handle -> [{from:"you"|"them"|"sys", text}]
|
|
1299
|
+
var unread = {}; // handle -> count of unseen incoming messages
|
|
1300
|
+
var chatLoops = {}; // handle -> true while its poll loop is running
|
|
1301
|
+
var MAX_CHAT_KEPT = 200; // per-conversation local cap (mirrors the server)
|
|
1302
|
+
|
|
1303
|
+
function fetchMessage(handle, timeoutMs){
|
|
1304
|
+
var ctrl = new AbortController();
|
|
1305
|
+
var t = setTimeout(function(){ ctrl.abort(); }, timeoutMs);
|
|
1306
|
+
return fetch("/live/message?handle=" + encodeURIComponent(handle), { signal: ctrl.signal })
|
|
1307
|
+
.then(function(r){ clearTimeout(t); return r; })
|
|
1308
|
+
.catch(function(e){ clearTimeout(t); throw e; });
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
function postChat(handle, text){
|
|
1312
|
+
return fetch("/live/message", {
|
|
1313
|
+
method: "POST",
|
|
1314
|
+
headers: { "content-type": "application/json" },
|
|
1315
|
+
body: JSON.stringify({ handle: handle, text: text })
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
function peerKnown(handle){
|
|
1320
|
+
for (var i = 0; i < knownPeers.length; i++){ if (knownPeers[i].handle === handle) return true; }
|
|
1321
|
+
return false;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
function pushChat(handle, entry){
|
|
1325
|
+
var conv = conversations[handle] || (conversations[handle] = []);
|
|
1326
|
+
conv.push(entry);
|
|
1327
|
+
if (conv.length > MAX_CHAT_KEPT) conv.splice(0, conv.length - MAX_CHAT_KEPT);
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
// One long-poll loop per connected peer: started when the peer is first
|
|
1331
|
+
// seen, stopped when it vanishes (its mailbox on the server is gone).
|
|
1332
|
+
function ensureChatLoop(handle){
|
|
1333
|
+
if (chatLoops[handle]) return;
|
|
1334
|
+
chatLoops[handle] = true;
|
|
1335
|
+
(async function(){
|
|
1336
|
+
while (peerKnown(handle)) {
|
|
1337
|
+
var res;
|
|
1338
|
+
try { res = await fetchMessage(handle, 30000); }
|
|
1339
|
+
catch(e){ await sleep(1000); continue; }
|
|
1340
|
+
if (!res || res.status !== 200) { await sleep(1000); continue; }
|
|
1341
|
+
var data = await res.json();
|
|
1342
|
+
var m = data && data.message;
|
|
1343
|
+
if (!m) continue; // timed out empty
|
|
1344
|
+
pushChat(handle, { from: "them", text: m.text });
|
|
1345
|
+
if (chatWith === handle) {
|
|
1346
|
+
renderChat();
|
|
1347
|
+
} else {
|
|
1348
|
+
unread[handle] = (unread[handle] || 0) + 1;
|
|
1349
|
+
renderLiveRows(knownPeers);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
delete chatLoops[handle];
|
|
1353
|
+
})();
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
function renderChat(){
|
|
1357
|
+
chatMsgs.innerHTML = "";
|
|
1358
|
+
var conv = (chatWith && conversations[chatWith]) || [];
|
|
1359
|
+
if (conv.length === 0) {
|
|
1360
|
+
var empty = document.createElement("div");
|
|
1361
|
+
empty.className = "cp-empty";
|
|
1362
|
+
empty.textContent = "No messages yet \u2014 say hi.";
|
|
1363
|
+
chatMsgs.appendChild(empty);
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
conv.forEach(function(m){
|
|
1367
|
+
var el = document.createElement("div");
|
|
1368
|
+
el.className = "cp-msg " + (m.from === "you" ? "you" : m.from === "sys" ? "sys" : "them");
|
|
1369
|
+
// textContent only \u2014 a peer's text is never parsed as HTML.
|
|
1370
|
+
el.textContent = m.text;
|
|
1371
|
+
chatMsgs.appendChild(el);
|
|
1372
|
+
});
|
|
1373
|
+
chatMsgs.scrollTop = chatMsgs.scrollHeight;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
function openChat(handle){
|
|
1377
|
+
chatWith = handle;
|
|
1378
|
+
delete unread[handle];
|
|
1379
|
+
chatTitle.textContent = handle;
|
|
1380
|
+
chatSub.textContent = peerKnown(handle)
|
|
1381
|
+
? "live over the P2P link \xB7 text only"
|
|
1382
|
+
: "peer offline \xB7 messages will not deliver";
|
|
1383
|
+
chatPanel.classList.add("is-open");
|
|
1384
|
+
renderChat();
|
|
1385
|
+
renderLiveRows(knownPeers);
|
|
1386
|
+
chatInput.focus();
|
|
1387
|
+
}
|
|
1388
|
+
chatClose.addEventListener("click", function(){
|
|
1389
|
+
chatWith = null;
|
|
1390
|
+
chatPanel.classList.remove("is-open");
|
|
1391
|
+
});
|
|
1392
|
+
|
|
1393
|
+
function sendChat(){
|
|
1394
|
+
var target = chatWith;
|
|
1395
|
+
var text = chatInput.value.trim();
|
|
1396
|
+
if (!target || text === "") return;
|
|
1397
|
+
chatInput.value = "";
|
|
1398
|
+
postChat(target, text).then(function(res){
|
|
1399
|
+
// Honest failure \u2014 never show a message as sent when it wasn't.
|
|
1400
|
+
pushChat(target, res && res.status === 200
|
|
1401
|
+
? { from: "you", text: text }
|
|
1402
|
+
: { from: "sys", text: "(not delivered \u2014 peer offline?)" });
|
|
1403
|
+
if (chatWith === target) renderChat();
|
|
1404
|
+
}).catch(function(){
|
|
1405
|
+
pushChat(target, { from: "sys", text: "(not delivered \u2014 server unreachable)" });
|
|
1406
|
+
if (chatWith === target) renderChat();
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
chatSend.addEventListener("click", sendChat);
|
|
1410
|
+
chatInput.addEventListener("keydown", function(e){ if (e.key === "Enter") sendChat(); });
|
|
1411
|
+
|
|
1412
|
+
// Render the live-peers rows: one per connected peer with its verification
|
|
1413
|
+
// marks, a Chat button (opens the conversation), and a Call button that
|
|
1414
|
+
// rings THAT peer specifically.
|
|
1415
|
+
function renderLiveRows(peers){
|
|
1416
|
+
liveRows.innerHTML = "";
|
|
1417
|
+
peers.forEach(function(p){
|
|
1418
|
+
var row = document.createElement("div");
|
|
1419
|
+
row.className = "live-row";
|
|
1420
|
+
var who = document.createElement("div");
|
|
1421
|
+
// Marks from the peer's hello, shown only when present: \u2713 usage
|
|
1422
|
+
// verified (real local logs), \u{1F511} identity-verified (signed hello).
|
|
1423
|
+
var marks = (p.verified === true ? " \u2713" : "") + (p.identityVerified === true ? " \u{1F511}" : "");
|
|
1424
|
+
var h = document.createElement("div"); h.className = "h"; h.textContent = p.handle + marks;
|
|
1425
|
+
var s = document.createElement("div"); s.className = "s"; s.textContent = p.league + " \xB7 " + p.harness;
|
|
1426
|
+
who.appendChild(h); who.appendChild(s);
|
|
1427
|
+
var actions = document.createElement("div");
|
|
1428
|
+
actions.className = "live-actions";
|
|
1429
|
+
var chatBtn = document.createElement("button");
|
|
1430
|
+
chatBtn.className = "cbtn" + (unread[p.handle] ? " has-unread" : "");
|
|
1431
|
+
chatBtn.type = "button";
|
|
1432
|
+
chatBtn.textContent = "Chat" + (unread[p.handle] ? " (" + unread[p.handle] + ")" : "");
|
|
1433
|
+
chatBtn.addEventListener("click", function(){ openChat(p.handle); });
|
|
1434
|
+
var btn = document.createElement("button");
|
|
1435
|
+
btn.className = "vbtn"; btn.type = "button"; btn.textContent = "Call";
|
|
1436
|
+
btn.addEventListener("click", function(){
|
|
1437
|
+
if (rtc.remoteHandle || pendingOffer) return; // already in a call / prompt
|
|
1438
|
+
btn.disabled = true;
|
|
1439
|
+
// DIRECTED call: the rtc-offer is relayed only to this peer's handle.
|
|
1440
|
+
startCallAsOfferer(p.handle).catch(function(){ hangup(); });
|
|
1441
|
+
});
|
|
1442
|
+
actions.appendChild(chatBtn);
|
|
1443
|
+
actions.appendChild(btn);
|
|
1444
|
+
row.appendChild(who);
|
|
1445
|
+
row.appendChild(actions);
|
|
1446
|
+
liveRows.appendChild(row);
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1154
1450
|
async function refreshPeers(){
|
|
1155
1451
|
try {
|
|
1156
1452
|
var res = await fetch("/api/live/peers");
|
|
@@ -1158,26 +1454,15 @@ var webAppHtml = `<!DOCTYPE html>
|
|
|
1158
1454
|
var data = await res.json();
|
|
1159
1455
|
var peers = (data && data.peers) || [];
|
|
1160
1456
|
knownPeers = peers;
|
|
1457
|
+
peers.forEach(function(p){ ensureChatLoop(p.handle); });
|
|
1458
|
+
if (chatWith) {
|
|
1459
|
+
chatSub.textContent = peerKnown(chatWith)
|
|
1460
|
+
? "live over the P2P link \xB7 text only"
|
|
1461
|
+
: "peer offline \xB7 messages will not deliver";
|
|
1462
|
+
}
|
|
1161
1463
|
if (peers.length === 0) { livePanel.classList.remove("is-open"); return; }
|
|
1162
1464
|
livePanel.classList.add("is-open");
|
|
1163
|
-
|
|
1164
|
-
peers.forEach(function(p){
|
|
1165
|
-
var row = document.createElement("div");
|
|
1166
|
-
row.className = "live-row";
|
|
1167
|
-
var who = document.createElement("div");
|
|
1168
|
-
var h = document.createElement("div"); h.className = "h"; h.textContent = p.handle;
|
|
1169
|
-
var s = document.createElement("div"); s.className = "s"; s.textContent = p.league + " \xB7 " + p.harness;
|
|
1170
|
-
who.appendChild(h); who.appendChild(s);
|
|
1171
|
-
var btn = document.createElement("button");
|
|
1172
|
-
btn.className = "vbtn"; btn.type = "button"; btn.textContent = "Video";
|
|
1173
|
-
btn.addEventListener("click", function(){
|
|
1174
|
-
if (rtc.remoteHandle) return; // already in a call
|
|
1175
|
-
btn.disabled = true;
|
|
1176
|
-
startCallAsOfferer(p.handle).catch(function(){ btn.disabled = false; });
|
|
1177
|
-
});
|
|
1178
|
-
row.appendChild(who); row.appendChild(btn);
|
|
1179
|
-
liveRows.appendChild(row);
|
|
1180
|
-
});
|
|
1465
|
+
renderLiveRows(peers);
|
|
1181
1466
|
} catch(e){}
|
|
1182
1467
|
}
|
|
1183
1468
|
refreshPeers();
|
|
@@ -1188,19 +1473,37 @@ var webAppHtml = `<!DOCTYPE html>
|
|
|
1188
1473
|
</html>`;
|
|
1189
1474
|
|
|
1190
1475
|
// src/server.ts
|
|
1476
|
+
var MAX_QUEUED_MESSAGES = 200;
|
|
1191
1477
|
function createLiveBridge() {
|
|
1192
1478
|
const boxes = /* @__PURE__ */ new Map();
|
|
1193
1479
|
const bridge = {
|
|
1194
1480
|
get peers() {
|
|
1195
|
-
return [...boxes.values()].map((m) =>
|
|
1481
|
+
return [...boxes.values()].map((m) => {
|
|
1482
|
+
const h = m.link.hello;
|
|
1483
|
+
return {
|
|
1484
|
+
handle: h.handle,
|
|
1485
|
+
league: h.league,
|
|
1486
|
+
harness: h.harness,
|
|
1487
|
+
...h.verified !== void 0 ? { verified: h.verified } : {},
|
|
1488
|
+
...h.identityVerified !== void 0 ? { identityVerified: h.identityVerified } : {}
|
|
1489
|
+
};
|
|
1490
|
+
});
|
|
1196
1491
|
},
|
|
1197
1492
|
addLink(link) {
|
|
1198
1493
|
const handle2 = link.hello.handle;
|
|
1199
|
-
boxes.set(handle2, { link, incoming: [] });
|
|
1494
|
+
boxes.set(handle2, { link, incoming: [], messages: [] });
|
|
1200
1495
|
link.onSignal((f) => {
|
|
1201
1496
|
const mb = boxes.get(handle2);
|
|
1202
1497
|
if (mb) mb.incoming.push(f);
|
|
1203
1498
|
});
|
|
1499
|
+
link.onMessage((m) => {
|
|
1500
|
+
const mb = boxes.get(handle2);
|
|
1501
|
+
if (!mb) return;
|
|
1502
|
+
mb.messages.push(m);
|
|
1503
|
+
if (mb.messages.length > MAX_QUEUED_MESSAGES) {
|
|
1504
|
+
mb.messages.splice(0, mb.messages.length - MAX_QUEUED_MESSAGES);
|
|
1505
|
+
}
|
|
1506
|
+
});
|
|
1204
1507
|
link.onClose(() => {
|
|
1205
1508
|
const cur = boxes.get(handle2);
|
|
1206
1509
|
if (cur && cur.link === link) boxes.delete(handle2);
|
|
@@ -1221,6 +1524,22 @@ function createLiveBridge() {
|
|
|
1221
1524
|
if (cur.incoming.length > 0) return cur.incoming.shift() ?? null;
|
|
1222
1525
|
}
|
|
1223
1526
|
return null;
|
|
1527
|
+
},
|
|
1528
|
+
sendMessage(handle2, text) {
|
|
1529
|
+
boxes.get(handle2)?.link.send(text);
|
|
1530
|
+
},
|
|
1531
|
+
async pollMessage(handle2, timeoutMs) {
|
|
1532
|
+
const mb = boxes.get(handle2);
|
|
1533
|
+
if (!mb) return null;
|
|
1534
|
+
if (mb.messages.length > 0) return mb.messages.shift() ?? null;
|
|
1535
|
+
const deadline = Date.now() + timeoutMs;
|
|
1536
|
+
while (Date.now() < deadline) {
|
|
1537
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
1538
|
+
const cur = boxes.get(handle2);
|
|
1539
|
+
if (!cur) return null;
|
|
1540
|
+
if (cur.messages.length > 0) return cur.messages.shift() ?? null;
|
|
1541
|
+
}
|
|
1542
|
+
return null;
|
|
1224
1543
|
}
|
|
1225
1544
|
};
|
|
1226
1545
|
return bridge;
|
|
@@ -1394,11 +1713,62 @@ async function handle(req, res, opts) {
|
|
|
1394
1713
|
sendJson(res, 200, { ok: true });
|
|
1395
1714
|
return;
|
|
1396
1715
|
}
|
|
1716
|
+
if (req.method === "GET" && pathname === "/live/message") {
|
|
1717
|
+
const live = opts.live;
|
|
1718
|
+
if (!live) {
|
|
1719
|
+
sendJson(res, 200, { message: null, reason: "live-not-attached" });
|
|
1720
|
+
return;
|
|
1721
|
+
}
|
|
1722
|
+
const handle2 = url.searchParams.get("handle") ?? "";
|
|
1723
|
+
if (handle2 === "") {
|
|
1724
|
+
sendJson(res, 400, { error: "missing handle" });
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
const message = await live.pollMessage(handle2, 25e3);
|
|
1728
|
+
if (req.destroyed || res.writableEnded) return;
|
|
1729
|
+
sendJson(res, 200, { message });
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
if (req.method === "POST" && pathname === "/live/message") {
|
|
1733
|
+
const live = opts.live;
|
|
1734
|
+
if (!live) {
|
|
1735
|
+
sendJson(res, 400, { error: "live-not-attached" });
|
|
1736
|
+
return;
|
|
1737
|
+
}
|
|
1738
|
+
const body = await readBody(req);
|
|
1739
|
+
let parsed = {};
|
|
1740
|
+
try {
|
|
1741
|
+
parsed = JSON.parse(body);
|
|
1742
|
+
} catch {
|
|
1743
|
+
sendJson(res, 400, { error: "invalid JSON body" });
|
|
1744
|
+
return;
|
|
1745
|
+
}
|
|
1746
|
+
const handle2 = typeof parsed["handle"] === "string" ? parsed["handle"] : "";
|
|
1747
|
+
if (handle2 === "") {
|
|
1748
|
+
sendJson(res, 400, { error: "missing handle" });
|
|
1749
|
+
return;
|
|
1750
|
+
}
|
|
1751
|
+
const text = parsed["text"];
|
|
1752
|
+
if (typeof text !== "string") {
|
|
1753
|
+
sendJson(res, 400, { error: "missing text" });
|
|
1754
|
+
return;
|
|
1755
|
+
}
|
|
1756
|
+
const reParsed = parseFrame(
|
|
1757
|
+
JSON.stringify({ t: "msg", id: randomUUID(), text, at: Date.now() })
|
|
1758
|
+
);
|
|
1759
|
+
if (reParsed === null || reParsed.t !== "msg") {
|
|
1760
|
+
sendJson(res, 400, { error: "invalid message text" });
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1763
|
+
live.sendMessage(handle2, reParsed.text);
|
|
1764
|
+
sendJson(res, 200, { ok: true });
|
|
1765
|
+
return;
|
|
1766
|
+
}
|
|
1397
1767
|
sendJson(res, 404, { error: "not found" });
|
|
1398
1768
|
}
|
|
1399
1769
|
|
|
1400
1770
|
// src/cli.ts
|
|
1401
|
-
var VERSION = "0.
|
|
1771
|
+
var VERSION = "0.4.1";
|
|
1402
1772
|
function parsePort(raw) {
|
|
1403
1773
|
const n = Number(raw);
|
|
1404
1774
|
if (!Number.isInteger(n) || n < 1 || n > 65535) return void 0;
|
|
@@ -1489,6 +1859,21 @@ function parseArgs(argv) {
|
|
|
1489
1859
|
function leagueLabel(name) {
|
|
1490
1860
|
return name === "below-1M" ? "below 1M (not yet in a league)" : `${name} League`;
|
|
1491
1861
|
}
|
|
1862
|
+
function formatTokens(n) {
|
|
1863
|
+
const trim = (v) => String(Math.round(v * 10) / 10);
|
|
1864
|
+
const abs = Math.abs(n);
|
|
1865
|
+
if (abs >= 1e9) return `${trim(n / 1e9)}B`;
|
|
1866
|
+
if (abs >= 1e6) return `${trim(n / 1e6)}M`;
|
|
1867
|
+
if (abs >= 1e3) return `${trim(n / 1e3)}k`;
|
|
1868
|
+
return String(n);
|
|
1869
|
+
}
|
|
1870
|
+
function verificationText(snapshot) {
|
|
1871
|
+
if (snapshot.source === "real") {
|
|
1872
|
+
return `verified: real usage \u2014 ${formatTokens(snapshot.totalTokens)} tokens from ${snapshot.harness} logs`;
|
|
1873
|
+
}
|
|
1874
|
+
if (snapshot.source === "self-report") return "self-reported (unverified)";
|
|
1875
|
+
return "demo (unverified)";
|
|
1876
|
+
}
|
|
1492
1877
|
function discoveryScope(myLeague, any) {
|
|
1493
1878
|
const names = any ? allLeagueNames() : leaguesWithin(myLeague, 1);
|
|
1494
1879
|
const ordered = [myLeague, ...names.filter((n) => n !== myLeague)];
|
|
@@ -1510,21 +1895,38 @@ function peerDirection(myLeague, peerLeague, sameBullet = "+") {
|
|
|
1510
1895
|
if (d < 0) return { bullet: "\u2193", qual: " \xB7 lower league" };
|
|
1511
1896
|
return { bullet: sameBullet, qual: "" };
|
|
1512
1897
|
}
|
|
1898
|
+
function buildHello(profile) {
|
|
1899
|
+
const claims = {
|
|
1900
|
+
handle: resolveHandle(),
|
|
1901
|
+
league: profile.league,
|
|
1902
|
+
harness: profile.harness,
|
|
1903
|
+
verified: profile.verified
|
|
1904
|
+
};
|
|
1905
|
+
return { ...claims, ...signHelloClaims(loadOrCreateIdentity(), claims) };
|
|
1906
|
+
}
|
|
1907
|
+
function usageMark(peer) {
|
|
1908
|
+
return peer.verified === true ? "\u2713" : "~";
|
|
1909
|
+
}
|
|
1910
|
+
function idMark(peer) {
|
|
1911
|
+
return peer.identityVerified === true ? " \u{1F511}" : "";
|
|
1912
|
+
}
|
|
1913
|
+
var MARKS_LEGEND = "marks: \u2713 usage verified (real local logs) \xB7 ~ unverified (self-report/demo/legacy) \xB7 \u{1F511} identity-verified (signed hello)";
|
|
1513
1914
|
async function cmdConnect() {
|
|
1514
1915
|
const harness = process2.env["VIBEDATING_HARNESS"] ?? "claude-code";
|
|
1515
1916
|
const handle2 = resolveHandle();
|
|
1516
1917
|
const snapshot = await readUsage(harness);
|
|
1517
1918
|
const profile = connectProfile(snapshot, handle2);
|
|
1919
|
+
const identity = loadOrCreateIdentity();
|
|
1518
1920
|
const lg = league(snapshot.totalTokens);
|
|
1519
1921
|
process2.stdout.write("\n");
|
|
1520
1922
|
process2.stdout.write(` ${leagueLabel(lg.name)}
|
|
1521
1923
|
`);
|
|
1522
1924
|
process2.stdout.write(` handle: ${profile.handle} \xB7 harness: ${profile.harness}
|
|
1523
1925
|
`);
|
|
1524
|
-
process2.stdout.write(
|
|
1525
|
-
|
|
1526
|
-
`
|
|
1527
|
-
|
|
1926
|
+
process2.stdout.write(` verification: ${verificationText(snapshot)}
|
|
1927
|
+
`);
|
|
1928
|
+
process2.stdout.write(` identity: ed25519 ${identity.publicKeyHex.slice(0, 12)}\u2026 \u2014 signs your hello (\u{1F511})
|
|
1929
|
+
`);
|
|
1528
1930
|
process2.stdout.write("\n");
|
|
1529
1931
|
process2.stdout.write(" \u2022 raw usage stays local \xB7 only league shared\n\n");
|
|
1530
1932
|
return 0;
|
|
@@ -1573,11 +1975,13 @@ async function cmdMatches(live) {
|
|
|
1573
1975
|
`);
|
|
1574
1976
|
process2.stdout.write(
|
|
1575
1977
|
`${livePeers.length} live peer${livePeers.length === 1 ? "" : "s"} in range (discovered over the DHT):
|
|
1576
|
-
|
|
1577
1978
|
`
|
|
1578
1979
|
);
|
|
1980
|
+
process2.stdout.write(` ${MARKS_LEGEND}
|
|
1981
|
+
|
|
1982
|
+
`);
|
|
1579
1983
|
for (const p of livePeers) {
|
|
1580
|
-
process2.stdout.write(` ${p.handle.padEnd(28)} ${p.league} \xB7 ${p.harness}
|
|
1984
|
+
process2.stdout.write(` ${p.handle.padEnd(28)} ${p.league} \xB7 ${p.harness} ${usageMark(p)}${idMark(p)}
|
|
1581
1985
|
`);
|
|
1582
1986
|
}
|
|
1583
1987
|
process2.stdout.write("\n");
|
|
@@ -1613,17 +2017,15 @@ async function cmdDiscover(live, any) {
|
|
|
1613
2017
|
if (live && !canShareLive()) grantLiveConsent();
|
|
1614
2018
|
if (!canShareLive()) {
|
|
1615
2019
|
process2.stderr.write(
|
|
1616
|
-
"Live discovery is off. It shares ONLY your handle + league + harness (never raw usage)
|
|
2020
|
+
"Live discovery is off. It shares ONLY your handle + league + harness + verified flag + identity pubkey\n(never raw usage) with same-league peers on the public DHT. Opt in: `vibedating discover --live`\n"
|
|
1617
2021
|
);
|
|
1618
2022
|
return 1;
|
|
1619
2023
|
}
|
|
1620
|
-
const hello =
|
|
1621
|
-
handle: resolveHandle(),
|
|
1622
|
-
league: profile.league,
|
|
1623
|
-
harness: profile.harness
|
|
1624
|
-
};
|
|
2024
|
+
const hello = buildHello(profile);
|
|
1625
2025
|
process2.stdout.write("\n");
|
|
1626
2026
|
process2.stdout.write(` ${LIVE_NOTICE}
|
|
2027
|
+
`);
|
|
2028
|
+
process2.stdout.write(` ${MARKS_LEGEND}
|
|
1627
2029
|
`);
|
|
1628
2030
|
const { topics, acceptLeague } = discoveryScope(profile.league, any);
|
|
1629
2031
|
const session = await startDiscovery({
|
|
@@ -1634,7 +2036,7 @@ async function cmdDiscover(live, any) {
|
|
|
1634
2036
|
onPeer: (peer, isNew) => {
|
|
1635
2037
|
const { bullet, qual } = peerDirection(profile.league, peer.league);
|
|
1636
2038
|
process2.stdout.write(
|
|
1637
|
-
` ${bullet} ${peer.handle} (${peer.league}${qual} \xB7 ${peer.harness})${isNew ? " \u2190 new match" : ""}
|
|
2039
|
+
` ${bullet} ${peer.handle} (${peer.league}${qual} \xB7 ${peer.harness}) ${usageMark(peer)}${idMark(peer)}${isNew ? " \u2190 new match" : ""}
|
|
1638
2040
|
`
|
|
1639
2041
|
);
|
|
1640
2042
|
}
|
|
@@ -1664,7 +2066,7 @@ async function cmdDiscover(live, any) {
|
|
|
1664
2066
|
);
|
|
1665
2067
|
return 0;
|
|
1666
2068
|
}
|
|
1667
|
-
async function cmdOpen(port) {
|
|
2069
|
+
async function cmdOpen(port, any) {
|
|
1668
2070
|
const profile = loadProfile();
|
|
1669
2071
|
let live;
|
|
1670
2072
|
let session;
|
|
@@ -1677,12 +2079,15 @@ async function cmdOpen(port) {
|
|
|
1677
2079
|
process2.stdout.write(`
|
|
1678
2080
|
${LIVE_NOTICE}
|
|
1679
2081
|
`);
|
|
1680
|
-
const hello =
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
2082
|
+
const hello = buildHello(profile);
|
|
2083
|
+
const { topics, acceptLeague } = discoveryScope(profile.league, any);
|
|
2084
|
+
void startDiscovery({
|
|
2085
|
+
hello,
|
|
2086
|
+
topics,
|
|
2087
|
+
acceptLeague,
|
|
2088
|
+
isBlocked: blockedChecker(),
|
|
2089
|
+
onLink: (link) => live.addLink(link)
|
|
2090
|
+
}).then((s) => {
|
|
1686
2091
|
session = s;
|
|
1687
2092
|
}).catch(() => {
|
|
1688
2093
|
});
|
|
@@ -1691,9 +2096,11 @@ async function cmdOpen(port) {
|
|
|
1691
2096
|
vibedating local web app \u2192 ${started.url}
|
|
1692
2097
|
`);
|
|
1693
2098
|
if (live) {
|
|
1694
|
-
process2.stdout.write(
|
|
2099
|
+
process2.stdout.write(
|
|
2100
|
+
any ? " \u2022 live video + chat available for connected peers (ANY league \u2014 --any)\n" : " \u2022 live video + chat available for connected peers (your league + adjacent; --any = everyone)\n"
|
|
2101
|
+
);
|
|
1695
2102
|
} else {
|
|
1696
|
-
process2.stdout.write(" \u2022 connect first (`vibedating connect`) to enable live video\n");
|
|
2103
|
+
process2.stdout.write(" \u2022 connect first (`vibedating connect`) to enable live video + chat\n");
|
|
1697
2104
|
}
|
|
1698
2105
|
process2.stdout.write(" \u2022 raw usage stays local \xB7 only league shared\n");
|
|
1699
2106
|
process2.stdout.write(" (Ctrl+C to stop)\n\n");
|
|
@@ -1723,13 +2130,11 @@ async function cmdLive(dating, any, to) {
|
|
|
1723
2130
|
process2.stderr.write("Could not enable live discovery. Try `vibedating discover --live`.\n");
|
|
1724
2131
|
return 1;
|
|
1725
2132
|
}
|
|
1726
|
-
const hello =
|
|
1727
|
-
handle: resolveHandle(),
|
|
1728
|
-
league: profile.league,
|
|
1729
|
-
harness: profile.harness
|
|
1730
|
-
};
|
|
2133
|
+
const hello = buildHello(profile);
|
|
1731
2134
|
process2.stdout.write("\n");
|
|
1732
2135
|
process2.stdout.write(` ${LIVE_NOTICE}
|
|
2136
|
+
`);
|
|
2137
|
+
process2.stdout.write(` ${MARKS_LEGEND}
|
|
1733
2138
|
`);
|
|
1734
2139
|
process2.stdout.write(
|
|
1735
2140
|
target !== null ? ` targeted \u2014 auto-opening ${target} when they connect (/quit to stop)
|
|
@@ -1743,7 +2148,7 @@ async function cmdLive(dating, any, to) {
|
|
|
1743
2148
|
}
|
|
1744
2149
|
const { qual } = peerDirection(profile.league, link.hello.league);
|
|
1745
2150
|
process2.stdout.write(
|
|
1746
|
-
` \xB7 matched ${link.hello.handle} (${link.hello.league}${qual} \xB7 ${link.hello.harness})
|
|
2151
|
+
` \xB7 matched ${link.hello.handle} (${link.hello.league}${qual} \xB7 ${link.hello.harness}) ${usageMark(link.hello)}${idMark(link.hello)}
|
|
1747
2152
|
`
|
|
1748
2153
|
);
|
|
1749
2154
|
link.onMessage((m) => {
|
|
@@ -1761,7 +2166,7 @@ async function cmdLive(dating, any, to) {
|
|
|
1761
2166
|
if (target !== null && !sameHandle(link.hello.handle, target)) {
|
|
1762
2167
|
const { qual } = peerDirection(profile.league, link.hello.league);
|
|
1763
2168
|
process2.stdout.write(
|
|
1764
|
-
` + ${link.hello.handle} (${link.hello.league}${qual}) \u2014 not your target
|
|
2169
|
+
` + ${link.hello.handle} (${link.hello.league}${qual}) ${usageMark(link.hello)}${idMark(link.hello)} \u2014 not your target
|
|
1765
2170
|
`
|
|
1766
2171
|
);
|
|
1767
2172
|
link.close();
|
|
@@ -1839,13 +2244,11 @@ async function cmdFind(targetArg, any) {
|
|
|
1839
2244
|
process2.stderr.write("Could not enable live discovery. Try `vibedating discover --live`.\n");
|
|
1840
2245
|
return 1;
|
|
1841
2246
|
}
|
|
1842
|
-
const hello =
|
|
1843
|
-
handle: resolveHandle(),
|
|
1844
|
-
league: profile.league,
|
|
1845
|
-
harness: profile.harness
|
|
1846
|
-
};
|
|
2247
|
+
const hello = buildHello(profile);
|
|
1847
2248
|
process2.stdout.write("\n");
|
|
1848
2249
|
process2.stdout.write(` ${LIVE_NOTICE}
|
|
2250
|
+
`);
|
|
2251
|
+
process2.stdout.write(` ${MARKS_LEGEND}
|
|
1849
2252
|
`);
|
|
1850
2253
|
process2.stdout.write(
|
|
1851
2254
|
` looking for ${target} in your league${any ? " (+ every league)" : " + adjacent"}\u2026
|
|
@@ -1862,11 +2265,15 @@ async function cmdFind(targetArg, any) {
|
|
|
1862
2265
|
const { qual } = peerDirection(profile.league, peer.league);
|
|
1863
2266
|
if (sameHandle(peer.handle, target)) {
|
|
1864
2267
|
found = true;
|
|
1865
|
-
process2.stdout.write(
|
|
1866
|
-
`)
|
|
2268
|
+
process2.stdout.write(
|
|
2269
|
+
` \u2605 found ${peer.handle} (${peer.league}${qual} \xB7 ${peer.harness}) ${usageMark(peer)}${idMark(peer)}
|
|
2270
|
+
`
|
|
2271
|
+
);
|
|
1867
2272
|
} else {
|
|
1868
|
-
process2.stdout.write(
|
|
1869
|
-
`)
|
|
2273
|
+
process2.stdout.write(
|
|
2274
|
+
` + ${peer.handle} (${peer.league}${qual}) ${usageMark(peer)}${idMark(peer)} \u2014 not your target
|
|
2275
|
+
`
|
|
2276
|
+
);
|
|
1870
2277
|
}
|
|
1871
2278
|
}
|
|
1872
2279
|
});
|
|
@@ -1956,8 +2363,9 @@ Usage:
|
|
|
1956
2363
|
vibedating block <@handle> Block a handle \u2014 their hello is dropped (never recorded/paired)
|
|
1957
2364
|
vibedating unblock <@handle> Remove a handle from the blocklist
|
|
1958
2365
|
vibedating blocklist List blocked handles
|
|
1959
|
-
vibedating open [--port N]
|
|
1960
|
-
+ live
|
|
2366
|
+
vibedating open [--port N] [--any] Serve the local web app (default: random port)
|
|
2367
|
+
+ live video + chat with connected peers
|
|
2368
|
+
(your league + adjacent; --any = every league)
|
|
1961
2369
|
vibedating mcp Run the stdio MCP server (profile, matches)
|
|
1962
2370
|
vibedating --version
|
|
1963
2371
|
vibedating --help
|
|
@@ -1965,12 +2373,16 @@ Usage:
|
|
|
1965
2373
|
Privacy:
|
|
1966
2374
|
Raw token usage is read and stored LOCALLY (~/.vibedating). Only the league
|
|
1967
2375
|
bucket is ever shared. Live discovery (off by default) shares ONLY your
|
|
1968
|
-
handle + league + harness
|
|
2376
|
+
handle + league + harness + verified flag + identity pubkey (an ed25519 key
|
|
2377
|
+
generated on first connect, stored 0600 in ~/.vibedating/identity.json) with
|
|
2378
|
+
same-league peers \u2014 opt in with --live. Peers are marked: \u2713 usage verified
|
|
2379
|
+
(real local logs) \xB7 ~ unverified \xB7 \u{1F511} identity-verified (signed hello).
|
|
1969
2380
|
|
|
1970
2381
|
Matching:
|
|
1971
|
-
discover/live match your league + adjacent (\xB11) tiers by default, so
|
|
1972
|
-
leagues and cross-league friends still connect. --any matches every
|
|
1973
|
-
find / live --to look for one specific handle instead of the first
|
|
2382
|
+
discover/live/open match your league + adjacent (\xB11) tiers by default, so
|
|
2383
|
+
thin leagues and cross-league friends still connect. --any matches every
|
|
2384
|
+
league. find / live --to look for one specific handle instead of the first
|
|
2385
|
+
random peer.
|
|
1974
2386
|
|
|
1975
2387
|
Env:
|
|
1976
2388
|
VIBEDATING_TOKENS=<n> Self-report a token count (e.g. 23400000 or 12M)
|
|
@@ -2003,7 +2415,7 @@ async function main(argv) {
|
|
|
2003
2415
|
case "discover":
|
|
2004
2416
|
return cmdDiscover(parsed.live, parsed.any);
|
|
2005
2417
|
case "open":
|
|
2006
|
-
return cmdOpen(parsed.port);
|
|
2418
|
+
return cmdOpen(parsed.port, parsed.any);
|
|
2007
2419
|
case "live":
|
|
2008
2420
|
return cmdLive(parsed.dating, parsed.any, parsed.to);
|
|
2009
2421
|
case "find":
|