vibedate 0.2.1 → 0.3.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/dist/{chunk-6NWF2VD7.js → chunk-3VT4EOBX.js} +156 -11
- package/dist/{chunk-5ZJIPMU6.js → chunk-OVO2CQ7X.js} +1 -1
- package/dist/cli.d.ts +7 -1
- package/dist/cli.js +307 -26
- package/dist/index.d.ts +52 -5
- package/dist/index.js +5 -1
- package/dist/mcp.js +2 -2
- package/package.json +6 -3
|
@@ -432,6 +432,115 @@ function grantLiveConsent(dir = defaultStateDir()) {
|
|
|
432
432
|
function canShareLive(dir = defaultStateDir()) {
|
|
433
433
|
return createLedger(dir).allows(LIVE_CONSENT_SCOPE);
|
|
434
434
|
}
|
|
435
|
+
var HANDLE_FILE = "handle.json";
|
|
436
|
+
var DEFAULT_HANDLE = "@you";
|
|
437
|
+
var MAX_HANDLE_LEN = 32;
|
|
438
|
+
function handleFilePath(dir) {
|
|
439
|
+
return path2.join(dir, HANDLE_FILE);
|
|
440
|
+
}
|
|
441
|
+
function stripLeadingAt(handle) {
|
|
442
|
+
return handle.replace(/^@+/, "");
|
|
443
|
+
}
|
|
444
|
+
function sameHandle(a, b) {
|
|
445
|
+
return stripLeadingAt(a) === stripLeadingAt(b);
|
|
446
|
+
}
|
|
447
|
+
function normalizeHandle(input) {
|
|
448
|
+
if (typeof input !== "string") return null;
|
|
449
|
+
const trimmed = input.trim();
|
|
450
|
+
if (trimmed.length === 0) return null;
|
|
451
|
+
const body = stripLeadingAt(trimmed);
|
|
452
|
+
if (body.length === 0) return null;
|
|
453
|
+
if (/\s/.test(body) || /[\x00-\x1f\x7f]/.test(body)) return null;
|
|
454
|
+
const canonical = "@" + body;
|
|
455
|
+
if (canonical.length > MAX_HANDLE_LEN) return null;
|
|
456
|
+
return canonical;
|
|
457
|
+
}
|
|
458
|
+
function loadHandle(dir = defaultStateDir()) {
|
|
459
|
+
try {
|
|
460
|
+
const raw = readFileSync(handleFilePath(dir), "utf8");
|
|
461
|
+
const data = JSON.parse(raw);
|
|
462
|
+
if (typeof data["handle"] !== "string") return DEFAULT_HANDLE;
|
|
463
|
+
return normalizeHandle(data["handle"]) ?? DEFAULT_HANDLE;
|
|
464
|
+
} catch {
|
|
465
|
+
return DEFAULT_HANDLE;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
function saveHandle(handle, dir = defaultStateDir()) {
|
|
469
|
+
const canonical = normalizeHandle(handle);
|
|
470
|
+
if (canonical === null) {
|
|
471
|
+
throw new Error(
|
|
472
|
+
`invalid handle: use 1-${MAX_HANDLE_LEN} chars (optional leading '@'), no whitespace`
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
mkdirSync(dir, { recursive: true });
|
|
476
|
+
writeFileSync2(handleFilePath(dir), JSON.stringify({ handle: canonical }, null, 2) + "\n", "utf8");
|
|
477
|
+
const existing = loadProfile(dir);
|
|
478
|
+
if (existing !== null) {
|
|
479
|
+
writeFileSync2(
|
|
480
|
+
profilePath(dir),
|
|
481
|
+
JSON.stringify({ ...existing, handle: canonical }, null, 2) + "\n",
|
|
482
|
+
"utf8"
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
return canonical;
|
|
486
|
+
}
|
|
487
|
+
function resolveHandle(dir = defaultStateDir()) {
|
|
488
|
+
const env = process.env["VIBEDATING_HANDLE"];
|
|
489
|
+
if (env !== void 0 && env.trim() !== "") {
|
|
490
|
+
const canonical = normalizeHandle(env);
|
|
491
|
+
if (canonical !== null) return canonical;
|
|
492
|
+
}
|
|
493
|
+
return loadHandle(dir);
|
|
494
|
+
}
|
|
495
|
+
var BLOCKLIST_FILE = "blocklist.json";
|
|
496
|
+
function blocklistPath(dir) {
|
|
497
|
+
return path2.join(dir, BLOCKLIST_FILE);
|
|
498
|
+
}
|
|
499
|
+
function persistBlocklist(blocked, dir) {
|
|
500
|
+
mkdirSync(dir, { recursive: true });
|
|
501
|
+
writeFileSync2(blocklistPath(dir), JSON.stringify({ blocked }, null, 2) + "\n", "utf8");
|
|
502
|
+
}
|
|
503
|
+
function loadBlocklist(dir = defaultStateDir()) {
|
|
504
|
+
try {
|
|
505
|
+
const raw = readFileSync(blocklistPath(dir), "utf8");
|
|
506
|
+
const data = JSON.parse(raw);
|
|
507
|
+
if (!Array.isArray(data["blocked"])) return [];
|
|
508
|
+
return data["blocked"].filter((h) => typeof h === "string");
|
|
509
|
+
} catch {
|
|
510
|
+
return [];
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
function isBlocked(handle, dir = defaultStateDir()) {
|
|
514
|
+
const want = stripLeadingAt(handle);
|
|
515
|
+
if (want === "") return false;
|
|
516
|
+
return loadBlocklist(dir).some((entry) => stripLeadingAt(entry) === want);
|
|
517
|
+
}
|
|
518
|
+
function addBlock(handle, dir = defaultStateDir()) {
|
|
519
|
+
const canonical = normalizeHandle(handle);
|
|
520
|
+
if (canonical === null) {
|
|
521
|
+
throw new Error(
|
|
522
|
+
`invalid handle: use 1-${MAX_HANDLE_LEN} chars (optional leading '@'), no whitespace`
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
const list = loadBlocklist(dir);
|
|
526
|
+
if (list.some((e) => sameHandle(e, canonical))) return { blocked: list, changed: false };
|
|
527
|
+
const blocked = [...list, canonical];
|
|
528
|
+
persistBlocklist(blocked, dir);
|
|
529
|
+
return { blocked, changed: true };
|
|
530
|
+
}
|
|
531
|
+
function removeBlock(handle, dir = defaultStateDir()) {
|
|
532
|
+
const canonical = normalizeHandle(handle);
|
|
533
|
+
if (canonical === null) {
|
|
534
|
+
throw new Error(
|
|
535
|
+
`invalid handle: use 1-${MAX_HANDLE_LEN} chars (optional leading '@'), no whitespace`
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
const list = loadBlocklist(dir);
|
|
539
|
+
const blocked = list.filter((e) => !sameHandle(e, canonical));
|
|
540
|
+
if (blocked.length === list.length) return { blocked: list, changed: false };
|
|
541
|
+
persistBlocklist(blocked, dir);
|
|
542
|
+
return { blocked, changed: true };
|
|
543
|
+
}
|
|
435
544
|
|
|
436
545
|
// src/p2p.ts
|
|
437
546
|
var TOPIC_PREFIX = "vibedate:";
|
|
@@ -439,7 +548,7 @@ function leagueTopic(leagueName) {
|
|
|
439
548
|
return createHash("sha256").update(`${TOPIC_PREFIX}${leagueName}`, "utf8").digest();
|
|
440
549
|
}
|
|
441
550
|
var LIVE_NOTICE = "live discovery: sharing only your handle + league + harness (never raw usage) with same-league peers on the public DHT";
|
|
442
|
-
var
|
|
551
|
+
var MAX_HANDLE_LEN2 = 64;
|
|
443
552
|
var MAX_LEAGUE_LEN = 32;
|
|
444
553
|
var MAX_HARNESS_LEN = 64;
|
|
445
554
|
var MAX_HANDSHAKE_LEN = 4096;
|
|
@@ -464,7 +573,7 @@ function parseHandshake(raw) {
|
|
|
464
573
|
const rec = data;
|
|
465
574
|
const handle = rec["handle"];
|
|
466
575
|
const league2 = rec["league"];
|
|
467
|
-
if (typeof handle !== "string" || handle.length === 0 || handle.length >
|
|
576
|
+
if (typeof handle !== "string" || handle.length === 0 || handle.length > MAX_HANDLE_LEN2) {
|
|
468
577
|
return null;
|
|
469
578
|
}
|
|
470
579
|
if (typeof league2 !== "string" || league2.length === 0 || league2.length > MAX_LEAGUE_LEN) {
|
|
@@ -511,7 +620,9 @@ function recordPeer(hello, dir = defaultStateDir(), now = /* @__PURE__ */ new Da
|
|
|
511
620
|
}
|
|
512
621
|
async function startDiscovery(opts) {
|
|
513
622
|
const { hello, stateDir = defaultStateDir(), onPeer, onLink, notify = vibeCoreNotify } = opts;
|
|
514
|
-
const
|
|
623
|
+
const isBlocked2 = opts.isBlocked;
|
|
624
|
+
const topics = opts.topics ? [...opts.topics] : opts.topic !== void 0 ? [opts.topic] : [leagueTopic(hello.league)];
|
|
625
|
+
const acceptLeague = opts.acceptLeague ?? ((l) => l === hello.league);
|
|
515
626
|
const { default: Hyperswarm } = await import("hyperswarm");
|
|
516
627
|
const swarm = new Hyperswarm(opts.bootstrap === void 0 ? {} : { bootstrap: opts.bootstrap });
|
|
517
628
|
await swarm.dht.fullyBootstrapped();
|
|
@@ -539,7 +650,8 @@ async function startDiscovery(opts) {
|
|
|
539
650
|
league: frame.league,
|
|
540
651
|
harness: frame.harness
|
|
541
652
|
};
|
|
542
|
-
if (peer.league
|
|
653
|
+
if (!acceptLeague(peer.league)) continue;
|
|
654
|
+
if (isBlocked2 !== void 0 && isBlocked2(peer.handle)) continue;
|
|
543
655
|
peers.set(remoteKey, peer);
|
|
544
656
|
const { isNew } = recordPeer(peer, stateDir);
|
|
545
657
|
if (isNew) {
|
|
@@ -570,17 +682,22 @@ async function startDiscovery(opts) {
|
|
|
570
682
|
socket.on("error", () => {
|
|
571
683
|
});
|
|
572
684
|
});
|
|
573
|
-
const
|
|
574
|
-
const ready =
|
|
685
|
+
const discoveries = topics.map((t) => swarm.join(t, { server: true, client: true }));
|
|
686
|
+
const ready = Promise.all(
|
|
687
|
+
discoveries.map((d) => d.flushed().catch(() => void 0))
|
|
688
|
+
);
|
|
575
689
|
await ready;
|
|
576
690
|
const refresher = setInterval(() => {
|
|
577
|
-
void
|
|
691
|
+
for (const d of discoveries) void d.refresh({ server: true, client: true }).catch(() => {
|
|
578
692
|
});
|
|
579
693
|
}, REFRESH_INTERVAL_MS);
|
|
580
694
|
refresher.unref();
|
|
581
695
|
let closed = false;
|
|
582
696
|
return {
|
|
583
|
-
topic,
|
|
697
|
+
topic: topics[0],
|
|
698
|
+
// primary (first) — kept for back-compat / display
|
|
699
|
+
topics,
|
|
700
|
+
// every joined topic (primary first)
|
|
584
701
|
hello,
|
|
585
702
|
peers,
|
|
586
703
|
ready,
|
|
@@ -588,9 +705,11 @@ async function startDiscovery(opts) {
|
|
|
588
705
|
if (closed) return;
|
|
589
706
|
closed = true;
|
|
590
707
|
clearInterval(refresher);
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
708
|
+
for (const t of topics) {
|
|
709
|
+
try {
|
|
710
|
+
await swarm.leave(t);
|
|
711
|
+
} catch {
|
|
712
|
+
}
|
|
594
713
|
}
|
|
595
714
|
await swarm.destroy();
|
|
596
715
|
}
|
|
@@ -619,6 +738,22 @@ function leagueIndex(name) {
|
|
|
619
738
|
if (name === BELOW_LEAGUE) return -1;
|
|
620
739
|
return LEAGUES.findIndex((l) => l.name === name);
|
|
621
740
|
}
|
|
741
|
+
function allLeagueNames() {
|
|
742
|
+
return [BELOW_LEAGUE, ...LEAGUES.map((l) => l.name)];
|
|
743
|
+
}
|
|
744
|
+
function leaguesWithin(name, width) {
|
|
745
|
+
const w = Math.max(0, Math.trunc(width));
|
|
746
|
+
if (name === BELOW_LEAGUE) {
|
|
747
|
+
return [BELOW_LEAGUE, ...LEAGUES.slice(0, w).map((l) => l.name)];
|
|
748
|
+
}
|
|
749
|
+
const center = leagueIndex(name);
|
|
750
|
+
if (center < 0) return [];
|
|
751
|
+
const lo = Math.max(0, center - w);
|
|
752
|
+
const hi = Math.min(LEAGUES.length - 1, center + w);
|
|
753
|
+
const out = [];
|
|
754
|
+
for (let i = lo; i <= hi; i++) out.push(LEAGUES[i].name);
|
|
755
|
+
return out;
|
|
756
|
+
}
|
|
622
757
|
var TOKENS_ENV = "VIBEDATING_TOKENS";
|
|
623
758
|
var DEMO_TOTAL_TOKENS = 234e5;
|
|
624
759
|
var MS_PER_DAY = 864e5;
|
|
@@ -721,6 +856,14 @@ export {
|
|
|
721
856
|
loadProfile,
|
|
722
857
|
grantLiveConsent,
|
|
723
858
|
canShareLive,
|
|
859
|
+
sameHandle,
|
|
860
|
+
normalizeHandle,
|
|
861
|
+
saveHandle,
|
|
862
|
+
resolveHandle,
|
|
863
|
+
loadBlocklist,
|
|
864
|
+
isBlocked,
|
|
865
|
+
addBlock,
|
|
866
|
+
removeBlock,
|
|
724
867
|
TOPIC_PREFIX,
|
|
725
868
|
leagueTopic,
|
|
726
869
|
LIVE_NOTICE,
|
|
@@ -733,6 +876,8 @@ export {
|
|
|
733
876
|
BELOW_LEAGUE,
|
|
734
877
|
league,
|
|
735
878
|
leagueIndex,
|
|
879
|
+
allLeagueNames,
|
|
880
|
+
leaguesWithin,
|
|
736
881
|
TOKENS_ENV,
|
|
737
882
|
DEMO_TOTAL_TOKENS,
|
|
738
883
|
readUsage,
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/** Recognized top-level commands, plus the synthetic help/version. */
|
|
3
|
-
type Command = 'connect' | 'matches' | 'discover' | 'open' | 'live' | 'mcp' | 'help' | 'version' | null;
|
|
3
|
+
type Command = 'connect' | 'matches' | 'discover' | 'open' | 'live' | 'find' | 'handle' | 'block' | 'unblock' | 'blocklist' | 'mcp' | 'help' | 'version' | null;
|
|
4
4
|
interface ParsedArgs {
|
|
5
5
|
readonly command: Command;
|
|
6
6
|
/** Port for `open --port`; undefined means "let the OS pick". */
|
|
@@ -9,6 +9,12 @@ interface ParsedArgs {
|
|
|
9
9
|
readonly live: boolean;
|
|
10
10
|
/** `live --dating`: pick-a-handle mode vs omegle auto-pair. Default false. */
|
|
11
11
|
readonly dating: boolean;
|
|
12
|
+
/** `discover --any` / `live --any`: match every league (not just ±1). Default false. */
|
|
13
|
+
readonly any: boolean;
|
|
14
|
+
/** Positional argument for `handle`/`find` (e.g. `@name`). */
|
|
15
|
+
readonly arg: string | undefined;
|
|
16
|
+
/** `live --to <@handle>`: targeted match — auto-open that specific peer. */
|
|
17
|
+
readonly to: string | undefined;
|
|
12
18
|
}
|
|
13
19
|
/**
|
|
14
20
|
* Parse argv (the slice AFTER the program name) into a command + options.
|
package/dist/cli.js
CHANGED
|
@@ -1,23 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runMcp
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-OVO2CQ7X.js";
|
|
5
5
|
import {
|
|
6
6
|
CANDIDATES,
|
|
7
7
|
LIVE_NOTICE,
|
|
8
8
|
TOPIC_PREFIX,
|
|
9
|
+
addBlock,
|
|
10
|
+
allLeagueNames,
|
|
9
11
|
canShareLive,
|
|
10
12
|
connectProfile,
|
|
11
13
|
grantLiveConsent,
|
|
14
|
+
isBlocked,
|
|
12
15
|
league,
|
|
13
16
|
leagueIndex,
|
|
17
|
+
leagueTopic,
|
|
18
|
+
leaguesWithin,
|
|
19
|
+
loadBlocklist,
|
|
14
20
|
loadPeers,
|
|
15
21
|
loadProfile,
|
|
16
22
|
matches,
|
|
23
|
+
normalizeHandle,
|
|
17
24
|
parseFrame,
|
|
18
25
|
readUsage,
|
|
26
|
+
removeBlock,
|
|
27
|
+
resolveHandle,
|
|
28
|
+
sameHandle,
|
|
29
|
+
saveHandle,
|
|
19
30
|
startDiscovery
|
|
20
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-3VT4EOBX.js";
|
|
21
32
|
|
|
22
33
|
// src/cli.ts
|
|
23
34
|
import readline from "readline";
|
|
@@ -1387,26 +1398,54 @@ async function handle(req, res, opts) {
|
|
|
1387
1398
|
}
|
|
1388
1399
|
|
|
1389
1400
|
// src/cli.ts
|
|
1390
|
-
var VERSION = "0.
|
|
1401
|
+
var VERSION = "0.3.1";
|
|
1391
1402
|
function parsePort(raw) {
|
|
1392
1403
|
const n = Number(raw);
|
|
1393
1404
|
if (!Number.isInteger(n) || n < 1 || n > 65535) return void 0;
|
|
1394
1405
|
return n;
|
|
1395
1406
|
}
|
|
1396
1407
|
function parseArgs(argv) {
|
|
1397
|
-
let out = {
|
|
1408
|
+
let out = {
|
|
1409
|
+
command: null,
|
|
1410
|
+
port: void 0,
|
|
1411
|
+
live: false,
|
|
1412
|
+
dating: false,
|
|
1413
|
+
any: false,
|
|
1414
|
+
arg: void 0,
|
|
1415
|
+
to: void 0
|
|
1416
|
+
};
|
|
1398
1417
|
for (let i = 0; i < argv.length; i++) {
|
|
1399
1418
|
const a = argv[i];
|
|
1400
1419
|
if (a === "--version" || a === "-v") {
|
|
1401
|
-
return {
|
|
1420
|
+
return {
|
|
1421
|
+
command: "version",
|
|
1422
|
+
port: void 0,
|
|
1423
|
+
live: false,
|
|
1424
|
+
dating: false,
|
|
1425
|
+
any: false,
|
|
1426
|
+
arg: void 0,
|
|
1427
|
+
to: void 0
|
|
1428
|
+
};
|
|
1402
1429
|
}
|
|
1403
1430
|
if (a === "--help" || a === "-h") {
|
|
1404
|
-
return {
|
|
1431
|
+
return {
|
|
1432
|
+
command: "help",
|
|
1433
|
+
port: void 0,
|
|
1434
|
+
live: false,
|
|
1435
|
+
dating: false,
|
|
1436
|
+
any: false,
|
|
1437
|
+
arg: void 0,
|
|
1438
|
+
to: void 0
|
|
1439
|
+
};
|
|
1405
1440
|
}
|
|
1406
1441
|
if (a === "--live") {
|
|
1407
1442
|
out = { ...out, live: true };
|
|
1408
1443
|
continue;
|
|
1409
1444
|
}
|
|
1445
|
+
if (a === "--any") {
|
|
1446
|
+
out = { ...out, any: true };
|
|
1447
|
+
continue;
|
|
1448
|
+
}
|
|
1410
1449
|
if (a === "--dating") {
|
|
1411
1450
|
out = { ...out, dating: true };
|
|
1412
1451
|
continue;
|
|
@@ -1425,10 +1464,24 @@ function parseArgs(argv) {
|
|
|
1425
1464
|
if (p !== void 0) out = { ...out, port: p };
|
|
1426
1465
|
continue;
|
|
1427
1466
|
}
|
|
1467
|
+
if (a === "--to") {
|
|
1468
|
+
const next = argv[i + 1];
|
|
1469
|
+
if (next !== void 0) {
|
|
1470
|
+
out = { ...out, to: next };
|
|
1471
|
+
i++;
|
|
1472
|
+
}
|
|
1473
|
+
continue;
|
|
1474
|
+
}
|
|
1475
|
+
if (a.startsWith("--to=")) {
|
|
1476
|
+
out = { ...out, to: a.slice("--to=".length) };
|
|
1477
|
+
continue;
|
|
1478
|
+
}
|
|
1428
1479
|
if (a.startsWith("-")) continue;
|
|
1429
|
-
const known = a === "connect" || a === "matches" || a === "discover" || a === "open" || a === "live" || a === "mcp" || a === "help" ? a : null;
|
|
1480
|
+
const known = a === "connect" || a === "matches" || a === "discover" || a === "open" || a === "live" || a === "find" || a === "handle" || a === "block" || a === "unblock" || a === "blocklist" || a === "mcp" || a === "help" ? a : null;
|
|
1430
1481
|
if (known !== null && out.command === null) {
|
|
1431
1482
|
out = { ...out, command: known };
|
|
1483
|
+
} else if (out.arg === void 0) {
|
|
1484
|
+
out = { ...out, arg: a };
|
|
1432
1485
|
}
|
|
1433
1486
|
}
|
|
1434
1487
|
return out;
|
|
@@ -1436,9 +1489,30 @@ function parseArgs(argv) {
|
|
|
1436
1489
|
function leagueLabel(name) {
|
|
1437
1490
|
return name === "below-1M" ? "below 1M (not yet in a league)" : `${name} League`;
|
|
1438
1491
|
}
|
|
1492
|
+
function discoveryScope(myLeague, any) {
|
|
1493
|
+
const names = any ? allLeagueNames() : leaguesWithin(myLeague, 1);
|
|
1494
|
+
const ordered = [myLeague, ...names.filter((n) => n !== myLeague)];
|
|
1495
|
+
if (any) {
|
|
1496
|
+
return { topics: ordered.map(leagueTopic), acceptLeague: () => true };
|
|
1497
|
+
}
|
|
1498
|
+
const accepted = new Set(names);
|
|
1499
|
+
return {
|
|
1500
|
+
topics: ordered.map(leagueTopic),
|
|
1501
|
+
acceptLeague: (peerLeague) => accepted.has(peerLeague)
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
function blockedChecker() {
|
|
1505
|
+
return (handle2) => isBlocked(handle2);
|
|
1506
|
+
}
|
|
1507
|
+
function peerDirection(myLeague, peerLeague, sameBullet = "+") {
|
|
1508
|
+
const d = leagueIndex(peerLeague) - leagueIndex(myLeague);
|
|
1509
|
+
if (d > 0) return { bullet: "\u2191", qual: " \xB7 higher league" };
|
|
1510
|
+
if (d < 0) return { bullet: "\u2193", qual: " \xB7 lower league" };
|
|
1511
|
+
return { bullet: sameBullet, qual: "" };
|
|
1512
|
+
}
|
|
1439
1513
|
async function cmdConnect() {
|
|
1440
1514
|
const harness = process2.env["VIBEDATING_HARNESS"] ?? "claude-code";
|
|
1441
|
-
const handle2 =
|
|
1515
|
+
const handle2 = resolveHandle();
|
|
1442
1516
|
const snapshot = await readUsage(harness);
|
|
1443
1517
|
const profile = connectProfile(snapshot, handle2);
|
|
1444
1518
|
const lg = league(snapshot.totalTokens);
|
|
@@ -1455,6 +1529,28 @@ async function cmdConnect() {
|
|
|
1455
1529
|
process2.stdout.write(" \u2022 raw usage stays local \xB7 only league shared\n\n");
|
|
1456
1530
|
return 0;
|
|
1457
1531
|
}
|
|
1532
|
+
async function cmdHandle(arg) {
|
|
1533
|
+
if (arg === void 0 || arg.trim() === "") {
|
|
1534
|
+
const handle2 = resolveHandle();
|
|
1535
|
+
process2.stdout.write(`${handle2}
|
|
1536
|
+
`);
|
|
1537
|
+
const env = process2.env["VIBEDATING_HANDLE"];
|
|
1538
|
+
if (env !== void 0 && env.trim() !== "" && normalizeHandle(env) !== null) {
|
|
1539
|
+
process2.stdout.write(" (env VIBEDATING_HANDLE overrides the persisted handle for this run)\n");
|
|
1540
|
+
}
|
|
1541
|
+
return 0;
|
|
1542
|
+
}
|
|
1543
|
+
try {
|
|
1544
|
+
const canonical = saveHandle(arg);
|
|
1545
|
+
process2.stdout.write(` handle set \u2192 ${canonical} (saved to ~/.vibedating/handle.json)
|
|
1546
|
+
`);
|
|
1547
|
+
return 0;
|
|
1548
|
+
} catch (err) {
|
|
1549
|
+
process2.stderr.write(`${err instanceof Error ? err.message : String(err)}
|
|
1550
|
+
`);
|
|
1551
|
+
return 1;
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1458
1554
|
async function cmdMatches(live) {
|
|
1459
1555
|
const profile = loadProfile();
|
|
1460
1556
|
if (!profile) {
|
|
@@ -1508,7 +1604,7 @@ async function cmdMatches(live) {
|
|
|
1508
1604
|
process2.stdout.write("\n");
|
|
1509
1605
|
return 0;
|
|
1510
1606
|
}
|
|
1511
|
-
async function cmdDiscover(live) {
|
|
1607
|
+
async function cmdDiscover(live, any) {
|
|
1512
1608
|
const profile = loadProfile();
|
|
1513
1609
|
if (!profile) {
|
|
1514
1610
|
process2.stderr.write("Not connected yet. Run `vibedating connect` first.\n");
|
|
@@ -1522,27 +1618,39 @@ async function cmdDiscover(live) {
|
|
|
1522
1618
|
return 1;
|
|
1523
1619
|
}
|
|
1524
1620
|
const hello = {
|
|
1525
|
-
handle:
|
|
1621
|
+
handle: resolveHandle(),
|
|
1526
1622
|
league: profile.league,
|
|
1527
1623
|
harness: profile.harness
|
|
1528
1624
|
};
|
|
1529
1625
|
process2.stdout.write("\n");
|
|
1530
1626
|
process2.stdout.write(` ${LIVE_NOTICE}
|
|
1531
1627
|
`);
|
|
1628
|
+
const { topics, acceptLeague } = discoveryScope(profile.league, any);
|
|
1532
1629
|
const session = await startDiscovery({
|
|
1533
1630
|
hello,
|
|
1631
|
+
topics,
|
|
1632
|
+
acceptLeague,
|
|
1633
|
+
isBlocked: blockedChecker(),
|
|
1534
1634
|
onPeer: (peer, isNew) => {
|
|
1635
|
+
const { bullet, qual } = peerDirection(profile.league, peer.league);
|
|
1535
1636
|
process2.stdout.write(
|
|
1536
|
-
`
|
|
1637
|
+
` ${bullet} ${peer.handle} (${peer.league}${qual} \xB7 ${peer.harness})${isNew ? " \u2190 new match" : ""}
|
|
1537
1638
|
`
|
|
1538
1639
|
);
|
|
1539
1640
|
}
|
|
1540
1641
|
});
|
|
1541
1642
|
process2.stdout.write(
|
|
1542
|
-
` topic: ${TOPIC_PREFIX}${profile.league} \u2192 ${session.topic.toString("hex").slice(0, 12)}\u2026
|
|
1643
|
+
` topic: ${TOPIC_PREFIX}${profile.league} \u2192 ${session.topic.toString("hex").slice(0, 12)}\u2026${topics.length > 1 ? ` (+${topics.length - 1} more)` : ""}
|
|
1543
1644
|
`
|
|
1544
1645
|
);
|
|
1545
|
-
|
|
1646
|
+
if (!any && session.peers.size === 0) {
|
|
1647
|
+
process2.stdout.write(
|
|
1648
|
+
" no one in your league yet \u2014 also listening to adjacent leagues (use --any to match anyone)\n"
|
|
1649
|
+
);
|
|
1650
|
+
}
|
|
1651
|
+
process2.stdout.write(
|
|
1652
|
+
any ? " listening for ANY league peers\u2026 (Ctrl+C to stop)\n\n" : " listening for same + adjacent-league peers\u2026 (Ctrl+C to stop)\n\n"
|
|
1653
|
+
);
|
|
1546
1654
|
await new Promise((resolve) => {
|
|
1547
1655
|
process2.once("SIGINT", () => resolve());
|
|
1548
1656
|
process2.once("SIGTERM", () => resolve());
|
|
@@ -1570,11 +1678,11 @@ async function cmdOpen(port) {
|
|
|
1570
1678
|
${LIVE_NOTICE}
|
|
1571
1679
|
`);
|
|
1572
1680
|
const hello = {
|
|
1573
|
-
handle:
|
|
1681
|
+
handle: resolveHandle(),
|
|
1574
1682
|
league: profile.league,
|
|
1575
1683
|
harness: profile.harness
|
|
1576
1684
|
};
|
|
1577
|
-
void startDiscovery({ hello, onLink: (link) => live.addLink(link) }).then((s) => {
|
|
1685
|
+
void startDiscovery({ hello, isBlocked: blockedChecker(), onLink: (link) => live.addLink(link) }).then((s) => {
|
|
1578
1686
|
session = s;
|
|
1579
1687
|
}).catch(() => {
|
|
1580
1688
|
});
|
|
@@ -1598,19 +1706,25 @@ async function cmdOpen(port) {
|
|
|
1598
1706
|
await new Promise((resolve) => started.server.close(() => resolve()));
|
|
1599
1707
|
return 0;
|
|
1600
1708
|
}
|
|
1601
|
-
async function cmdLive(dating) {
|
|
1709
|
+
async function cmdLive(dating, any, to) {
|
|
1602
1710
|
const profile = loadProfile();
|
|
1603
1711
|
if (!profile) {
|
|
1604
1712
|
process2.stderr.write("Not connected yet. Run `vibedating connect` first.\n");
|
|
1605
1713
|
return 1;
|
|
1606
1714
|
}
|
|
1715
|
+
const target = to !== void 0 ? normalizeHandle(to) : null;
|
|
1716
|
+
if (to !== void 0 && target === null) {
|
|
1717
|
+
process2.stderr.write(`invalid target handle: ${to}
|
|
1718
|
+
`);
|
|
1719
|
+
return 1;
|
|
1720
|
+
}
|
|
1607
1721
|
if (!canShareLive()) grantLiveConsent();
|
|
1608
1722
|
if (!canShareLive()) {
|
|
1609
1723
|
process2.stderr.write("Could not enable live discovery. Try `vibedating discover --live`.\n");
|
|
1610
1724
|
return 1;
|
|
1611
1725
|
}
|
|
1612
1726
|
const hello = {
|
|
1613
|
-
handle:
|
|
1727
|
+
handle: resolveHandle(),
|
|
1614
1728
|
league: profile.league,
|
|
1615
1729
|
harness: profile.harness
|
|
1616
1730
|
};
|
|
@@ -1618,7 +1732,8 @@ async function cmdLive(dating) {
|
|
|
1618
1732
|
process2.stdout.write(` ${LIVE_NOTICE}
|
|
1619
1733
|
`);
|
|
1620
1734
|
process2.stdout.write(
|
|
1621
|
-
|
|
1735
|
+
target !== null ? ` targeted \u2014 auto-opening ${target} when they connect (/quit to stop)
|
|
1736
|
+
` : dating ? " dating mode \u2014 /open <handle> to pick a peer\n" : " omegle mode \u2014 /next to roll a new peer\n"
|
|
1622
1737
|
);
|
|
1623
1738
|
const pairing = createPairing();
|
|
1624
1739
|
pairing.onMatch((link) => {
|
|
@@ -1626,8 +1741,9 @@ async function cmdLive(dating) {
|
|
|
1626
1741
|
process2.stdout.write(" \xB7 idle \u2014 no peer right now\n");
|
|
1627
1742
|
return;
|
|
1628
1743
|
}
|
|
1744
|
+
const { qual } = peerDirection(profile.league, link.hello.league);
|
|
1629
1745
|
process2.stdout.write(
|
|
1630
|
-
` \xB7 matched ${link.hello.handle} (${link.hello.league} \xB7 ${link.hello.harness})
|
|
1746
|
+
` \xB7 matched ${link.hello.handle} (${link.hello.league}${qual} \xB7 ${link.hello.harness})
|
|
1631
1747
|
`
|
|
1632
1748
|
);
|
|
1633
1749
|
link.onMessage((m) => {
|
|
@@ -1635,12 +1751,31 @@ async function cmdLive(dating) {
|
|
|
1635
1751
|
`);
|
|
1636
1752
|
});
|
|
1637
1753
|
});
|
|
1754
|
+
const { topics, acceptLeague } = discoveryScope(profile.league, any);
|
|
1638
1755
|
const session = await startDiscovery({
|
|
1639
1756
|
hello,
|
|
1640
|
-
|
|
1757
|
+
topics,
|
|
1758
|
+
acceptLeague,
|
|
1759
|
+
isBlocked: blockedChecker(),
|
|
1760
|
+
onLink: (link) => {
|
|
1761
|
+
if (target !== null && !sameHandle(link.hello.handle, target)) {
|
|
1762
|
+
const { qual } = peerDirection(profile.league, link.hello.league);
|
|
1763
|
+
process2.stdout.write(
|
|
1764
|
+
` + ${link.hello.handle} (${link.hello.league}${qual}) \u2014 not your target
|
|
1765
|
+
`
|
|
1766
|
+
);
|
|
1767
|
+
link.close();
|
|
1768
|
+
return;
|
|
1769
|
+
}
|
|
1770
|
+
if (target !== null) {
|
|
1771
|
+
process2.stdout.write(` \u2605 found ${link.hello.handle} \u2014 auto-opening
|
|
1772
|
+
`);
|
|
1773
|
+
}
|
|
1774
|
+
pairing.add(link);
|
|
1775
|
+
}
|
|
1641
1776
|
});
|
|
1642
1777
|
process2.stdout.write(
|
|
1643
|
-
` topic: ${TOPIC_PREFIX}${profile.league} \u2192 ${session.topic.toString("hex").slice(0, 12)}\u2026
|
|
1778
|
+
` topic: ${TOPIC_PREFIX}${profile.league} \u2192 ${session.topic.toString("hex").slice(0, 12)}\u2026${topics.length > 1 ? ` (+${topics.length - 1} more)` : ""}
|
|
1644
1779
|
`
|
|
1645
1780
|
);
|
|
1646
1781
|
process2.stdout.write(" type to chat \xB7 /next \xB7 /open <handle> \xB7 /quit\n");
|
|
@@ -1683,13 +1818,144 @@ async function cmdLive(dating) {
|
|
|
1683
1818
|
process2.stdout.write("\n");
|
|
1684
1819
|
return 0;
|
|
1685
1820
|
}
|
|
1821
|
+
async function cmdFind(targetArg, any) {
|
|
1822
|
+
if (targetArg === void 0 || targetArg.trim() === "") {
|
|
1823
|
+
process2.stderr.write("usage: vibedating find <@handle> [--any]\n");
|
|
1824
|
+
return 1;
|
|
1825
|
+
}
|
|
1826
|
+
const target = normalizeHandle(targetArg);
|
|
1827
|
+
if (target === null) {
|
|
1828
|
+
process2.stderr.write(`invalid handle: ${targetArg}
|
|
1829
|
+
`);
|
|
1830
|
+
return 1;
|
|
1831
|
+
}
|
|
1832
|
+
const profile = loadProfile();
|
|
1833
|
+
if (!profile) {
|
|
1834
|
+
process2.stderr.write("Not connected yet. Run `vibedating connect` first.\n");
|
|
1835
|
+
return 1;
|
|
1836
|
+
}
|
|
1837
|
+
if (!canShareLive()) grantLiveConsent();
|
|
1838
|
+
if (!canShareLive()) {
|
|
1839
|
+
process2.stderr.write("Could not enable live discovery. Try `vibedating discover --live`.\n");
|
|
1840
|
+
return 1;
|
|
1841
|
+
}
|
|
1842
|
+
const hello = {
|
|
1843
|
+
handle: resolveHandle(),
|
|
1844
|
+
league: profile.league,
|
|
1845
|
+
harness: profile.harness
|
|
1846
|
+
};
|
|
1847
|
+
process2.stdout.write("\n");
|
|
1848
|
+
process2.stdout.write(` ${LIVE_NOTICE}
|
|
1849
|
+
`);
|
|
1850
|
+
process2.stdout.write(
|
|
1851
|
+
` looking for ${target} in your league${any ? " (+ every league)" : " + adjacent"}\u2026
|
|
1852
|
+
`
|
|
1853
|
+
);
|
|
1854
|
+
let found = false;
|
|
1855
|
+
const { topics, acceptLeague } = discoveryScope(profile.league, any);
|
|
1856
|
+
const session = await startDiscovery({
|
|
1857
|
+
hello,
|
|
1858
|
+
topics,
|
|
1859
|
+
acceptLeague,
|
|
1860
|
+
isBlocked: blockedChecker(),
|
|
1861
|
+
onPeer: (peer) => {
|
|
1862
|
+
const { qual } = peerDirection(profile.league, peer.league);
|
|
1863
|
+
if (sameHandle(peer.handle, target)) {
|
|
1864
|
+
found = true;
|
|
1865
|
+
process2.stdout.write(` \u2605 found ${peer.handle} (${peer.league}${qual} \xB7 ${peer.harness})
|
|
1866
|
+
`);
|
|
1867
|
+
} else {
|
|
1868
|
+
process2.stdout.write(` + ${peer.handle} (${peer.league}${qual}) \u2014 not your target
|
|
1869
|
+
`);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
});
|
|
1873
|
+
process2.stdout.write(
|
|
1874
|
+
` topic: ${TOPIC_PREFIX}${profile.league} \u2192 ${session.topic.toString("hex").slice(0, 12)}\u2026${topics.length > 1 ? ` (+${topics.length - 1} more)` : ""}
|
|
1875
|
+
`
|
|
1876
|
+
);
|
|
1877
|
+
process2.stdout.write(" (Ctrl+C to stop)\n\n");
|
|
1878
|
+
await new Promise((resolve) => {
|
|
1879
|
+
process2.once("SIGINT", () => resolve());
|
|
1880
|
+
process2.once("SIGTERM", () => resolve());
|
|
1881
|
+
});
|
|
1882
|
+
process2.stdout.write("\n leaving the swarm\u2026\n");
|
|
1883
|
+
await session.close();
|
|
1884
|
+
if (!found) {
|
|
1885
|
+
process2.stdout.write(` \u2717 ${target} was not found this session
|
|
1886
|
+
|
|
1887
|
+
`);
|
|
1888
|
+
} else {
|
|
1889
|
+
process2.stdout.write("\n");
|
|
1890
|
+
}
|
|
1891
|
+
return 0;
|
|
1892
|
+
}
|
|
1893
|
+
async function cmdBlock(arg) {
|
|
1894
|
+
if (arg === void 0 || arg.trim() === "") {
|
|
1895
|
+
process2.stderr.write("usage: vibedating block <@handle>\n");
|
|
1896
|
+
return 1;
|
|
1897
|
+
}
|
|
1898
|
+
const canonical = normalizeHandle(arg);
|
|
1899
|
+
if (canonical === null) {
|
|
1900
|
+
process2.stderr.write(`invalid handle: ${arg}
|
|
1901
|
+
`);
|
|
1902
|
+
return 1;
|
|
1903
|
+
}
|
|
1904
|
+
const { blocked, changed } = addBlock(canonical);
|
|
1905
|
+
process2.stdout.write(
|
|
1906
|
+
changed ? ` blocked ${canonical} (saved to ~/.vibedating/blocklist.json)
|
|
1907
|
+
` : ` ${canonical} is already blocked
|
|
1908
|
+
`
|
|
1909
|
+
);
|
|
1910
|
+
process2.stdout.write(` ${blocked.length} handle${blocked.length === 1 ? "" : "s"} blocked
|
|
1911
|
+
`);
|
|
1912
|
+
return 0;
|
|
1913
|
+
}
|
|
1914
|
+
async function cmdUnblock(arg) {
|
|
1915
|
+
if (arg === void 0 || arg.trim() === "") {
|
|
1916
|
+
process2.stderr.write("usage: vibedating unblock <@handle>\n");
|
|
1917
|
+
return 1;
|
|
1918
|
+
}
|
|
1919
|
+
const canonical = normalizeHandle(arg);
|
|
1920
|
+
if (canonical === null) {
|
|
1921
|
+
process2.stderr.write(`invalid handle: ${arg}
|
|
1922
|
+
`);
|
|
1923
|
+
return 1;
|
|
1924
|
+
}
|
|
1925
|
+
const { blocked, changed } = removeBlock(canonical);
|
|
1926
|
+
process2.stdout.write(
|
|
1927
|
+
changed ? ` unblocked ${canonical}
|
|
1928
|
+
` : ` ${canonical} was not blocked
|
|
1929
|
+
`
|
|
1930
|
+
);
|
|
1931
|
+
process2.stdout.write(` ${blocked.length} handle${blocked.length === 1 ? "" : "s"} blocked
|
|
1932
|
+
`);
|
|
1933
|
+
return 0;
|
|
1934
|
+
}
|
|
1935
|
+
async function cmdBlocklist() {
|
|
1936
|
+
const blocked = loadBlocklist();
|
|
1937
|
+
if (blocked.length === 0) {
|
|
1938
|
+
process2.stdout.write(" (blocklist is empty)\n");
|
|
1939
|
+
return 0;
|
|
1940
|
+
}
|
|
1941
|
+
process2.stdout.write(` ${blocked.length} blocked handle${blocked.length === 1 ? "" : "s"}:
|
|
1942
|
+
`);
|
|
1943
|
+
for (const h of blocked) process2.stdout.write(` ${h}
|
|
1944
|
+
`);
|
|
1945
|
+
return 0;
|
|
1946
|
+
}
|
|
1686
1947
|
var HELP = `vibedating ${VERSION} \u2014 dating by tokens (local-first)
|
|
1687
1948
|
|
|
1688
1949
|
Usage:
|
|
1689
1950
|
vibedating connect Read your usage, compute + print your league
|
|
1690
1951
|
vibedating matches [--live] List candidates in your league (live peers if any)
|
|
1691
|
-
vibedating discover [--live] Find live
|
|
1692
|
-
vibedating live [--dating]
|
|
1952
|
+
vibedating discover [--live] [--any] Find live peers over the DHT (your league + adjacent; --any = everyone)
|
|
1953
|
+
vibedating live [--dating] [--any] [--to @handle] Live chat (your league + adjacent; --any = everyone; /next or --dating pick; --to targets one peer)
|
|
1954
|
+
vibedating find <@handle> [--any] Search the DHT for one specific handle (\u2605 highlights a match)
|
|
1955
|
+
vibedating handle [@name] Print or set your handle (persisted; a leading '@' is optional)
|
|
1956
|
+
vibedating block <@handle> Block a handle \u2014 their hello is dropped (never recorded/paired)
|
|
1957
|
+
vibedating unblock <@handle> Remove a handle from the blocklist
|
|
1958
|
+
vibedating blocklist List blocked handles
|
|
1693
1959
|
vibedating open [--port N] Serve the local web app (default: random port)
|
|
1694
1960
|
+ live A/V video with connected same-league peers
|
|
1695
1961
|
vibedating mcp Run the stdio MCP server (profile, matches)
|
|
@@ -1701,10 +1967,15 @@ Privacy:
|
|
|
1701
1967
|
bucket is ever shared. Live discovery (off by default) shares ONLY your
|
|
1702
1968
|
handle + league + harness with same-league peers \u2014 opt in with --live.
|
|
1703
1969
|
|
|
1970
|
+
Matching:
|
|
1971
|
+
discover/live match your league + adjacent (\xB11) tiers by default, so thin
|
|
1972
|
+
leagues and cross-league friends still connect. --any matches every league.
|
|
1973
|
+
find / live --to look for one specific handle instead of the first random peer.
|
|
1974
|
+
|
|
1704
1975
|
Env:
|
|
1705
1976
|
VIBEDATING_TOKENS=<n> Self-report a token count (e.g. 23400000 or 12M)
|
|
1706
1977
|
VIBEDATING_HARNESS=<h> Harness id (claude-code, codex, \u2026)
|
|
1707
|
-
VIBEDATING_HANDLE=<@id> Display handle
|
|
1978
|
+
VIBEDATING_HANDLE=<@id> Display handle (one-off override; not persisted)
|
|
1708
1979
|
`;
|
|
1709
1980
|
async function main(argv) {
|
|
1710
1981
|
const parsed = parseArgs(argv);
|
|
@@ -1719,14 +1990,24 @@ async function main(argv) {
|
|
|
1719
1990
|
return 0;
|
|
1720
1991
|
case "connect":
|
|
1721
1992
|
return cmdConnect();
|
|
1993
|
+
case "handle":
|
|
1994
|
+
return cmdHandle(parsed.arg);
|
|
1995
|
+
case "block":
|
|
1996
|
+
return cmdBlock(parsed.arg);
|
|
1997
|
+
case "unblock":
|
|
1998
|
+
return cmdUnblock(parsed.arg);
|
|
1999
|
+
case "blocklist":
|
|
2000
|
+
return cmdBlocklist();
|
|
1722
2001
|
case "matches":
|
|
1723
2002
|
return cmdMatches(parsed.live);
|
|
1724
2003
|
case "discover":
|
|
1725
|
-
return cmdDiscover(parsed.live);
|
|
2004
|
+
return cmdDiscover(parsed.live, parsed.any);
|
|
1726
2005
|
case "open":
|
|
1727
2006
|
return cmdOpen(parsed.port);
|
|
1728
2007
|
case "live":
|
|
1729
|
-
return cmdLive(parsed.dating);
|
|
2008
|
+
return cmdLive(parsed.dating, parsed.any, parsed.to);
|
|
2009
|
+
case "find":
|
|
2010
|
+
return cmdFind(parsed.arg, parsed.any);
|
|
1730
2011
|
case "mcp":
|
|
1731
2012
|
await runMcp();
|
|
1732
2013
|
return 0;
|
package/dist/index.d.ts
CHANGED
|
@@ -142,9 +142,31 @@ interface DiscoveryOptions {
|
|
|
142
142
|
readonly hello: PeerHello;
|
|
143
143
|
/**
|
|
144
144
|
* Override the joined topic (tests pass a random one on an isolated DHT).
|
|
145
|
-
* Defaults to {@link leagueTopic}`(hello.league)`.
|
|
145
|
+
* Defaults to {@link leagueTopic}`(hello.league)`. Ignored when {@link topics}
|
|
146
|
+
* is set.
|
|
146
147
|
*/
|
|
147
148
|
readonly topic?: Buffer;
|
|
149
|
+
/**
|
|
150
|
+
* ALL topics to join on the one swarm (e.g. your league + adjacent leagues),
|
|
151
|
+
* so thin pools and cross-league friends still connect. Every topic is
|
|
152
|
+
* joined, refreshed, and left on close. Defaults to `[topic]` — i.e. a single
|
|
153
|
+
* own-league topic (the legacy behavior).
|
|
154
|
+
*/
|
|
155
|
+
readonly topics?: readonly Buffer[];
|
|
156
|
+
/**
|
|
157
|
+
* Predicate over an incoming peer's advertised league. Defaults to EXACT
|
|
158
|
+
* match against `hello.league` — the same privacy invariant as before. Widen
|
|
159
|
+
* it (e.g. ±1 adjacency via {@link leaguesWithin}) to accept cross-league
|
|
160
|
+
* peers that arrive on a shared topic.
|
|
161
|
+
*/
|
|
162
|
+
readonly acceptLeague?: (peerLeague: string) => boolean;
|
|
163
|
+
/**
|
|
164
|
+
* Predicate over an incoming peer's advertised handle. A blocked peer's hello
|
|
165
|
+
* is DROPPED exactly like a wrong-league one — never recorded to peers.json,
|
|
166
|
+
* never notified, never handed to `onLink`/pairing. Default: nothing blocked.
|
|
167
|
+
* The CLI passes one backed by the persisted blocklist (~/.vibedating).
|
|
168
|
+
*/
|
|
169
|
+
readonly isBlocked?: (handle: string) => boolean;
|
|
148
170
|
/** DHT bootstrap nodes; omit for the public DHT. Tests pass a local testnet. */
|
|
149
171
|
readonly bootstrap?: ReadonlyArray<{
|
|
150
172
|
readonly host: string;
|
|
@@ -165,15 +187,17 @@ interface DiscoveryOptions {
|
|
|
165
187
|
readonly notify?: NotifySink;
|
|
166
188
|
}
|
|
167
189
|
interface DiscoverySession {
|
|
168
|
-
/** The
|
|
190
|
+
/** The primary (first) joined topic. See {@link topics} for the full set. */
|
|
169
191
|
readonly topic: Buffer;
|
|
192
|
+
/** Every topic this session joined (primary first). */
|
|
193
|
+
readonly topics: readonly Buffer[];
|
|
170
194
|
/** What we broadcast on every connection. */
|
|
171
195
|
readonly hello: PeerHello;
|
|
172
196
|
/** Live peer set, keyed by the remote's public key (hex). */
|
|
173
197
|
readonly peers: ReadonlyMap<string, PeerHello>;
|
|
174
|
-
/** Resolves when the first DHT announce/lookup round for
|
|
198
|
+
/** Resolves when the first DHT announce/lookup round for every topic completes. */
|
|
175
199
|
readonly ready: Promise<unknown>;
|
|
176
|
-
/** Leave
|
|
200
|
+
/** Leave every topic and destroy the node. Idempotent. */
|
|
177
201
|
close(): Promise<void>;
|
|
178
202
|
}
|
|
179
203
|
/**
|
|
@@ -222,6 +246,29 @@ declare function league(totalTokens: number): {
|
|
|
222
246
|
};
|
|
223
247
|
/** Index of a league name in {@link LEAGUES}; {@link BELOW_LEAGUE} → -1. */
|
|
224
248
|
declare function leagueIndex(name: string): number;
|
|
249
|
+
/**
|
|
250
|
+
* Every league name on the ladder: {@link BELOW_LEAGUE} first (it sits just
|
|
251
|
+
* below the 1M tier), then each {@link LEAGUES} name in ascending order.
|
|
252
|
+
* Pure; returns a fresh array each call.
|
|
253
|
+
*/
|
|
254
|
+
declare function allLeagueNames(): string[];
|
|
255
|
+
/**
|
|
256
|
+
* League names whose index is within ±`width` of `name`'s index on the ladder,
|
|
257
|
+
* clamped to the array bounds. Ascending (index order), de-duplicated.
|
|
258
|
+
*
|
|
259
|
+
* Edge-case decisions:
|
|
260
|
+
* - **{@link BELOW_LEAGUE}** is treated as "index -1" — it sits just below the
|
|
261
|
+
* 1M tier, so `width` extends ONLY upward into the ladder
|
|
262
|
+
* (`leaguesWithin('below-1M', 1) → ['below-1M', '1M']`). This mirrors the
|
|
263
|
+
* existing `matches()` rule that below-1M is adjacent only to 1M.
|
|
264
|
+
* - **Unknown league names** (not in {@link LEAGUES}, not {@link BELOW_LEAGUE})
|
|
265
|
+
* return `[]` — we can't place them on the ladder, so they have no
|
|
266
|
+
* neighborhood.
|
|
267
|
+
* - **Negative width** is clamped to 0 (self only); non-integer width is
|
|
268
|
+
* truncated.
|
|
269
|
+
* - **Width larger than the ladder** is clamped to the bounds (whole ladder).
|
|
270
|
+
*/
|
|
271
|
+
declare function leaguesWithin(name: string, width: number): string[];
|
|
225
272
|
/** Env var that holds a self-reported total token count (e.g. `23400000`). */
|
|
226
273
|
declare const TOKENS_ENV = "VIBEDATING_TOKENS";
|
|
227
274
|
/**
|
|
@@ -277,4 +324,4 @@ declare const CANDIDATES: readonly Candidate[];
|
|
|
277
324
|
*/
|
|
278
325
|
declare function matches(myLeague: string, candidates?: readonly Candidate[]): Candidate[];
|
|
279
326
|
|
|
280
|
-
export { BELOW_LEAGUE, CANDIDATES, type Candidate, DEMO_TOTAL_TOKENS, type DiscoveryOptions, type DiscoverySession, LEAGUES, LIVE_NOTICE, type League, type PeerHello, type StoredPeer, TOKENS_ENV, TOPIC_PREFIX, league, leagueIndex, leagueTopic, loadPeers, matches, parseHandshake, parseTokensEnv, readUsage, recordPeer, serializeHandshake, startDiscovery, tryReadVerifiedUsage };
|
|
327
|
+
export { BELOW_LEAGUE, CANDIDATES, type Candidate, DEMO_TOTAL_TOKENS, type DiscoveryOptions, type DiscoverySession, LEAGUES, LIVE_NOTICE, type League, type PeerHello, type StoredPeer, TOKENS_ENV, TOPIC_PREFIX, allLeagueNames, league, leagueIndex, leagueTopic, leaguesWithin, loadPeers, matches, parseHandshake, parseTokensEnv, readUsage, recordPeer, serializeHandshake, startDiscovery, tryReadVerifiedUsage };
|
package/dist/index.js
CHANGED
|
@@ -6,10 +6,12 @@ import {
|
|
|
6
6
|
LIVE_NOTICE,
|
|
7
7
|
TOKENS_ENV,
|
|
8
8
|
TOPIC_PREFIX,
|
|
9
|
+
allLeagueNames,
|
|
9
10
|
createConsentLedger,
|
|
10
11
|
league,
|
|
11
12
|
leagueIndex,
|
|
12
13
|
leagueTopic,
|
|
14
|
+
leaguesWithin,
|
|
13
15
|
loadPeers,
|
|
14
16
|
matches,
|
|
15
17
|
parseHandshake,
|
|
@@ -19,7 +21,7 @@ import {
|
|
|
19
21
|
serializeHandshake,
|
|
20
22
|
startDiscovery,
|
|
21
23
|
tryReadVerifiedUsage
|
|
22
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-3VT4EOBX.js";
|
|
23
25
|
export {
|
|
24
26
|
BELOW_LEAGUE,
|
|
25
27
|
CANDIDATES,
|
|
@@ -28,10 +30,12 @@ export {
|
|
|
28
30
|
LIVE_NOTICE,
|
|
29
31
|
TOKENS_ENV,
|
|
30
32
|
TOPIC_PREFIX,
|
|
33
|
+
allLeagueNames,
|
|
31
34
|
createConsentLedger,
|
|
32
35
|
league,
|
|
33
36
|
leagueIndex,
|
|
34
37
|
leagueTopic,
|
|
38
|
+
leaguesWithin,
|
|
35
39
|
loadPeers,
|
|
36
40
|
matches,
|
|
37
41
|
parseHandshake,
|
package/dist/mcp.js
CHANGED
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibedate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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",
|
|
7
7
|
"author": "Pooria Arab",
|
|
8
8
|
"bin": {
|
|
9
|
-
"
|
|
9
|
+
"vibedate": "./dist/cli.js",
|
|
10
|
+
"vibedating": "./dist/cli.js",
|
|
11
|
+
"vibedate-mcp": "./dist/mcp.js"
|
|
10
12
|
},
|
|
11
13
|
"main": "./dist/index.js",
|
|
12
14
|
"types": "./dist/index.d.ts",
|
|
@@ -35,7 +37,8 @@
|
|
|
35
37
|
"hyperdht": "^6.33.0",
|
|
36
38
|
"tsup": "^8.0.0",
|
|
37
39
|
"typescript": "^5.4.0",
|
|
38
|
-
"vitest": "^1.6.0"
|
|
40
|
+
"vitest": "^1.6.0",
|
|
41
|
+
"werift": "^0.24.1"
|
|
39
42
|
},
|
|
40
43
|
"homepage": "https://github.com/pooriaarab/vibedating#readme",
|
|
41
44
|
"repository": {
|