vibedate 0.5.0 → 0.7.0
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/dist/{chunk-KKWP4DLY.js → chunk-HDHJLZZG.js} +343 -0
- package/dist/{chunk-I5U3O4RH.js → chunk-ZD6JBWEW.js} +1 -1
- package/dist/cli.d.ts +9 -1
- package/dist/cli.js +484 -34
- package/dist/index.d.ts +217 -1
- package/dist/index.js +21 -1
- package/dist/mcp.js +2 -2
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runMcp
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ZD6JBWEW.js";
|
|
5
5
|
import {
|
|
6
6
|
CANDIDATES,
|
|
7
7
|
DEFAULT_HANDLE,
|
|
8
8
|
LIVE_NOTICE,
|
|
9
9
|
MAX_HANDLE_LEN,
|
|
10
|
+
ROOM_TOPIC_PREFIX,
|
|
10
11
|
TOPIC_PREFIX,
|
|
11
12
|
addBlock,
|
|
12
13
|
allLeagueNames,
|
|
13
14
|
canShareLive,
|
|
14
15
|
connectProfile,
|
|
16
|
+
createNostrPoolTransport,
|
|
17
|
+
createNostrRelayLink,
|
|
15
18
|
defaultStateDir,
|
|
16
19
|
grantLiveConsent,
|
|
17
20
|
isBlocked,
|
|
@@ -22,6 +25,7 @@ import {
|
|
|
22
25
|
loadBlocklist,
|
|
23
26
|
loadHandle,
|
|
24
27
|
loadOrCreateIdentity,
|
|
28
|
+
loadOrCreateNostrKey,
|
|
25
29
|
loadPeers,
|
|
26
30
|
loadProfile,
|
|
27
31
|
matches,
|
|
@@ -34,8 +38,9 @@ import {
|
|
|
34
38
|
sanitizePeerText,
|
|
35
39
|
saveHandle,
|
|
36
40
|
signHelloClaims,
|
|
37
|
-
startDiscovery
|
|
38
|
-
|
|
41
|
+
startDiscovery,
|
|
42
|
+
startRoom
|
|
43
|
+
} from "./chunk-HDHJLZZG.js";
|
|
39
44
|
|
|
40
45
|
// src/cli.ts
|
|
41
46
|
import readline from "readline";
|
|
@@ -1888,6 +1893,111 @@ function createLiveBridge() {
|
|
|
1888
1893
|
};
|
|
1889
1894
|
return bridge;
|
|
1890
1895
|
}
|
|
1896
|
+
var MAX_QUEUED_ROOM_SIGNALS = 200;
|
|
1897
|
+
function createRoomBridge(name) {
|
|
1898
|
+
let session;
|
|
1899
|
+
const messageQueue = [];
|
|
1900
|
+
const messageWaiters = [];
|
|
1901
|
+
const signalBoxes = /* @__PURE__ */ new Map();
|
|
1902
|
+
const signalWaiters = /* @__PURE__ */ new Map();
|
|
1903
|
+
const drainMessage = (m) => {
|
|
1904
|
+
const safe = { ...m, text: sanitizePeerText(m.text) };
|
|
1905
|
+
const waiter = messageWaiters.shift();
|
|
1906
|
+
if (waiter !== void 0) {
|
|
1907
|
+
waiter(safe);
|
|
1908
|
+
return;
|
|
1909
|
+
}
|
|
1910
|
+
messageQueue.push(safe);
|
|
1911
|
+
if (messageQueue.length > MAX_QUEUED_MESSAGES) {
|
|
1912
|
+
messageQueue.splice(0, messageQueue.length - MAX_QUEUED_MESSAGES);
|
|
1913
|
+
}
|
|
1914
|
+
};
|
|
1915
|
+
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);
|
|
1929
|
+
}
|
|
1930
|
+
box.push(frame);
|
|
1931
|
+
if (box.length > MAX_QUEUED_ROOM_SIGNALS) {
|
|
1932
|
+
box.splice(0, box.length - MAX_QUEUED_ROOM_SIGNALS);
|
|
1933
|
+
}
|
|
1934
|
+
};
|
|
1935
|
+
return {
|
|
1936
|
+
name,
|
|
1937
|
+
get self() {
|
|
1938
|
+
return session?.hello.handle;
|
|
1939
|
+
},
|
|
1940
|
+
get members() {
|
|
1941
|
+
if (session === void 0) return [];
|
|
1942
|
+
return [...session.members.values()].map((h) => ({
|
|
1943
|
+
handle: h.handle,
|
|
1944
|
+
league: h.league,
|
|
1945
|
+
harness: h.harness,
|
|
1946
|
+
...h.verified !== void 0 ? { verified: h.verified } : {},
|
|
1947
|
+
...h.identityVerified !== void 0 ? { identityVerified: h.identityVerified } : {}
|
|
1948
|
+
}));
|
|
1949
|
+
},
|
|
1950
|
+
attach(s) {
|
|
1951
|
+
session = s;
|
|
1952
|
+
s.onMessage(drainMessage);
|
|
1953
|
+
s.onSignal((from, frame) => drainSignal(from, frame));
|
|
1954
|
+
},
|
|
1955
|
+
broadcast(text) {
|
|
1956
|
+
session?.broadcast(text);
|
|
1957
|
+
},
|
|
1958
|
+
async pollMessage(timeoutMs) {
|
|
1959
|
+
if (messageQueue.length > 0) return messageQueue.shift() ?? null;
|
|
1960
|
+
return new Promise((resolve) => {
|
|
1961
|
+
const timer = setTimeout(() => {
|
|
1962
|
+
const idx = messageWaiters.indexOf(resolve);
|
|
1963
|
+
if (idx >= 0) messageWaiters.splice(idx, 1);
|
|
1964
|
+
resolve(null);
|
|
1965
|
+
}, timeoutMs);
|
|
1966
|
+
messageWaiters.push((m) => {
|
|
1967
|
+
clearTimeout(timer);
|
|
1968
|
+
resolve(m);
|
|
1969
|
+
});
|
|
1970
|
+
});
|
|
1971
|
+
},
|
|
1972
|
+
sendSignal(handle2, frame) {
|
|
1973
|
+
session?.sendSignal(handle2, frame);
|
|
1974
|
+
},
|
|
1975
|
+
async pollSignal(handle2, timeoutMs) {
|
|
1976
|
+
const box = signalBoxes.get(handle2);
|
|
1977
|
+
if (box !== void 0 && box.length > 0) return box.shift() ?? null;
|
|
1978
|
+
return new Promise((resolve) => {
|
|
1979
|
+
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
|
+
}
|
|
1986
|
+
resolve(null);
|
|
1987
|
+
}, timeoutMs);
|
|
1988
|
+
let waiters = signalWaiters.get(handle2);
|
|
1989
|
+
if (waiters === void 0) {
|
|
1990
|
+
waiters = [];
|
|
1991
|
+
signalWaiters.set(handle2, waiters);
|
|
1992
|
+
}
|
|
1993
|
+
waiters.push((f) => {
|
|
1994
|
+
clearTimeout(timer);
|
|
1995
|
+
resolve(f);
|
|
1996
|
+
});
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
1999
|
+
};
|
|
2000
|
+
}
|
|
1891
2001
|
function currentState(dir) {
|
|
1892
2002
|
const p = loadProfile(dir);
|
|
1893
2003
|
if (!p) return { connected: false, candidates: [] };
|
|
@@ -2108,11 +2218,101 @@ async function handle(req, res, opts) {
|
|
|
2108
2218
|
sendJson(res, 200, { ok: true });
|
|
2109
2219
|
return;
|
|
2110
2220
|
}
|
|
2221
|
+
if (req.method === "GET" && pathname === "/api/room") {
|
|
2222
|
+
const room = opts.room;
|
|
2223
|
+
sendJson(res, 200, room ? { room: room.name, self: room.self ?? null, members: room.members } : { room: null, self: null, members: [] });
|
|
2224
|
+
return;
|
|
2225
|
+
}
|
|
2226
|
+
if (req.method === "GET" && pathname === "/room/message") {
|
|
2227
|
+
const room = opts.room;
|
|
2228
|
+
if (!room) {
|
|
2229
|
+
sendJson(res, 200, { message: null, reason: "room-not-attached" });
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
const message = await room.pollMessage(25e3);
|
|
2233
|
+
if (req.destroyed || res.writableEnded) return;
|
|
2234
|
+
sendJson(res, 200, { message });
|
|
2235
|
+
return;
|
|
2236
|
+
}
|
|
2237
|
+
if (req.method === "POST" && pathname === "/room/message") {
|
|
2238
|
+
const room = opts.room;
|
|
2239
|
+
if (!room) {
|
|
2240
|
+
sendJson(res, 400, { error: "room-not-attached" });
|
|
2241
|
+
return;
|
|
2242
|
+
}
|
|
2243
|
+
const body = await readBody(req);
|
|
2244
|
+
let parsed = {};
|
|
2245
|
+
try {
|
|
2246
|
+
parsed = JSON.parse(body);
|
|
2247
|
+
} catch {
|
|
2248
|
+
sendJson(res, 400, { error: "invalid JSON body" });
|
|
2249
|
+
return;
|
|
2250
|
+
}
|
|
2251
|
+
const text = parsed["text"];
|
|
2252
|
+
if (typeof text !== "string") {
|
|
2253
|
+
sendJson(res, 400, { error: "missing text" });
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2256
|
+
const reParsed = parseFrame(
|
|
2257
|
+
JSON.stringify({ t: "msg", id: randomUUID(), text, at: Date.now() })
|
|
2258
|
+
);
|
|
2259
|
+
if (reParsed === null || reParsed.t !== "msg") {
|
|
2260
|
+
sendJson(res, 400, { error: "invalid message text" });
|
|
2261
|
+
return;
|
|
2262
|
+
}
|
|
2263
|
+
room.broadcast(reParsed.text);
|
|
2264
|
+
sendJson(res, 200, { ok: true });
|
|
2265
|
+
return;
|
|
2266
|
+
}
|
|
2267
|
+
if (req.method === "GET" && pathname === "/room/signal") {
|
|
2268
|
+
const room = opts.room;
|
|
2269
|
+
if (!room) {
|
|
2270
|
+
sendJson(res, 200, { frame: null, reason: "room-not-attached" });
|
|
2271
|
+
return;
|
|
2272
|
+
}
|
|
2273
|
+
const handle2 = url.searchParams.get("handle") ?? "";
|
|
2274
|
+
if (handle2 === "") {
|
|
2275
|
+
sendJson(res, 400, { error: "missing handle" });
|
|
2276
|
+
return;
|
|
2277
|
+
}
|
|
2278
|
+
const frame = await room.pollSignal(handle2, 25e3);
|
|
2279
|
+
if (req.destroyed || res.writableEnded) return;
|
|
2280
|
+
sendJson(res, 200, { frame });
|
|
2281
|
+
return;
|
|
2282
|
+
}
|
|
2283
|
+
if (req.method === "POST" && pathname === "/room/signal") {
|
|
2284
|
+
const room = opts.room;
|
|
2285
|
+
if (!room) {
|
|
2286
|
+
sendJson(res, 400, { error: "room-not-attached" });
|
|
2287
|
+
return;
|
|
2288
|
+
}
|
|
2289
|
+
const body = await readBody(req);
|
|
2290
|
+
let parsed = {};
|
|
2291
|
+
try {
|
|
2292
|
+
parsed = JSON.parse(body);
|
|
2293
|
+
} catch {
|
|
2294
|
+
sendJson(res, 400, { error: "invalid JSON body" });
|
|
2295
|
+
return;
|
|
2296
|
+
}
|
|
2297
|
+
const handle2 = typeof parsed["handle"] === "string" ? parsed["handle"] : "";
|
|
2298
|
+
if (handle2 === "") {
|
|
2299
|
+
sendJson(res, 400, { error: "missing handle" });
|
|
2300
|
+
return;
|
|
2301
|
+
}
|
|
2302
|
+
const reParsed = parseFrame(JSON.stringify(parsed["frame"]));
|
|
2303
|
+
if (reParsed === null || reParsed.t !== "rtc-offer" && reParsed.t !== "rtc-answer" && reParsed.t !== "rtc-ice") {
|
|
2304
|
+
sendJson(res, 400, { error: "invalid rtc frame" });
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
2307
|
+
room.sendSignal(handle2, reParsed);
|
|
2308
|
+
sendJson(res, 200, { ok: true });
|
|
2309
|
+
return;
|
|
2310
|
+
}
|
|
2111
2311
|
sendJson(res, 404, { error: "not found" });
|
|
2112
2312
|
}
|
|
2113
2313
|
|
|
2114
2314
|
// src/cli.ts
|
|
2115
|
-
var VERSION = "0.
|
|
2315
|
+
var VERSION = "0.7.0";
|
|
2116
2316
|
function parsePort(raw) {
|
|
2117
2317
|
const n = Number(raw);
|
|
2118
2318
|
if (!Number.isInteger(n) || n < 1 || n > 65535) return void 0;
|
|
@@ -2127,7 +2327,9 @@ function parseArgs(argv) {
|
|
|
2127
2327
|
any: false,
|
|
2128
2328
|
arg: void 0,
|
|
2129
2329
|
to: void 0,
|
|
2130
|
-
keepAlive: false
|
|
2330
|
+
keepAlive: false,
|
|
2331
|
+
viaRelay: false,
|
|
2332
|
+
room: void 0
|
|
2131
2333
|
};
|
|
2132
2334
|
for (let i = 0; i < argv.length; i++) {
|
|
2133
2335
|
const a = argv[i];
|
|
@@ -2140,7 +2342,9 @@ function parseArgs(argv) {
|
|
|
2140
2342
|
any: false,
|
|
2141
2343
|
arg: void 0,
|
|
2142
2344
|
to: void 0,
|
|
2143
|
-
keepAlive: false
|
|
2345
|
+
keepAlive: false,
|
|
2346
|
+
viaRelay: false,
|
|
2347
|
+
room: void 0
|
|
2144
2348
|
};
|
|
2145
2349
|
}
|
|
2146
2350
|
if (a === "--help" || a === "-h") {
|
|
@@ -2152,7 +2356,9 @@ function parseArgs(argv) {
|
|
|
2152
2356
|
any: false,
|
|
2153
2357
|
arg: void 0,
|
|
2154
2358
|
to: void 0,
|
|
2155
|
-
keepAlive: false
|
|
2359
|
+
keepAlive: false,
|
|
2360
|
+
viaRelay: false,
|
|
2361
|
+
room: void 0
|
|
2156
2362
|
};
|
|
2157
2363
|
}
|
|
2158
2364
|
if (a === "--live") {
|
|
@@ -2163,6 +2369,22 @@ function parseArgs(argv) {
|
|
|
2163
2369
|
out = { ...out, keepAlive: true };
|
|
2164
2370
|
continue;
|
|
2165
2371
|
}
|
|
2372
|
+
if (a === "--via-relay") {
|
|
2373
|
+
out = { ...out, viaRelay: true };
|
|
2374
|
+
continue;
|
|
2375
|
+
}
|
|
2376
|
+
if (a === "--room") {
|
|
2377
|
+
const next = argv[i + 1];
|
|
2378
|
+
if (next !== void 0) {
|
|
2379
|
+
out = { ...out, room: next };
|
|
2380
|
+
i++;
|
|
2381
|
+
}
|
|
2382
|
+
continue;
|
|
2383
|
+
}
|
|
2384
|
+
if (a.startsWith("--room=")) {
|
|
2385
|
+
out = { ...out, room: a.slice("--room=".length) };
|
|
2386
|
+
continue;
|
|
2387
|
+
}
|
|
2166
2388
|
if (a === "--any") {
|
|
2167
2389
|
out = { ...out, any: true };
|
|
2168
2390
|
continue;
|
|
@@ -2198,7 +2420,7 @@ function parseArgs(argv) {
|
|
|
2198
2420
|
continue;
|
|
2199
2421
|
}
|
|
2200
2422
|
if (a.startsWith("-")) continue;
|
|
2201
|
-
const known = a === "connect" || a === "matches" || a === "discover" || a === "open" || a === "live" || a === "find" || a === "handle" || a === "block" || a === "unblock" || a === "blocklist" || a === "daemon" || a === "mcp" || a === "help" ? a : null;
|
|
2423
|
+
const known = a === "connect" || a === "matches" || a === "discover" || a === "open" || a === "live" || a === "find" || a === "handle" || a === "block" || a === "unblock" || a === "blocklist" || a === "daemon" || a === "mcp" || a === "room" || a === "help" ? a : null;
|
|
2202
2424
|
if (known !== null && out.command === null) {
|
|
2203
2425
|
out = { ...out, command: known };
|
|
2204
2426
|
} else if (out.arg === void 0) {
|
|
@@ -2381,7 +2603,7 @@ async function cmdMatches(live) {
|
|
|
2381
2603
|
process2.stdout.write("\n");
|
|
2382
2604
|
return 0;
|
|
2383
2605
|
}
|
|
2384
|
-
async function cmdDiscover(live, any) {
|
|
2606
|
+
async function cmdDiscover(live, any, viaRelay) {
|
|
2385
2607
|
const profile = loadProfile();
|
|
2386
2608
|
if (!profile) {
|
|
2387
2609
|
process2.stderr.write("Not connected yet. Run `vibedating connect` first.\n");
|
|
@@ -2400,6 +2622,9 @@ async function cmdDiscover(live, any) {
|
|
|
2400
2622
|
`);
|
|
2401
2623
|
process2.stdout.write(` ${MARKS_LEGEND}
|
|
2402
2624
|
`);
|
|
2625
|
+
if (viaRelay) {
|
|
2626
|
+
process2.stdout.write(" \u2022 --via-relay: the relay fallback reaches a KNOWN peer over Nostr \u2014 use `live --via-relay --to @handle`\n");
|
|
2627
|
+
}
|
|
2403
2628
|
const { topics, acceptLeague } = discoveryScope(profile.league, any);
|
|
2404
2629
|
const session = await startDiscovery({
|
|
2405
2630
|
hello,
|
|
@@ -2439,36 +2664,59 @@ async function cmdDiscover(live, any) {
|
|
|
2439
2664
|
);
|
|
2440
2665
|
return 0;
|
|
2441
2666
|
}
|
|
2442
|
-
async function cmdOpen(port, any) {
|
|
2667
|
+
async function cmdOpen(port, any, room) {
|
|
2443
2668
|
const profile = loadProfile();
|
|
2444
2669
|
let live;
|
|
2670
|
+
let roomBridge;
|
|
2445
2671
|
let session;
|
|
2672
|
+
let roomSession;
|
|
2446
2673
|
if (profile) {
|
|
2447
2674
|
if (!canShareLive()) grantLiveConsent();
|
|
2448
|
-
|
|
2675
|
+
if (room !== void 0) {
|
|
2676
|
+
roomBridge = createRoomBridge(room);
|
|
2677
|
+
} else {
|
|
2678
|
+
live = createLiveBridge();
|
|
2679
|
+
}
|
|
2449
2680
|
}
|
|
2450
|
-
const started = await startServer({
|
|
2451
|
-
|
|
2681
|
+
const started = await startServer({
|
|
2682
|
+
port,
|
|
2683
|
+
live,
|
|
2684
|
+
...roomBridge === void 0 ? {} : { room: roomBridge }
|
|
2685
|
+
});
|
|
2686
|
+
if (profile) {
|
|
2452
2687
|
process2.stdout.write(`
|
|
2453
2688
|
${LIVE_NOTICE}
|
|
2454
2689
|
`);
|
|
2455
2690
|
const hello = buildHello(profile);
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2691
|
+
if (room !== void 0 && roomBridge !== void 0) {
|
|
2692
|
+
void startRoom({ hello, room, isBlocked: blockedChecker() }).then((s) => {
|
|
2693
|
+
roomSession = s;
|
|
2694
|
+
roomBridge.attach(s);
|
|
2695
|
+
}).catch(() => {
|
|
2696
|
+
});
|
|
2697
|
+
} else if (live) {
|
|
2698
|
+
const { topics, acceptLeague } = discoveryScope(profile.league, any);
|
|
2699
|
+
void startDiscovery({
|
|
2700
|
+
hello,
|
|
2701
|
+
topics,
|
|
2702
|
+
acceptLeague,
|
|
2703
|
+
isBlocked: blockedChecker(),
|
|
2704
|
+
onLink: (link) => live.addLink(link)
|
|
2705
|
+
}).then((s) => {
|
|
2706
|
+
session = s;
|
|
2707
|
+
}).catch(() => {
|
|
2708
|
+
});
|
|
2709
|
+
}
|
|
2467
2710
|
}
|
|
2468
2711
|
process2.stdout.write(`
|
|
2469
2712
|
vibedating local web app \u2192 ${started.url}
|
|
2470
2713
|
`);
|
|
2471
|
-
if (
|
|
2714
|
+
if (room !== void 0) {
|
|
2715
|
+
process2.stdout.write(
|
|
2716
|
+
` \u2022 room: ${room} \u2014 roster + group chat + full-mesh group video (~6 people; an SFU is the upgrade path for bigger rooms)
|
|
2717
|
+
`
|
|
2718
|
+
);
|
|
2719
|
+
} else if (live) {
|
|
2472
2720
|
process2.stdout.write(
|
|
2473
2721
|
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"
|
|
2474
2722
|
);
|
|
@@ -2482,6 +2730,7 @@ async function cmdOpen(port, any) {
|
|
|
2482
2730
|
process2.once("SIGTERM", () => resolve());
|
|
2483
2731
|
});
|
|
2484
2732
|
process2.stdout.write("\n shutting down\u2026\n");
|
|
2733
|
+
if (roomSession) await roomSession.close();
|
|
2485
2734
|
if (session) await session.close();
|
|
2486
2735
|
await new Promise((resolve) => started.server.close(() => resolve()));
|
|
2487
2736
|
return 0;
|
|
@@ -2489,7 +2738,100 @@ async function cmdOpen(port, any) {
|
|
|
2489
2738
|
function shouldKeepAlive(flag, stdinIsTTY) {
|
|
2490
2739
|
return flag || stdinIsTTY !== true;
|
|
2491
2740
|
}
|
|
2492
|
-
async function
|
|
2741
|
+
async function cmdLiveViaRelay(profile, to) {
|
|
2742
|
+
const target = to !== void 0 ? normalizeHandle(to) : null;
|
|
2743
|
+
if (to !== void 0 && target === null) {
|
|
2744
|
+
process2.stderr.write(`invalid target handle: ${to}
|
|
2745
|
+
`);
|
|
2746
|
+
return 1;
|
|
2747
|
+
}
|
|
2748
|
+
if (target === null) {
|
|
2749
|
+
process2.stderr.write(
|
|
2750
|
+
"relay mode (v0) targets one known peer \u2014 use: vibedating live --via-relay --to <@handle>\n(discover the peer first with `vibedating discover --live`, then reach them over the relay).\n"
|
|
2751
|
+
);
|
|
2752
|
+
return 1;
|
|
2753
|
+
}
|
|
2754
|
+
const peer = loadPeers().find((p) => sameHandle(p.handle, target) && typeof p.pubkey === "string");
|
|
2755
|
+
if (peer === void 0 || peer.pubkey === void 0) {
|
|
2756
|
+
process2.stderr.write(
|
|
2757
|
+
`${target} is not a known peer with an identity pubkey. Run \`vibedating discover --live\` first to meet them over the DHT, then retry --via-relay.
|
|
2758
|
+
`
|
|
2759
|
+
);
|
|
2760
|
+
return 1;
|
|
2761
|
+
}
|
|
2762
|
+
const identity = loadOrCreateIdentity();
|
|
2763
|
+
process2.stdout.write("\n");
|
|
2764
|
+
process2.stdout.write(` ${LIVE_NOTICE}
|
|
2765
|
+
`);
|
|
2766
|
+
process2.stdout.write(
|
|
2767
|
+
` routing e2e chat to ${sanitizePeerText(peer.handle)} through public Nostr relays (direct hole-punch fallback)
|
|
2768
|
+
`
|
|
2769
|
+
);
|
|
2770
|
+
process2.stdout.write(" \u2022 the relay stores only ciphertext \u2014 it cannot read your chat\n");
|
|
2771
|
+
const myNostr = await loadOrCreateNostrKey();
|
|
2772
|
+
const transport = await createNostrPoolTransport();
|
|
2773
|
+
const link = await createNostrRelayLink({
|
|
2774
|
+
myNostr,
|
|
2775
|
+
myEd25519Hex: identity.publicKeyHex,
|
|
2776
|
+
peerEd25519Hex: peer.pubkey,
|
|
2777
|
+
hello: peer,
|
|
2778
|
+
transport
|
|
2779
|
+
});
|
|
2780
|
+
const pairing = createPairing();
|
|
2781
|
+
pairing.onMatch((l) => {
|
|
2782
|
+
if (l === void 0) {
|
|
2783
|
+
process2.stdout.write(" \xB7 relay peer gone\n");
|
|
2784
|
+
return;
|
|
2785
|
+
}
|
|
2786
|
+
const { qual } = peerDirection(profile.league, l.hello.league);
|
|
2787
|
+
process2.stdout.write(
|
|
2788
|
+
` \xB7 relayed to ${sanitizePeerText(l.hello.handle)} (${l.hello.league}${qual} \xB7 ${l.hello.harness}) ${usageMark(l.hello)}${idMark(l.hello)}
|
|
2789
|
+
`
|
|
2790
|
+
);
|
|
2791
|
+
l.onMessage((m) => {
|
|
2792
|
+
process2.stdout.write(` <${sanitizePeerText(l.hello.handle)}> ${sanitizePeerText(m.text)}
|
|
2793
|
+
`);
|
|
2794
|
+
});
|
|
2795
|
+
});
|
|
2796
|
+
pairing.add(link);
|
|
2797
|
+
process2.stdout.write(" type to chat \xB7 /quit\n");
|
|
2798
|
+
process2.stdout.write(" (Ctrl+C to stop)\n\n");
|
|
2799
|
+
const rl = readline.createInterface({ input: process2.stdin, terminal: false });
|
|
2800
|
+
const stop = () => {
|
|
2801
|
+
rl.close();
|
|
2802
|
+
};
|
|
2803
|
+
process2.once("SIGINT", stop);
|
|
2804
|
+
process2.once("SIGTERM", stop);
|
|
2805
|
+
let quitRequested = false;
|
|
2806
|
+
for await (const line of rl) {
|
|
2807
|
+
const text = line.trim();
|
|
2808
|
+
if (text === "/quit") {
|
|
2809
|
+
quitRequested = true;
|
|
2810
|
+
break;
|
|
2811
|
+
}
|
|
2812
|
+
if (text === "/next") {
|
|
2813
|
+
process2.stdout.write(" \xB7 relay mode targets one peer \u2014 /next re-announces presence\n");
|
|
2814
|
+
continue;
|
|
2815
|
+
}
|
|
2816
|
+
if (text === "") continue;
|
|
2817
|
+
const cur2 = pairing.current();
|
|
2818
|
+
if (cur2 !== void 0) {
|
|
2819
|
+
cur2.send(text);
|
|
2820
|
+
} else {
|
|
2821
|
+
process2.stdout.write(" \xB7 no relay peer yet \u2014 waiting for the presence exchange\u2026\n");
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
process2.removeListener("SIGINT", stop);
|
|
2825
|
+
process2.removeListener("SIGTERM", stop);
|
|
2826
|
+
void quitRequested;
|
|
2827
|
+
const cur = pairing.current();
|
|
2828
|
+
if (cur !== void 0) cur.close();
|
|
2829
|
+
process2.stdout.write("\n closing relay link\u2026\n");
|
|
2830
|
+
link.close();
|
|
2831
|
+
process2.stdout.write("\n");
|
|
2832
|
+
return 0;
|
|
2833
|
+
}
|
|
2834
|
+
async function cmdLive(dating, any, to, keepAlive, viaRelay) {
|
|
2493
2835
|
const profile = loadProfile();
|
|
2494
2836
|
if (!profile) {
|
|
2495
2837
|
process2.stderr.write("Not connected yet. Run `vibedating connect` first.\n");
|
|
@@ -2506,6 +2848,7 @@ async function cmdLive(dating, any, to, keepAlive) {
|
|
|
2506
2848
|
process2.stderr.write("Could not enable live discovery. Try `vibedating discover --live`.\n");
|
|
2507
2849
|
return 1;
|
|
2508
2850
|
}
|
|
2851
|
+
if (viaRelay) return cmdLiveViaRelay(profile, to);
|
|
2509
2852
|
const hello = buildHello(profile);
|
|
2510
2853
|
process2.stdout.write("\n");
|
|
2511
2854
|
process2.stdout.write(` ${LIVE_NOTICE}
|
|
@@ -2610,6 +2953,104 @@ async function cmdLive(dating, any, to, keepAlive) {
|
|
|
2610
2953
|
process2.stdout.write("\n");
|
|
2611
2954
|
return 0;
|
|
2612
2955
|
}
|
|
2956
|
+
async function cmdRoom(name, keepAlive) {
|
|
2957
|
+
if (name === void 0 || name.trim() === "") {
|
|
2958
|
+
process2.stderr.write("usage: vibedating room <name>\n");
|
|
2959
|
+
return 1;
|
|
2960
|
+
}
|
|
2961
|
+
const profile = loadProfile();
|
|
2962
|
+
if (!profile) {
|
|
2963
|
+
process2.stderr.write("Not connected yet. Run `vibedating connect` first.\n");
|
|
2964
|
+
return 1;
|
|
2965
|
+
}
|
|
2966
|
+
if (!canShareLive()) grantLiveConsent();
|
|
2967
|
+
if (!canShareLive()) {
|
|
2968
|
+
process2.stderr.write("Could not enable live discovery. Try `vibedating discover --live`.\n");
|
|
2969
|
+
return 1;
|
|
2970
|
+
}
|
|
2971
|
+
const hello = buildHello(profile);
|
|
2972
|
+
process2.stdout.write("\n");
|
|
2973
|
+
process2.stdout.write(` ${LIVE_NOTICE}
|
|
2974
|
+
`);
|
|
2975
|
+
process2.stdout.write(` ${MARKS_LEGEND}
|
|
2976
|
+
`);
|
|
2977
|
+
process2.stdout.write(` room: ${name} (cross-league \u2014 everyone in the room is a member)
|
|
2978
|
+
`);
|
|
2979
|
+
const session = await startRoom({
|
|
2980
|
+
hello,
|
|
2981
|
+
room: name,
|
|
2982
|
+
isBlocked: blockedChecker()
|
|
2983
|
+
});
|
|
2984
|
+
session.onRoster((members) => {
|
|
2985
|
+
if (members.length === 0) {
|
|
2986
|
+
process2.stdout.write(" \xB7 room empty \u2014 waiting for members\u2026\n");
|
|
2987
|
+
return;
|
|
2988
|
+
}
|
|
2989
|
+
const list = members.map(
|
|
2990
|
+
(m) => `${sanitizePeerText(m.handle)} (${m.league} \xB7 ${m.harness}) ${usageMark(m)}${idMark(m)}`
|
|
2991
|
+
).join(", ");
|
|
2992
|
+
process2.stdout.write(` \xB7 room (${members.length}): ${list}
|
|
2993
|
+
`);
|
|
2994
|
+
});
|
|
2995
|
+
session.onMessage((m) => {
|
|
2996
|
+
process2.stdout.write(` <${sanitizePeerText(m.from)}> ${sanitizePeerText(m.text)}
|
|
2997
|
+
`);
|
|
2998
|
+
});
|
|
2999
|
+
process2.stdout.write(
|
|
3000
|
+
` topic: ${ROOM_TOPIC_PREFIX}${name} \u2192 ${session.topic.toString("hex").slice(0, 12)}\u2026
|
|
3001
|
+
`
|
|
3002
|
+
);
|
|
3003
|
+
process2.stdout.write(" type to broadcast to the whole room \xB7 /who \xB7 /quit\n");
|
|
3004
|
+
process2.stdout.write(` group video: run \`vibedating open --room ${name}\`
|
|
3005
|
+
|
|
3006
|
+
`);
|
|
3007
|
+
const rl = readline.createInterface({ input: process2.stdin, terminal: false });
|
|
3008
|
+
const stop = () => {
|
|
3009
|
+
rl.close();
|
|
3010
|
+
};
|
|
3011
|
+
process2.once("SIGINT", stop);
|
|
3012
|
+
process2.once("SIGTERM", stop);
|
|
3013
|
+
let quitRequested = false;
|
|
3014
|
+
for await (const line of rl) {
|
|
3015
|
+
const text = line.trim();
|
|
3016
|
+
if (text === "/quit") {
|
|
3017
|
+
quitRequested = true;
|
|
3018
|
+
break;
|
|
3019
|
+
}
|
|
3020
|
+
if (text === "/who") {
|
|
3021
|
+
const members = [...session.members.values()];
|
|
3022
|
+
if (members.length === 0) {
|
|
3023
|
+
process2.stdout.write(" \xB7 room empty\n");
|
|
3024
|
+
} else {
|
|
3025
|
+
for (const m of members) {
|
|
3026
|
+
process2.stdout.write(
|
|
3027
|
+
` \xB7 ${sanitizePeerText(m.handle)} (${m.league} \xB7 ${m.harness}) ${usageMark(m)}${idMark(m)}
|
|
3028
|
+
`
|
|
3029
|
+
);
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
continue;
|
|
3033
|
+
}
|
|
3034
|
+
if (text === "") continue;
|
|
3035
|
+
const reached = session.broadcast(text);
|
|
3036
|
+
if (reached.length === 0) {
|
|
3037
|
+
process2.stdout.write(" \xB7 room empty \u2014 no one to hear you yet\n");
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
if (!quitRequested && shouldKeepAlive(keepAlive, process2.stdin.isTTY)) {
|
|
3041
|
+
process2.stdout.write(" stdin closed \u2014 staying in the room (Ctrl+C / SIGTERM to leave)\n");
|
|
3042
|
+
await new Promise((resolve) => {
|
|
3043
|
+
process2.once("SIGINT", () => resolve());
|
|
3044
|
+
process2.once("SIGTERM", () => resolve());
|
|
3045
|
+
});
|
|
3046
|
+
}
|
|
3047
|
+
process2.removeListener("SIGINT", stop);
|
|
3048
|
+
process2.removeListener("SIGTERM", stop);
|
|
3049
|
+
process2.stdout.write("\n leaving the room\u2026\n");
|
|
3050
|
+
await session.close();
|
|
3051
|
+
process2.stdout.write("\n");
|
|
3052
|
+
return 0;
|
|
3053
|
+
}
|
|
2613
3054
|
async function cmdFind(targetArg, any) {
|
|
2614
3055
|
if (targetArg === void 0 || targetArg.trim() === "") {
|
|
2615
3056
|
process2.stderr.write("usage: vibedating find <@handle> [--any]\n");
|
|
@@ -2846,10 +3287,11 @@ Usage:
|
|
|
2846
3287
|
vibedating connect Read your usage, compute + print your league
|
|
2847
3288
|
vibedating matches [--live] List candidates in your league (live peers if any)
|
|
2848
3289
|
vibedating discover [--live] [--any] Find live peers over the DHT (your league + adjacent; --any = everyone)
|
|
2849
|
-
vibedating live [--dating] [--any] [--to @handle] [--keep-alive] Live chat (your league
|
|
3290
|
+
vibedating live [--dating] [--any] [--to @handle] [--keep-alive] [--via-relay] Live chat (your league
|
|
2850
3291
|
+ adjacent; --any = everyone; /next or --dating pick;
|
|
2851
3292
|
--to targets one peer; --keep-alive survives stdin EOF \u2014
|
|
2852
|
-
automatic when stdin is not a TTY
|
|
3293
|
+
automatic when stdin is not a TTY; --via-relay routes e2e chat
|
|
3294
|
+
through public Nostr relays when direct hole-punching fails)
|
|
2853
3295
|
vibedating find <@handle> [--any] Search the DHT for one specific handle (\u2605 highlights a match)
|
|
2854
3296
|
vibedating handle [@name] Print or set your handle (persisted; a leading '@' is optional)
|
|
2855
3297
|
vibedating block <@handle> Block a handle \u2014 their hello is dropped (never recorded/paired)
|
|
@@ -2860,9 +3302,15 @@ Usage:
|
|
|
2860
3302
|
NEW matches, never opens chat/video. install adds a
|
|
2861
3303
|
login service (launchd on macOS, systemd on Linux);
|
|
2862
3304
|
uninstall removes it.
|
|
2863
|
-
vibedating
|
|
2864
|
-
+
|
|
2865
|
-
|
|
3305
|
+
vibedating room <name> Join/create a named room (multi-peer): live roster
|
|
3306
|
+
+ group text chat broadcast to all members.
|
|
3307
|
+
Consent-gated like live. /who lists members, /quit leaves.
|
|
3308
|
+
vibedating open [--port N] [--any] [--room <name>] Serve the local web app (default:
|
|
3309
|
+
random port) + live video + chat with connected peers
|
|
3310
|
+
(your league + adjacent; --any = every league).
|
|
3311
|
+
--room <name> opens the room view instead: roster +
|
|
3312
|
+
group chat + full-mesh group video (~6 people; an SFU
|
|
3313
|
+
is the upgrade path for bigger rooms).
|
|
2866
3314
|
vibedating mcp Run the stdio MCP server (profile, matches)
|
|
2867
3315
|
vibedating --version
|
|
2868
3316
|
vibedating --help
|
|
@@ -2912,13 +3360,15 @@ async function main(argv) {
|
|
|
2912
3360
|
case "matches":
|
|
2913
3361
|
return cmdMatches(parsed.live);
|
|
2914
3362
|
case "discover":
|
|
2915
|
-
return cmdDiscover(parsed.live, parsed.any);
|
|
3363
|
+
return cmdDiscover(parsed.live, parsed.any, parsed.viaRelay);
|
|
2916
3364
|
case "open":
|
|
2917
|
-
return cmdOpen(parsed.port, parsed.any);
|
|
3365
|
+
return cmdOpen(parsed.port, parsed.any, parsed.room);
|
|
2918
3366
|
case "live":
|
|
2919
|
-
return cmdLive(parsed.dating, parsed.any, parsed.to, parsed.keepAlive);
|
|
3367
|
+
return cmdLive(parsed.dating, parsed.any, parsed.to, parsed.keepAlive, parsed.viaRelay);
|
|
2920
3368
|
case "find":
|
|
2921
3369
|
return cmdFind(parsed.arg, parsed.any);
|
|
3370
|
+
case "room":
|
|
3371
|
+
return cmdRoom(parsed.arg ?? parsed.room, parsed.keepAlive);
|
|
2922
3372
|
case "mcp":
|
|
2923
3373
|
await runMcp();
|
|
2924
3374
|
return 0;
|