vibedate 0.8.2 → 0.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-S7MO5OSO.js → chunk-KIFZHCWP.js} +240 -53
- package/dist/{chunk-JBADPOR5.js → chunk-VHO7JWSS.js} +37 -3
- package/dist/cli.js +70 -24
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +2 -2
- package/dist/{room-BStsMaeT.d.ts → room-B-0DreC0.d.ts} +17 -5
- package/package.json +4 -2
|
@@ -18,13 +18,18 @@ import { makeEvent, notify as vibeCoreNotify } from "@pooriaarab/vibe-core";
|
|
|
18
18
|
var MAX_TEXT_LEN = 4e3;
|
|
19
19
|
var MAX_ID_LEN = 64;
|
|
20
20
|
var MAX_B64_CHUNK_LEN = 16 * 1024;
|
|
21
|
+
var MAX_BINARY_CHUNK_BYTES = 64 * 1024;
|
|
21
22
|
var MAX_MEDIA_SIZE = 25 * 1024 * 1024;
|
|
22
23
|
var MAX_MIME_LEN = 128;
|
|
23
24
|
var MAX_NAME_LEN = 256;
|
|
24
25
|
var MAX_SDP_LEN = 64 * 1024;
|
|
25
26
|
var MAX_CANDIDATE_LEN = 4 * 1024;
|
|
26
27
|
var MAX_FRAME_LEN = Math.max(MAX_B64_CHUNK_LEN, 2 * MAX_SDP_LEN) + 2048;
|
|
28
|
+
var BIN_MEDIA_CHUNK_TAG = 1;
|
|
27
29
|
function serializeFrame(f) {
|
|
30
|
+
if (f.t === "media-chunk" && "data" in f) {
|
|
31
|
+
throw new Error("binary media-chunk must use serializeBinaryMediaChunk, not serializeFrame");
|
|
32
|
+
}
|
|
28
33
|
return JSON.stringify(f);
|
|
29
34
|
}
|
|
30
35
|
function parseFrame(raw) {
|
|
@@ -123,6 +128,107 @@ function parseFrame(raw) {
|
|
|
123
128
|
return null;
|
|
124
129
|
}
|
|
125
130
|
}
|
|
131
|
+
function serializeBinaryMediaChunk(parts) {
|
|
132
|
+
const { id, seq, data } = parts;
|
|
133
|
+
if (typeof id !== "string" || id.length === 0 || id.length > MAX_ID_LEN) {
|
|
134
|
+
throw new Error(`invalid media chunk id length: ${typeof id === "string" ? id.length : "?"}`);
|
|
135
|
+
}
|
|
136
|
+
if (typeof seq !== "number" || !Number.isFinite(seq) || !Number.isInteger(seq) || seq < 0) {
|
|
137
|
+
throw new Error(`invalid media chunk seq: ${String(seq)}`);
|
|
138
|
+
}
|
|
139
|
+
if (!Buffer.isBuffer(data) || data.length === 0 || data.length > MAX_BINARY_CHUNK_BYTES) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`invalid media chunk payload length: ${Buffer.isBuffer(data) ? data.length : "n/a"}`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
const hdr = Buffer.from(JSON.stringify({ id, seq }), "utf8");
|
|
145
|
+
if (hdr.length > 65535) throw new Error(`media chunk header too large: ${hdr.length}`);
|
|
146
|
+
const out = Buffer.allocUnsafe(1 + 2 + 4 + hdr.length + data.length);
|
|
147
|
+
let o = 0;
|
|
148
|
+
out[o++] = BIN_MEDIA_CHUNK_TAG;
|
|
149
|
+
out.writeUInt16BE(hdr.length, o);
|
|
150
|
+
o += 2;
|
|
151
|
+
out.writeUInt32BE(data.length, o);
|
|
152
|
+
o += 4;
|
|
153
|
+
hdr.copy(out, o);
|
|
154
|
+
o += hdr.length;
|
|
155
|
+
data.copy(out, o);
|
|
156
|
+
return out;
|
|
157
|
+
}
|
|
158
|
+
function tryParseBinaryMediaChunk(buf) {
|
|
159
|
+
if (buf.length === 0) return { frame: null, bytesConsumed: 0 };
|
|
160
|
+
if (buf[0] !== BIN_MEDIA_CHUNK_TAG) return { frame: null, bytesConsumed: 0 };
|
|
161
|
+
if (buf.length < 7) return { frame: null, bytesConsumed: 0 };
|
|
162
|
+
const hdrLen = buf.readUInt16BE(1);
|
|
163
|
+
const payloadLen = buf.readUInt32BE(3);
|
|
164
|
+
const MAX_HDR_LEN = 512;
|
|
165
|
+
if (hdrLen === 0 || hdrLen > MAX_HDR_LEN || payloadLen === 0 || payloadLen > MAX_BINARY_CHUNK_BYTES) {
|
|
166
|
+
return { frame: null, bytesConsumed: 1 };
|
|
167
|
+
}
|
|
168
|
+
const total = 7 + hdrLen + payloadLen;
|
|
169
|
+
if (buf.length < total) return { frame: null, bytesConsumed: 0 };
|
|
170
|
+
const hdrBuf = buf.subarray(7, 7 + hdrLen);
|
|
171
|
+
let hdrObj;
|
|
172
|
+
try {
|
|
173
|
+
hdrObj = JSON.parse(hdrBuf.toString("utf8"));
|
|
174
|
+
} catch {
|
|
175
|
+
return { frame: null, bytesConsumed: 1 };
|
|
176
|
+
}
|
|
177
|
+
if (typeof hdrObj !== "object" || hdrObj === null || Array.isArray(hdrObj)) {
|
|
178
|
+
return { frame: null, bytesConsumed: 1 };
|
|
179
|
+
}
|
|
180
|
+
const r = hdrObj;
|
|
181
|
+
const id = r["id"];
|
|
182
|
+
const seq = r["seq"];
|
|
183
|
+
if (typeof id !== "string" || id.length === 0 || id.length > MAX_ID_LEN) {
|
|
184
|
+
return { frame: null, bytesConsumed: 1 };
|
|
185
|
+
}
|
|
186
|
+
if (typeof seq !== "number" || !Number.isFinite(seq) || !Number.isInteger(seq) || seq < 0) {
|
|
187
|
+
return { frame: null, bytesConsumed: 1 };
|
|
188
|
+
}
|
|
189
|
+
const data = Buffer.from(buf.subarray(7 + hdrLen, 7 + hdrLen + payloadLen));
|
|
190
|
+
return {
|
|
191
|
+
frame: { t: "media-chunk", id, seq, data },
|
|
192
|
+
bytesConsumed: total
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function pullFramesFromBuffer(buf) {
|
|
196
|
+
const frames = [];
|
|
197
|
+
let offset = 0;
|
|
198
|
+
while (offset < buf.length) {
|
|
199
|
+
const first = buf[offset];
|
|
200
|
+
if (first === BIN_MEDIA_CHUNK_TAG) {
|
|
201
|
+
const slice = offset === 0 ? buf : buf.subarray(offset);
|
|
202
|
+
const { frame: frame2, bytesConsumed } = tryParseBinaryMediaChunk(slice);
|
|
203
|
+
if (bytesConsumed === 0) {
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
if (frame2 !== null) frames.push(frame2);
|
|
207
|
+
offset += bytesConsumed;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
const nlRel = buf.indexOf(10, offset);
|
|
211
|
+
if (nlRel < 0) {
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
const lineBuf = buf.subarray(offset, nlRel);
|
|
215
|
+
offset = nlRel + 1;
|
|
216
|
+
if (lineBuf.length === 0) continue;
|
|
217
|
+
let onlyWs = true;
|
|
218
|
+
for (let i = 0; i < lineBuf.length; i++) {
|
|
219
|
+
const c = lineBuf[i];
|
|
220
|
+
if (c !== 32 && c !== 9 && c !== 13) {
|
|
221
|
+
onlyWs = false;
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (onlyWs) continue;
|
|
226
|
+
const frame = parseFrame(lineBuf);
|
|
227
|
+
if (frame !== null) frames.push(frame);
|
|
228
|
+
}
|
|
229
|
+
const rest = offset === 0 ? buf : buf.subarray(offset);
|
|
230
|
+
return { frames, rest: rest.length === 0 ? Buffer.alloc(0) : Buffer.from(rest) };
|
|
231
|
+
}
|
|
126
232
|
|
|
127
233
|
// src/identity.ts
|
|
128
234
|
import {
|
|
@@ -137,10 +243,15 @@ import { chmodSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writ
|
|
|
137
243
|
import path2 from "path";
|
|
138
244
|
|
|
139
245
|
// src/state.ts
|
|
140
|
-
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
246
|
+
import { mkdirSync, readFileSync, rmSync, renameSync, writeFileSync } from "fs";
|
|
141
247
|
import os from "os";
|
|
142
248
|
import path from "path";
|
|
143
249
|
import { createConsentLedger } from "@pooriaarab/vibe-core";
|
|
250
|
+
function writeJsonAtomic(filePath, obj) {
|
|
251
|
+
const tmpPath = `${filePath}.tmp`;
|
|
252
|
+
writeFileSync(tmpPath, JSON.stringify(obj, null, 2) + "\n", "utf8");
|
|
253
|
+
renameSync(tmpPath, filePath);
|
|
254
|
+
}
|
|
144
255
|
var CONSENT_SCOPE = "share:league";
|
|
145
256
|
var LIVE_CONSENT_SCOPE = "share:live";
|
|
146
257
|
function defaultStateDir() {
|
|
@@ -162,7 +273,7 @@ var FileConsentStore = class {
|
|
|
162
273
|
}
|
|
163
274
|
save(grants) {
|
|
164
275
|
mkdirSync(path.dirname(this.file), { recursive: true });
|
|
165
|
-
|
|
276
|
+
writeJsonAtomic(this.file, { grants });
|
|
166
277
|
}
|
|
167
278
|
};
|
|
168
279
|
function createLedger(dir = defaultStateDir()) {
|
|
@@ -184,7 +295,7 @@ function connectProfile(snapshot, handle, dir = defaultStateDir()) {
|
|
|
184
295
|
connectedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
185
296
|
};
|
|
186
297
|
mkdirSync(dir, { recursive: true });
|
|
187
|
-
|
|
298
|
+
writeJsonAtomic(profilePath(dir), state);
|
|
188
299
|
return state;
|
|
189
300
|
}
|
|
190
301
|
function loadProfile(dir = defaultStateDir()) {
|
|
@@ -245,14 +356,10 @@ function saveHandle(handle, dir = defaultStateDir()) {
|
|
|
245
356
|
);
|
|
246
357
|
}
|
|
247
358
|
mkdirSync(dir, { recursive: true });
|
|
248
|
-
|
|
359
|
+
writeJsonAtomic(handleFilePath(dir), { handle: canonical });
|
|
249
360
|
const existing = loadProfile(dir);
|
|
250
361
|
if (existing !== null) {
|
|
251
|
-
|
|
252
|
-
profilePath(dir),
|
|
253
|
-
JSON.stringify({ ...existing, handle: canonical }, null, 2) + "\n",
|
|
254
|
-
"utf8"
|
|
255
|
-
);
|
|
362
|
+
writeJsonAtomic(profilePath(dir), { ...existing, handle: canonical });
|
|
256
363
|
}
|
|
257
364
|
return canonical;
|
|
258
365
|
}
|
|
@@ -270,7 +377,7 @@ function blocklistPath(dir) {
|
|
|
270
377
|
}
|
|
271
378
|
function persistBlocklist(blocked, dir) {
|
|
272
379
|
mkdirSync(dir, { recursive: true });
|
|
273
|
-
|
|
380
|
+
writeJsonAtomic(blocklistPath(dir), { blocked });
|
|
274
381
|
}
|
|
275
382
|
function loadBlocklist(dir = defaultStateDir()) {
|
|
276
383
|
try {
|
|
@@ -442,7 +549,7 @@ import { readFile } from "fs/promises";
|
|
|
442
549
|
import { writeFileSync as writeFileSync3 } from "fs";
|
|
443
550
|
import os2 from "os";
|
|
444
551
|
import path3 from "path";
|
|
445
|
-
var DEFAULT_CHUNK_BYTES =
|
|
552
|
+
var DEFAULT_CHUNK_BYTES = MAX_BINARY_CHUNK_BYTES;
|
|
446
553
|
var MIME_BY_EXT = {
|
|
447
554
|
".png": "image/png",
|
|
448
555
|
".jpg": "image/jpeg",
|
|
@@ -455,11 +562,32 @@ var MIME_BY_EXT = {
|
|
|
455
562
|
function inferMime(name) {
|
|
456
563
|
return MIME_BY_EXT[path3.extname(name).toLowerCase()] ?? "application/octet-stream";
|
|
457
564
|
}
|
|
458
|
-
function
|
|
459
|
-
return new Promise((resolve) => {
|
|
460
|
-
const ok = socket.write(
|
|
565
|
+
function writeBytes(socket, data) {
|
|
566
|
+
return new Promise((resolve, reject) => {
|
|
567
|
+
const ok = socket.write(data);
|
|
461
568
|
if (ok) resolve();
|
|
462
|
-
else
|
|
569
|
+
else {
|
|
570
|
+
const onDrain = () => {
|
|
571
|
+
cleanup();
|
|
572
|
+
resolve();
|
|
573
|
+
};
|
|
574
|
+
const onClose = () => {
|
|
575
|
+
cleanup();
|
|
576
|
+
reject(new Error("Socket closed before drain"));
|
|
577
|
+
};
|
|
578
|
+
const onError = (err) => {
|
|
579
|
+
cleanup();
|
|
580
|
+
reject(err);
|
|
581
|
+
};
|
|
582
|
+
const cleanup = () => {
|
|
583
|
+
socket.removeListener("drain", onDrain);
|
|
584
|
+
socket.removeListener("close", onClose);
|
|
585
|
+
socket.removeListener("error", onError);
|
|
586
|
+
};
|
|
587
|
+
socket.once("drain", onDrain);
|
|
588
|
+
socket.once("close", onClose);
|
|
589
|
+
socket.once("error", onError);
|
|
590
|
+
}
|
|
463
591
|
});
|
|
464
592
|
}
|
|
465
593
|
async function sendMedia(opts) {
|
|
@@ -471,15 +599,23 @@ async function sendMedia(opts) {
|
|
|
471
599
|
}
|
|
472
600
|
if (mime.length > MAX_MIME_LEN) throw new Error(`mime too long: ${mime.length} > ${MAX_MIME_LEN}`);
|
|
473
601
|
if (name.length > MAX_NAME_LEN) throw new Error(`name too long: ${name.length} > ${MAX_NAME_LEN}`);
|
|
474
|
-
await
|
|
475
|
-
const chunkBytes =
|
|
602
|
+
await writeBytes(socket, serializeFrame({ t: "media-start", id, mime, size, name }) + "\n");
|
|
603
|
+
const chunkBytes = Math.min(
|
|
604
|
+
opts.chunkBytes ?? DEFAULT_CHUNK_BYTES,
|
|
605
|
+
opts.legacyJson ? Math.floor(16 * 1024 * 3 / 4) : MAX_BINARY_CHUNK_BYTES
|
|
606
|
+
);
|
|
476
607
|
let seq = 0;
|
|
477
608
|
for (let off = 0; off < size; off += chunkBytes) {
|
|
478
|
-
const
|
|
479
|
-
|
|
609
|
+
const slice = data.subarray(off, off + chunkBytes);
|
|
610
|
+
if (opts.legacyJson) {
|
|
611
|
+
const b64 = slice.toString("base64");
|
|
612
|
+
await writeBytes(socket, serializeFrame({ t: "media-chunk", id, seq, b64 }) + "\n");
|
|
613
|
+
} else {
|
|
614
|
+
await writeBytes(socket, serializeBinaryMediaChunk({ id, seq, data: slice }));
|
|
615
|
+
}
|
|
480
616
|
seq++;
|
|
481
617
|
}
|
|
482
|
-
await
|
|
618
|
+
await writeBytes(socket, serializeFrame({ t: "media-end", id }) + "\n");
|
|
483
619
|
return { id, size };
|
|
484
620
|
}
|
|
485
621
|
async function sendMediaFile(opts) {
|
|
@@ -492,7 +628,8 @@ async function sendMediaFile(opts) {
|
|
|
492
628
|
mime,
|
|
493
629
|
name,
|
|
494
630
|
id: opts.id,
|
|
495
|
-
chunkBytes: opts.chunkBytes
|
|
631
|
+
chunkBytes: opts.chunkBytes,
|
|
632
|
+
legacyJson: opts.legacyJson
|
|
496
633
|
});
|
|
497
634
|
}
|
|
498
635
|
function safeName(id, name) {
|
|
@@ -533,9 +670,20 @@ var MediaReceiver = class {
|
|
|
533
670
|
return;
|
|
534
671
|
}
|
|
535
672
|
let bytes;
|
|
536
|
-
|
|
537
|
-
bytes =
|
|
538
|
-
|
|
673
|
+
if ("data" in frame && Buffer.isBuffer(frame.data)) {
|
|
674
|
+
bytes = frame.data;
|
|
675
|
+
if (bytes.length === 0 || bytes.length > MAX_BINARY_CHUNK_BYTES) {
|
|
676
|
+
this.abort(frame.id);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
} else if ("b64" in frame && typeof frame.b64 === "string") {
|
|
680
|
+
try {
|
|
681
|
+
bytes = Buffer.from(frame.b64, "base64");
|
|
682
|
+
} catch {
|
|
683
|
+
this.abort(frame.id);
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
} else {
|
|
539
687
|
this.abort(frame.id);
|
|
540
688
|
return;
|
|
541
689
|
}
|
|
@@ -558,7 +706,9 @@ var MediaReceiver = class {
|
|
|
558
706
|
const filePath = path3.join(this.opts.tmpDir ?? os2.tmpdir(), safeName(frame.id, tx.name));
|
|
559
707
|
try {
|
|
560
708
|
writeFileSync3(filePath, buf);
|
|
561
|
-
} catch {
|
|
709
|
+
} catch (err) {
|
|
710
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
711
|
+
this.onMedia({ mime: tx.mime, name: tx.name, path: filePath, size: tx.received, error });
|
|
562
712
|
return;
|
|
563
713
|
}
|
|
564
714
|
this.onMedia({ mime: tx.mime, name: tx.name, path: filePath, size: tx.received });
|
|
@@ -576,7 +726,7 @@ function createPeerLink(socket, hello, initialBuffer = "", linkOpts = {}) {
|
|
|
576
726
|
const mediaCbs = /* @__PURE__ */ new Set();
|
|
577
727
|
const signalCbs = /* @__PURE__ */ new Set();
|
|
578
728
|
const closeCbs = /* @__PURE__ */ new Set();
|
|
579
|
-
let buf = initialBuffer;
|
|
729
|
+
let buf = typeof initialBuffer === "string" ? Buffer.from(initialBuffer, "utf8") : Buffer.from(initialBuffer);
|
|
580
730
|
let closed = false;
|
|
581
731
|
let mediaReceiver;
|
|
582
732
|
const ensureMediaReceiver = () => {
|
|
@@ -624,19 +774,15 @@ function createPeerLink(socket, hello, initialBuffer = "", linkOpts = {}) {
|
|
|
624
774
|
}
|
|
625
775
|
};
|
|
626
776
|
const pump = () => {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
if (line.trim() === "") continue;
|
|
632
|
-
const frame = parseFrame(line);
|
|
633
|
-
if (frame === null) continue;
|
|
634
|
-
dispatch(frame);
|
|
635
|
-
}
|
|
777
|
+
if (buf.length === 0) return;
|
|
778
|
+
const { frames, rest } = pullFramesFromBuffer(buf);
|
|
779
|
+
buf = rest;
|
|
780
|
+
for (const frame of frames) dispatch(frame);
|
|
636
781
|
};
|
|
637
782
|
pump();
|
|
638
783
|
socket.on("data", (chunk) => {
|
|
639
|
-
|
|
784
|
+
const bytes = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
|
|
785
|
+
buf = buf.length === 0 ? Buffer.from(bytes) : Buffer.concat([buf, bytes]);
|
|
640
786
|
pump();
|
|
641
787
|
});
|
|
642
788
|
socket.on("end", () => {
|
|
@@ -659,6 +805,9 @@ function createPeerLink(socket, hello, initialBuffer = "", linkOpts = {}) {
|
|
|
659
805
|
});
|
|
660
806
|
return {
|
|
661
807
|
hello,
|
|
808
|
+
get closed() {
|
|
809
|
+
return closed;
|
|
810
|
+
},
|
|
662
811
|
send(text) {
|
|
663
812
|
if (closed) return;
|
|
664
813
|
const frame = { t: "msg", id: randomUUID2(), text, at: Date.now() };
|
|
@@ -778,11 +927,13 @@ function parseHandshake(raw) {
|
|
|
778
927
|
function peersPath(dir) {
|
|
779
928
|
return path4.join(dir, "peers.json");
|
|
780
929
|
}
|
|
781
|
-
function loadPeers(dir = defaultStateDir()) {
|
|
930
|
+
function loadPeers(dir = defaultStateDir(), maxAgeMs = 7 * 24 * 60 * 60 * 1e3) {
|
|
782
931
|
try {
|
|
783
932
|
const raw = readFileSync3(peersPath(dir), "utf8");
|
|
784
933
|
const data = JSON.parse(raw);
|
|
785
|
-
|
|
934
|
+
if (!Array.isArray(data.peers)) return [];
|
|
935
|
+
const now = Date.now();
|
|
936
|
+
return data.peers.filter((p) => now - new Date(p.lastSeenAt).getTime() <= maxAgeMs);
|
|
786
937
|
} catch {
|
|
787
938
|
return [];
|
|
788
939
|
}
|
|
@@ -798,7 +949,9 @@ function recordPeer(hello, dir = defaultStateDir(), now = /* @__PURE__ */ new Da
|
|
|
798
949
|
...hello.pubkey !== void 0 ? { pubkey: hello.pubkey } : {},
|
|
799
950
|
...hello.identityVerified !== void 0 ? { identityVerified: hello.identityVerified } : {}
|
|
800
951
|
};
|
|
801
|
-
const existing = peers.findIndex(
|
|
952
|
+
const existing = peers.findIndex(
|
|
953
|
+
(p) => clean.pubkey !== void 0 && p.pubkey !== void 0 ? p.pubkey === clean.pubkey : p.handle === clean.handle
|
|
954
|
+
);
|
|
802
955
|
let isNew;
|
|
803
956
|
let peer;
|
|
804
957
|
if (existing >= 0) {
|
|
@@ -821,10 +974,12 @@ function recordPeer(hello, dir = defaultStateDir(), now = /* @__PURE__ */ new Da
|
|
|
821
974
|
writeFileSync4(peersPath(dir), JSON.stringify({ peers }, null, 2) + "\n", "utf8");
|
|
822
975
|
return { peer, isNew };
|
|
823
976
|
}
|
|
824
|
-
function recordPeerMessage(
|
|
977
|
+
function recordPeerMessage(peer, dir = defaultStateDir(), now = /* @__PURE__ */ new Date()) {
|
|
825
978
|
try {
|
|
826
979
|
const peers = loadPeers(dir);
|
|
827
|
-
const idx = peers.findIndex(
|
|
980
|
+
const idx = peers.findIndex(
|
|
981
|
+
(p) => peer.pubkey !== void 0 && p.pubkey !== void 0 ? p.pubkey === peer.pubkey : p.handle === peer.handle
|
|
982
|
+
);
|
|
828
983
|
if (idx < 0) return false;
|
|
829
984
|
peers[idx] = { ...peers[idx], lastMessageAt: now.toISOString() };
|
|
830
985
|
mkdirSync3(dir, { recursive: true });
|
|
@@ -857,15 +1012,18 @@ async function startDiscovery(opts) {
|
|
|
857
1012
|
...hello.sig !== void 0 ? { sig: hello.sig } : {}
|
|
858
1013
|
}) + "\n"
|
|
859
1014
|
);
|
|
860
|
-
let buf =
|
|
1015
|
+
let buf = Buffer.alloc(0);
|
|
861
1016
|
let handedOff = false;
|
|
862
1017
|
const onData = (chunk) => {
|
|
863
1018
|
if (handedOff) return;
|
|
864
|
-
buf
|
|
1019
|
+
buf = buf.length === 0 ? Buffer.from(chunk) : Buffer.concat([buf, chunk]);
|
|
865
1020
|
let nl;
|
|
866
|
-
while ((nl = buf.indexOf(
|
|
867
|
-
|
|
868
|
-
|
|
1021
|
+
while ((nl = buf.indexOf(
|
|
1022
|
+
10
|
|
1023
|
+
/* \n */
|
|
1024
|
+
)) >= 0) {
|
|
1025
|
+
const line = buf.subarray(0, nl).toString("utf8");
|
|
1026
|
+
buf = buf.subarray(nl + 1);
|
|
869
1027
|
if (line.trim() === "") continue;
|
|
870
1028
|
const frame = parseFrame(line);
|
|
871
1029
|
if (frame === null) continue;
|
|
@@ -907,11 +1065,11 @@ async function startDiscovery(opts) {
|
|
|
907
1065
|
if (onLink !== void 0) {
|
|
908
1066
|
const link = createPeerLink(socket, peer, buf);
|
|
909
1067
|
link.onMessage(() => {
|
|
910
|
-
recordPeerMessage(peer
|
|
1068
|
+
recordPeerMessage(peer, stateDir);
|
|
911
1069
|
});
|
|
912
1070
|
onLink(link);
|
|
913
1071
|
}
|
|
914
|
-
buf =
|
|
1072
|
+
buf = Buffer.alloc(0);
|
|
915
1073
|
return;
|
|
916
1074
|
}
|
|
917
1075
|
};
|
|
@@ -1061,6 +1219,14 @@ async function createNostrRelayLink(opts) {
|
|
|
1061
1219
|
return;
|
|
1062
1220
|
}
|
|
1063
1221
|
peerNostrPubkey = event.pubkey;
|
|
1222
|
+
try {
|
|
1223
|
+
const parsed = JSON.parse(plaintext);
|
|
1224
|
+
if (parsed && typeof parsed === "object" && "__vibe_rtc" in parsed) {
|
|
1225
|
+
for (const cb of signalCbs) cb(parsed.__vibe_rtc);
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
} catch {
|
|
1229
|
+
}
|
|
1064
1230
|
const msg = { id: event.id, text: plaintext, at: event.created_at * 1e3 };
|
|
1065
1231
|
for (const cb of messageCbs) cb(msg);
|
|
1066
1232
|
return;
|
|
@@ -1069,6 +1235,9 @@ async function createNostrRelayLink(opts) {
|
|
|
1069
1235
|
publishPresence();
|
|
1070
1236
|
return {
|
|
1071
1237
|
hello,
|
|
1238
|
+
get closed() {
|
|
1239
|
+
return closed;
|
|
1240
|
+
},
|
|
1072
1241
|
send(text) {
|
|
1073
1242
|
if (closed) return;
|
|
1074
1243
|
if (peerNostrPubkey === void 0) {
|
|
@@ -1082,9 +1251,14 @@ async function createNostrRelayLink(opts) {
|
|
|
1082
1251
|
async sendMedia() {
|
|
1083
1252
|
return { id: "", size: 0 };
|
|
1084
1253
|
},
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1254
|
+
sendSignal(frame) {
|
|
1255
|
+
if (closed) return;
|
|
1256
|
+
const payload = JSON.stringify({ __vibe_rtc: frame });
|
|
1257
|
+
if (peerNostrPubkey === void 0) {
|
|
1258
|
+
pending.push(payload);
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
sendEncrypted(payload, peerNostrPubkey);
|
|
1088
1262
|
},
|
|
1089
1263
|
onMessage(cb) {
|
|
1090
1264
|
messageCbs.add(cb);
|
|
@@ -1157,6 +1331,7 @@ var ROOM_TOPIC_PREFIX = "vibedate-room:";
|
|
|
1157
1331
|
function roomTopic(name) {
|
|
1158
1332
|
return createHash3("sha256").update(`${ROOM_TOPIC_PREFIX}${name}`, "utf8").digest();
|
|
1159
1333
|
}
|
|
1334
|
+
var MAX_ROOM = 32;
|
|
1160
1335
|
async function startRoom(opts) {
|
|
1161
1336
|
const topic = roomTopic(opts.room);
|
|
1162
1337
|
const entries = /* @__PURE__ */ new Map();
|
|
@@ -1180,7 +1355,19 @@ async function startRoom(opts) {
|
|
|
1180
1355
|
...opts.stateDir === void 0 ? {} : { stateDir: opts.stateDir },
|
|
1181
1356
|
...opts.notify === void 0 ? {} : { notify: opts.notify },
|
|
1182
1357
|
onLink: (link) => {
|
|
1358
|
+
const isSelf = link.hello.pubkey !== void 0 && opts.hello.pubkey !== void 0 ? link.hello.pubkey === opts.hello.pubkey : link.hello.handle === opts.hello.handle;
|
|
1359
|
+
if (isSelf) {
|
|
1360
|
+
link.close();
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1183
1363
|
const handle = link.hello.handle;
|
|
1364
|
+
if (!entries.has(handle) && entries.size >= MAX_ROOM) {
|
|
1365
|
+
link.send(`Room is full (${MAX_ROOM} members max). Try again later.`);
|
|
1366
|
+
link.close();
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
const cur = entries.get(handle);
|
|
1370
|
+
if (cur) cur.link.close();
|
|
1184
1371
|
entries.set(handle, { hello: link.hello, link });
|
|
1185
1372
|
memberHellos.set(handle, link.hello);
|
|
1186
1373
|
fireRoster();
|
|
@@ -1191,8 +1378,8 @@ async function startRoom(opts) {
|
|
|
1191
1378
|
for (const cb of signalCbs) cb(handle, frame);
|
|
1192
1379
|
});
|
|
1193
1380
|
link.onClose(() => {
|
|
1194
|
-
const
|
|
1195
|
-
if (
|
|
1381
|
+
const cur2 = entries.get(handle);
|
|
1382
|
+
if (cur2 && cur2.link === link) {
|
|
1196
1383
|
entries.delete(handle);
|
|
1197
1384
|
memberHellos.delete(handle);
|
|
1198
1385
|
fireRoster();
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
signHelloClaims,
|
|
30
30
|
startDiscovery,
|
|
31
31
|
startRoom
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-KIFZHCWP.js";
|
|
33
33
|
|
|
34
34
|
// src/mcp.ts
|
|
35
35
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -14890,6 +14890,7 @@ function uninstallDaemonService(opts) {
|
|
|
14890
14890
|
|
|
14891
14891
|
// src/pairing.ts
|
|
14892
14892
|
var MAX_BUFFERED = 100;
|
|
14893
|
+
var MAX_QUEUE = 100;
|
|
14893
14894
|
function createPairing() {
|
|
14894
14895
|
const queue = [];
|
|
14895
14896
|
const matchCbs = /* @__PURE__ */ new Set();
|
|
@@ -14923,7 +14924,14 @@ function createPairing() {
|
|
|
14923
14924
|
link.onClose(() => {
|
|
14924
14925
|
buffers.delete(link);
|
|
14925
14926
|
if (current === link) {
|
|
14926
|
-
|
|
14927
|
+
let nextUp;
|
|
14928
|
+
while (queue.length > 0) {
|
|
14929
|
+
const cand = queue.shift();
|
|
14930
|
+
if (!cand.closed) {
|
|
14931
|
+
nextUp = cand;
|
|
14932
|
+
break;
|
|
14933
|
+
}
|
|
14934
|
+
}
|
|
14927
14935
|
setCurrent(nextUp);
|
|
14928
14936
|
} else {
|
|
14929
14937
|
const idx = queue.indexOf(link);
|
|
@@ -14959,12 +14967,31 @@ function createPairing() {
|
|
|
14959
14967
|
return current;
|
|
14960
14968
|
},
|
|
14961
14969
|
add(link) {
|
|
14970
|
+
const isSamePeer = (a, b) => {
|
|
14971
|
+
if (a.hello.pubkey !== void 0 && b.hello.pubkey !== void 0) {
|
|
14972
|
+
return a.hello.pubkey === b.hello.pubkey;
|
|
14973
|
+
}
|
|
14974
|
+
return a.hello.handle === b.hello.handle;
|
|
14975
|
+
};
|
|
14976
|
+
const existingIdx = queue.findIndex((l) => isSamePeer(l, link));
|
|
14977
|
+
if (existingIdx >= 0) {
|
|
14978
|
+
const old = queue.splice(existingIdx, 1)[0];
|
|
14979
|
+
old.close();
|
|
14980
|
+
} else if (current !== void 0 && isSamePeer(current, link)) {
|
|
14981
|
+
const old = current;
|
|
14982
|
+
current = void 0;
|
|
14983
|
+
old.close();
|
|
14984
|
+
}
|
|
14962
14985
|
watch(link);
|
|
14963
14986
|
bindMessages(link);
|
|
14964
14987
|
if (current === void 0) {
|
|
14965
14988
|
setCurrent(link);
|
|
14966
14989
|
} else {
|
|
14967
14990
|
queue.push(link);
|
|
14991
|
+
if (queue.length > MAX_QUEUE) {
|
|
14992
|
+
const oldest = queue.shift();
|
|
14993
|
+
oldest.close();
|
|
14994
|
+
}
|
|
14968
14995
|
}
|
|
14969
14996
|
},
|
|
14970
14997
|
next() {
|
|
@@ -14972,7 +14999,14 @@ function createPairing() {
|
|
|
14972
14999
|
current.close();
|
|
14973
15000
|
current = void 0;
|
|
14974
15001
|
}
|
|
14975
|
-
|
|
15002
|
+
let nextUp;
|
|
15003
|
+
while (queue.length > 0) {
|
|
15004
|
+
const cand = queue.shift();
|
|
15005
|
+
if (!cand.closed) {
|
|
15006
|
+
nextUp = cand;
|
|
15007
|
+
break;
|
|
15008
|
+
}
|
|
15009
|
+
}
|
|
14976
15010
|
setCurrent(nextUp);
|
|
14977
15011
|
return current;
|
|
14978
15012
|
},
|
package/dist/cli.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
stopDaemon,
|
|
11
11
|
uninstallDaemonService,
|
|
12
12
|
writeDaemonState
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-VHO7JWSS.js";
|
|
14
14
|
import {
|
|
15
15
|
CANDIDATES,
|
|
16
16
|
LIVE_NOTICE,
|
|
@@ -46,7 +46,7 @@ import {
|
|
|
46
46
|
signHelloClaims,
|
|
47
47
|
startDiscovery,
|
|
48
48
|
startRoom
|
|
49
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-KIFZHCWP.js";
|
|
50
50
|
|
|
51
51
|
// src/cli.ts
|
|
52
52
|
import readline from "readline";
|
|
@@ -1389,9 +1389,8 @@ var webAppHtml = `<!DOCTYPE html>
|
|
|
1389
1389
|
var idleIdx = 0;
|
|
1390
1390
|
|
|
1391
1391
|
function rtcConfig(){
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
return { iceServers: [{ urls: "stun:stun.l.google.com:19302" }] };
|
|
1392
|
+
var extra = (window.__VIBE_ICE__ && window.__VIBE_ICE__.iceServers) || [];
|
|
1393
|
+
return { iceServers: [{ urls: "stun:stun.l.google.com:19302" }].concat(extra) };
|
|
1395
1394
|
}
|
|
1396
1395
|
function sleep(ms){ return new Promise(function(r){ setTimeout(r, ms); }); }
|
|
1397
1396
|
|
|
@@ -2228,10 +2227,11 @@ function createLiveBridge() {
|
|
|
2228
2227
|
link.onMedia((m) => {
|
|
2229
2228
|
const mb = boxes.get(handle2);
|
|
2230
2229
|
if (!mb) {
|
|
2231
|
-
fs.unlink(m.path, () => {
|
|
2230
|
+
if (!m.error) fs.unlink(m.path, () => {
|
|
2232
2231
|
});
|
|
2233
2232
|
return;
|
|
2234
2233
|
}
|
|
2234
|
+
if (m.error) return;
|
|
2235
2235
|
mb.media.push({ ...m, name: sanitizePeerText(m.name), mime: sanitizePeerText(m.mime) });
|
|
2236
2236
|
if (mb.media.length > MAX_QUEUED_MESSAGES) {
|
|
2237
2237
|
const dropped = mb.media.splice(0, mb.media.length - MAX_QUEUED_MESSAGES);
|
|
@@ -2459,7 +2459,18 @@ async function handle(req, res, opts) {
|
|
|
2459
2459
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
2460
2460
|
const pathname = url.pathname;
|
|
2461
2461
|
if (req.method === "GET" && pathname === "/") {
|
|
2462
|
-
|
|
2462
|
+
const turnUrl = process.env["VIBEDATE_TURN_URL"];
|
|
2463
|
+
let html = webAppHtml;
|
|
2464
|
+
if (turnUrl) {
|
|
2465
|
+
const turnUser = process.env["VIBEDATE_TURN_USER"];
|
|
2466
|
+
const turnCred = process.env["VIBEDATE_TURN_CRED"];
|
|
2467
|
+
const server = { urls: turnUrl };
|
|
2468
|
+
if (turnUser) server.username = turnUser;
|
|
2469
|
+
if (turnCred) server.credential = turnCred;
|
|
2470
|
+
const script = `<script>window.__VIBE_ICE__={iceServers:${JSON.stringify([server]).replace(/</g, "\\u003c")}};</script>`;
|
|
2471
|
+
html = html.replace("<script>", script + "\n<script>");
|
|
2472
|
+
}
|
|
2473
|
+
send(res, 200, "text/html; charset=utf-8", html);
|
|
2463
2474
|
return;
|
|
2464
2475
|
}
|
|
2465
2476
|
if (req.method === "GET" && pathname === "/api/state") {
|
|
@@ -2784,7 +2795,7 @@ async function handle(req, res, opts) {
|
|
|
2784
2795
|
}
|
|
2785
2796
|
|
|
2786
2797
|
// src/cli.ts
|
|
2787
|
-
var VERSION = "0.8.
|
|
2798
|
+
var VERSION = "0.8.5";
|
|
2788
2799
|
function parsePort(raw) {
|
|
2789
2800
|
const n = Number(raw);
|
|
2790
2801
|
if (!Number.isInteger(n) || n < 1 || n > 65535) return void 0;
|
|
@@ -3136,7 +3147,7 @@ async function cmdDiscover(live, any, viaRelay) {
|
|
|
3136
3147
|
);
|
|
3137
3148
|
return 0;
|
|
3138
3149
|
}
|
|
3139
|
-
async function cmdOpen(port, any, room) {
|
|
3150
|
+
async function cmdOpen(port, any, room, viaRelay, to) {
|
|
3140
3151
|
const profile = loadProfile();
|
|
3141
3152
|
let live;
|
|
3142
3153
|
let roomBridge;
|
|
@@ -3167,17 +3178,45 @@ async function cmdOpen(port, any, room) {
|
|
|
3167
3178
|
}).catch(() => {
|
|
3168
3179
|
});
|
|
3169
3180
|
} else if (live) {
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
+
if (viaRelay && to !== void 0) {
|
|
3182
|
+
const target = normalizeHandle(to);
|
|
3183
|
+
const peer = target ? loadPeers().find((p) => sameHandle(p.handle, target) && typeof p.pubkey === "string") : void 0;
|
|
3184
|
+
if (peer && peer.pubkey) {
|
|
3185
|
+
const peerPubkey = peer.pubkey;
|
|
3186
|
+
void (async () => {
|
|
3187
|
+
try {
|
|
3188
|
+
const myNostr = await loadOrCreateNostrKey();
|
|
3189
|
+
const transport = await createNostrPoolTransport();
|
|
3190
|
+
const identity = loadOrCreateIdentity();
|
|
3191
|
+
const link = await createNostrRelayLink({
|
|
3192
|
+
myNostr,
|
|
3193
|
+
myEd25519Hex: identity.publicKeyHex,
|
|
3194
|
+
peerEd25519Hex: peerPubkey,
|
|
3195
|
+
hello: peer,
|
|
3196
|
+
transport
|
|
3197
|
+
});
|
|
3198
|
+
live.addLink(link);
|
|
3199
|
+
} catch {
|
|
3200
|
+
}
|
|
3201
|
+
})();
|
|
3202
|
+
} else {
|
|
3203
|
+
process2.stderr.write(`
|
|
3204
|
+
warning: --via-relay peer ${to} not found or lacks identity pubkey.
|
|
3205
|
+
`);
|
|
3206
|
+
}
|
|
3207
|
+
} else {
|
|
3208
|
+
const { topics, acceptLeague } = discoveryScope(profile.league, any);
|
|
3209
|
+
void startDiscovery({
|
|
3210
|
+
hello,
|
|
3211
|
+
topics,
|
|
3212
|
+
acceptLeague,
|
|
3213
|
+
isBlocked: blockedChecker(),
|
|
3214
|
+
onLink: (link) => live.addLink(link)
|
|
3215
|
+
}).then((s) => {
|
|
3216
|
+
session = s;
|
|
3217
|
+
}).catch(() => {
|
|
3218
|
+
});
|
|
3219
|
+
}
|
|
3181
3220
|
}
|
|
3182
3221
|
}
|
|
3183
3222
|
process2.stdout.write(`
|
|
@@ -3390,10 +3429,17 @@ async function cmdLive(dating, any, to, keepAlive, viaRelay) {
|
|
|
3390
3429
|
`);
|
|
3391
3430
|
}
|
|
3392
3431
|
link.onMedia((m) => {
|
|
3393
|
-
|
|
3394
|
-
|
|
3432
|
+
if (m.error) {
|
|
3433
|
+
process2.stdout.write(
|
|
3434
|
+
` \u{1F4CE} <${sanitizePeerText(link.hello.handle)}> sent ${sanitizePeerText(m.name)} \u2014 FAILED to save: ${m.error.message}
|
|
3395
3435
|
`
|
|
3396
|
-
|
|
3436
|
+
);
|
|
3437
|
+
} else {
|
|
3438
|
+
process2.stdout.write(
|
|
3439
|
+
` \u{1F4CE} <${sanitizePeerText(link.hello.handle)}> sent ${sanitizePeerText(m.name)} \u2014 saved to ${m.path}
|
|
3440
|
+
`
|
|
3441
|
+
);
|
|
3442
|
+
}
|
|
3397
3443
|
});
|
|
3398
3444
|
pairing.add(link);
|
|
3399
3445
|
}
|
|
@@ -3884,7 +3930,7 @@ async function main(argv) {
|
|
|
3884
3930
|
case "discover":
|
|
3885
3931
|
return cmdDiscover(parsed.live, parsed.any, parsed.viaRelay);
|
|
3886
3932
|
case "open":
|
|
3887
|
-
return cmdOpen(parsed.port, parsed.any, parsed.room);
|
|
3933
|
+
return cmdOpen(parsed.port, parsed.any, parsed.room, parsed.viaRelay, parsed.to);
|
|
3888
3934
|
case "live":
|
|
3889
3935
|
return cmdLive(parsed.dating, parsed.any, parsed.to, parsed.keepAlive, parsed.viaRelay);
|
|
3890
3936
|
case "find":
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { UsageSnapshot, UsageSource, Harness, HarnessUsageOptions } from '@pooriaarab/vibe-core';
|
|
2
2
|
export { Harness, UsageSnapshot, UsageSource, createConsentLedger } from '@pooriaarab/vibe-core';
|
|
3
|
-
import { P as PeerHello, a as PeerLink } from './room-
|
|
4
|
-
export { D as DiscoveryOptions, b as DiscoverySession, L as LIVE_NOTICE, R as ROOM_TOPIC_PREFIX, c as RoomMember, d as RoomMessage, e as RoomOptions, f as RoomSession, S as StoredPeer, T as TOPIC_PREFIX, l as leagueTopic, g as loadPeers, p as parseHandshake, r as recordPeer, h as recordPeerMessage, i as roomTopic, s as serializeHandshake, j as startDiscovery, k as startRoom } from './room-
|
|
3
|
+
import { P as PeerHello, a as PeerLink } from './room-B-0DreC0.js';
|
|
4
|
+
export { D as DiscoveryOptions, b as DiscoverySession, L as LIVE_NOTICE, R as ROOM_TOPIC_PREFIX, c as RoomMember, d as RoomMessage, e as RoomOptions, f as RoomSession, S as StoredPeer, T as TOPIC_PREFIX, l as leagueTopic, g as loadPeers, p as parseHandshake, r as recordPeer, h as recordPeerMessage, i as roomTopic, s as serializeHandshake, j as startDiscovery, k as startRoom } from './room-B-0DreC0.js';
|
|
5
5
|
import { KeyObject } from 'node:crypto';
|
|
6
6
|
|
|
7
7
|
/**
|
package/dist/index.js
CHANGED
package/dist/mcp.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { a as PeerLink, P as PeerHello, f as RoomSession, b as DiscoverySession, d as RoomMessage } from './room-
|
|
2
|
+
import { a as PeerLink, P as PeerHello, f as RoomSession, b as DiscoverySession, d as RoomMessage } from './room-B-0DreC0.js';
|
|
3
3
|
import '@pooriaarab/vibe-core';
|
|
4
4
|
|
|
5
5
|
/**
|
package/dist/mcp.js
CHANGED
|
@@ -24,11 +24,20 @@ type Frame = {
|
|
|
24
24
|
mime: string;
|
|
25
25
|
size: number;
|
|
26
26
|
name: string;
|
|
27
|
-
}
|
|
27
|
+
}
|
|
28
|
+
/** Legacy JSON media-chunk (base64). Prefer binary wire for new sends. */
|
|
29
|
+
| {
|
|
28
30
|
t: 'media-chunk';
|
|
29
31
|
id: string;
|
|
30
32
|
seq: number;
|
|
31
33
|
b64: string;
|
|
34
|
+
}
|
|
35
|
+
/** Binary-path media-chunk: raw bytes, no base64. Produced by the binary framer. */
|
|
36
|
+
| {
|
|
37
|
+
t: 'media-chunk';
|
|
38
|
+
id: string;
|
|
39
|
+
seq: number;
|
|
40
|
+
data: Buffer;
|
|
32
41
|
} | {
|
|
33
42
|
t: 'media-end';
|
|
34
43
|
id: string;
|
|
@@ -56,11 +65,14 @@ interface ReceivedMedia {
|
|
|
56
65
|
/** Temp file path holding the reassembled bytes. */
|
|
57
66
|
readonly path: string;
|
|
58
67
|
readonly size: number;
|
|
68
|
+
readonly error?: Error;
|
|
59
69
|
}
|
|
60
70
|
|
|
61
71
|
interface PeerLink {
|
|
62
72
|
/** The validated identity of the remote peer (from the hello handshake). */
|
|
63
73
|
readonly hello: PeerHello;
|
|
74
|
+
/** Whether the link has been closed. */
|
|
75
|
+
readonly closed: boolean;
|
|
64
76
|
/** Send a line of text as a `msg` frame. */
|
|
65
77
|
send(text: string): void;
|
|
66
78
|
/** Read a file from disk and send it as a chunked media transfer. */
|
|
@@ -149,10 +161,10 @@ interface StoredPeer extends PeerHello {
|
|
|
149
161
|
readonly lastMessageAt?: string;
|
|
150
162
|
}
|
|
151
163
|
/** Load persisted live peers, or `[]` if none/corrupt. Local-only data. */
|
|
152
|
-
declare function loadPeers(dir?: string): StoredPeer[];
|
|
164
|
+
declare function loadPeers(dir?: string, maxAgeMs?: number): StoredPeer[];
|
|
153
165
|
/**
|
|
154
|
-
* Record a successfully handshaken peer, keyed by
|
|
155
|
-
*
|
|
166
|
+
* Record a successfully handshaken peer, keyed by pubkey (if verified) or handle (legacy).
|
|
167
|
+
* Returns whether this peer is NEW (first time seen).
|
|
156
168
|
*/
|
|
157
169
|
declare function recordPeer(hello: PeerHello, dir?: string, now?: Date): {
|
|
158
170
|
peer: StoredPeer;
|
|
@@ -163,7 +175,7 @@ declare function recordPeer(hello: PeerHello, dir?: string, now?: Date): {
|
|
|
163
175
|
* Local metadata only; never on the wire. Returns false when the handle isn't
|
|
164
176
|
* a known peer. Never throws — best-effort bookkeeping.
|
|
165
177
|
*/
|
|
166
|
-
declare function recordPeerMessage(
|
|
178
|
+
declare function recordPeerMessage(peer: PeerHello, dir?: string, now?: Date): boolean;
|
|
167
179
|
/** Injection point for the match notification (defaults to vibe-core notify). */
|
|
168
180
|
type NotifySink = (event: VibeEvent) => void;
|
|
169
181
|
interface DiscoveryOptions {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibedate",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.5",
|
|
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",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"build": "tsup src/index.ts src/cli.ts src/mcp.ts --format esm --dts --clean",
|
|
26
26
|
"typecheck": "tsc --noEmit",
|
|
27
27
|
"test": "vitest run",
|
|
28
|
-
"test:watch": "vitest"
|
|
28
|
+
"test:watch": "vitest",
|
|
29
|
+
"bench": "tsx bench/run.ts"
|
|
29
30
|
},
|
|
30
31
|
"dependencies": {
|
|
31
32
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
@@ -37,6 +38,7 @@
|
|
|
37
38
|
"@types/node": "^20.11.0",
|
|
38
39
|
"hyperdht": "^6.33.0",
|
|
39
40
|
"tsup": "^8.0.0",
|
|
41
|
+
"tsx": "^4.23.1",
|
|
40
42
|
"typescript": "^5.4.0",
|
|
41
43
|
"vitest": "^1.6.0",
|
|
42
44
|
"werift": "^0.24.1"
|