vibedate 0.3.0 → 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-VBLFAOV3.js → chunk-3VT4EOBX.js} +121 -2
- package/dist/{chunk-DMA7MSN7.js → chunk-OVO2CQ7X.js} +1 -1
- package/dist/cli.d.ts +5 -1
- package/dist/cli.js +255 -18
- package/dist/index.d.ts +7 -0
- package/dist/index.js +1 -1
- package/dist/mcp.js +2 -2
- package/package.json +3 -2
|
@@ -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,6 +620,7 @@ 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;
|
|
623
|
+
const isBlocked2 = opts.isBlocked;
|
|
514
624
|
const topics = opts.topics ? [...opts.topics] : opts.topic !== void 0 ? [opts.topic] : [leagueTopic(hello.league)];
|
|
515
625
|
const acceptLeague = opts.acceptLeague ?? ((l) => l === hello.league);
|
|
516
626
|
const { default: Hyperswarm } = await import("hyperswarm");
|
|
@@ -541,6 +651,7 @@ async function startDiscovery(opts) {
|
|
|
541
651
|
harness: frame.harness
|
|
542
652
|
};
|
|
543
653
|
if (!acceptLeague(peer.league)) continue;
|
|
654
|
+
if (isBlocked2 !== void 0 && isBlocked2(peer.handle)) continue;
|
|
544
655
|
peers.set(remoteKey, peer);
|
|
545
656
|
const { isNew } = recordPeer(peer, stateDir);
|
|
546
657
|
if (isNew) {
|
|
@@ -745,6 +856,14 @@ export {
|
|
|
745
856
|
loadProfile,
|
|
746
857
|
grantLiveConsent,
|
|
747
858
|
canShareLive,
|
|
859
|
+
sameHandle,
|
|
860
|
+
normalizeHandle,
|
|
861
|
+
saveHandle,
|
|
862
|
+
resolveHandle,
|
|
863
|
+
loadBlocklist,
|
|
864
|
+
isBlocked,
|
|
865
|
+
addBlock,
|
|
866
|
+
removeBlock,
|
|
748
867
|
TOPIC_PREFIX,
|
|
749
868
|
leagueTopic,
|
|
750
869
|
LIVE_NOTICE,
|
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". */
|
|
@@ -11,6 +11,10 @@ interface ParsedArgs {
|
|
|
11
11
|
readonly dating: boolean;
|
|
12
12
|
/** `discover --any` / `live --any`: match every league (not just ±1). Default false. */
|
|
13
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;
|
|
14
18
|
}
|
|
15
19
|
/**
|
|
16
20
|
* Parse argv (the slice AFTER the program name) into a command + options.
|
package/dist/cli.js
CHANGED
|
@@ -1,26 +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,
|
|
9
10
|
allLeagueNames,
|
|
10
11
|
canShareLive,
|
|
11
12
|
connectProfile,
|
|
12
13
|
grantLiveConsent,
|
|
14
|
+
isBlocked,
|
|
13
15
|
league,
|
|
14
16
|
leagueIndex,
|
|
15
17
|
leagueTopic,
|
|
16
18
|
leaguesWithin,
|
|
19
|
+
loadBlocklist,
|
|
17
20
|
loadPeers,
|
|
18
21
|
loadProfile,
|
|
19
22
|
matches,
|
|
23
|
+
normalizeHandle,
|
|
20
24
|
parseFrame,
|
|
21
25
|
readUsage,
|
|
26
|
+
removeBlock,
|
|
27
|
+
resolveHandle,
|
|
28
|
+
sameHandle,
|
|
29
|
+
saveHandle,
|
|
22
30
|
startDiscovery
|
|
23
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-3VT4EOBX.js";
|
|
24
32
|
|
|
25
33
|
// src/cli.ts
|
|
26
34
|
import readline from "readline";
|
|
@@ -1390,21 +1398,45 @@ async function handle(req, res, opts) {
|
|
|
1390
1398
|
}
|
|
1391
1399
|
|
|
1392
1400
|
// src/cli.ts
|
|
1393
|
-
var VERSION = "0.3.
|
|
1401
|
+
var VERSION = "0.3.1";
|
|
1394
1402
|
function parsePort(raw) {
|
|
1395
1403
|
const n = Number(raw);
|
|
1396
1404
|
if (!Number.isInteger(n) || n < 1 || n > 65535) return void 0;
|
|
1397
1405
|
return n;
|
|
1398
1406
|
}
|
|
1399
1407
|
function parseArgs(argv) {
|
|
1400
|
-
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
|
+
};
|
|
1401
1417
|
for (let i = 0; i < argv.length; i++) {
|
|
1402
1418
|
const a = argv[i];
|
|
1403
1419
|
if (a === "--version" || a === "-v") {
|
|
1404
|
-
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
|
+
};
|
|
1405
1429
|
}
|
|
1406
1430
|
if (a === "--help" || a === "-h") {
|
|
1407
|
-
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
|
+
};
|
|
1408
1440
|
}
|
|
1409
1441
|
if (a === "--live") {
|
|
1410
1442
|
out = { ...out, live: true };
|
|
@@ -1432,10 +1464,24 @@ function parseArgs(argv) {
|
|
|
1432
1464
|
if (p !== void 0) out = { ...out, port: p };
|
|
1433
1465
|
continue;
|
|
1434
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
|
+
}
|
|
1435
1479
|
if (a.startsWith("-")) continue;
|
|
1436
|
-
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;
|
|
1437
1481
|
if (known !== null && out.command === null) {
|
|
1438
1482
|
out = { ...out, command: known };
|
|
1483
|
+
} else if (out.arg === void 0) {
|
|
1484
|
+
out = { ...out, arg: a };
|
|
1439
1485
|
}
|
|
1440
1486
|
}
|
|
1441
1487
|
return out;
|
|
@@ -1455,6 +1501,9 @@ function discoveryScope(myLeague, any) {
|
|
|
1455
1501
|
acceptLeague: (peerLeague) => accepted.has(peerLeague)
|
|
1456
1502
|
};
|
|
1457
1503
|
}
|
|
1504
|
+
function blockedChecker() {
|
|
1505
|
+
return (handle2) => isBlocked(handle2);
|
|
1506
|
+
}
|
|
1458
1507
|
function peerDirection(myLeague, peerLeague, sameBullet = "+") {
|
|
1459
1508
|
const d = leagueIndex(peerLeague) - leagueIndex(myLeague);
|
|
1460
1509
|
if (d > 0) return { bullet: "\u2191", qual: " \xB7 higher league" };
|
|
@@ -1463,7 +1512,7 @@ function peerDirection(myLeague, peerLeague, sameBullet = "+") {
|
|
|
1463
1512
|
}
|
|
1464
1513
|
async function cmdConnect() {
|
|
1465
1514
|
const harness = process2.env["VIBEDATING_HARNESS"] ?? "claude-code";
|
|
1466
|
-
const handle2 =
|
|
1515
|
+
const handle2 = resolveHandle();
|
|
1467
1516
|
const snapshot = await readUsage(harness);
|
|
1468
1517
|
const profile = connectProfile(snapshot, handle2);
|
|
1469
1518
|
const lg = league(snapshot.totalTokens);
|
|
@@ -1480,6 +1529,28 @@ async function cmdConnect() {
|
|
|
1480
1529
|
process2.stdout.write(" \u2022 raw usage stays local \xB7 only league shared\n\n");
|
|
1481
1530
|
return 0;
|
|
1482
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
|
+
}
|
|
1483
1554
|
async function cmdMatches(live) {
|
|
1484
1555
|
const profile = loadProfile();
|
|
1485
1556
|
if (!profile) {
|
|
@@ -1547,7 +1618,7 @@ async function cmdDiscover(live, any) {
|
|
|
1547
1618
|
return 1;
|
|
1548
1619
|
}
|
|
1549
1620
|
const hello = {
|
|
1550
|
-
handle:
|
|
1621
|
+
handle: resolveHandle(),
|
|
1551
1622
|
league: profile.league,
|
|
1552
1623
|
harness: profile.harness
|
|
1553
1624
|
};
|
|
@@ -1559,6 +1630,7 @@ async function cmdDiscover(live, any) {
|
|
|
1559
1630
|
hello,
|
|
1560
1631
|
topics,
|
|
1561
1632
|
acceptLeague,
|
|
1633
|
+
isBlocked: blockedChecker(),
|
|
1562
1634
|
onPeer: (peer, isNew) => {
|
|
1563
1635
|
const { bullet, qual } = peerDirection(profile.league, peer.league);
|
|
1564
1636
|
process2.stdout.write(
|
|
@@ -1606,11 +1678,11 @@ async function cmdOpen(port) {
|
|
|
1606
1678
|
${LIVE_NOTICE}
|
|
1607
1679
|
`);
|
|
1608
1680
|
const hello = {
|
|
1609
|
-
handle:
|
|
1681
|
+
handle: resolveHandle(),
|
|
1610
1682
|
league: profile.league,
|
|
1611
1683
|
harness: profile.harness
|
|
1612
1684
|
};
|
|
1613
|
-
void startDiscovery({ hello, onLink: (link) => live.addLink(link) }).then((s) => {
|
|
1685
|
+
void startDiscovery({ hello, isBlocked: blockedChecker(), onLink: (link) => live.addLink(link) }).then((s) => {
|
|
1614
1686
|
session = s;
|
|
1615
1687
|
}).catch(() => {
|
|
1616
1688
|
});
|
|
@@ -1634,19 +1706,25 @@ async function cmdOpen(port) {
|
|
|
1634
1706
|
await new Promise((resolve) => started.server.close(() => resolve()));
|
|
1635
1707
|
return 0;
|
|
1636
1708
|
}
|
|
1637
|
-
async function cmdLive(dating, any) {
|
|
1709
|
+
async function cmdLive(dating, any, to) {
|
|
1638
1710
|
const profile = loadProfile();
|
|
1639
1711
|
if (!profile) {
|
|
1640
1712
|
process2.stderr.write("Not connected yet. Run `vibedating connect` first.\n");
|
|
1641
1713
|
return 1;
|
|
1642
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
|
+
}
|
|
1643
1721
|
if (!canShareLive()) grantLiveConsent();
|
|
1644
1722
|
if (!canShareLive()) {
|
|
1645
1723
|
process2.stderr.write("Could not enable live discovery. Try `vibedating discover --live`.\n");
|
|
1646
1724
|
return 1;
|
|
1647
1725
|
}
|
|
1648
1726
|
const hello = {
|
|
1649
|
-
handle:
|
|
1727
|
+
handle: resolveHandle(),
|
|
1650
1728
|
league: profile.league,
|
|
1651
1729
|
harness: profile.harness
|
|
1652
1730
|
};
|
|
@@ -1654,7 +1732,8 @@ async function cmdLive(dating, any) {
|
|
|
1654
1732
|
process2.stdout.write(` ${LIVE_NOTICE}
|
|
1655
1733
|
`);
|
|
1656
1734
|
process2.stdout.write(
|
|
1657
|
-
|
|
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"
|
|
1658
1737
|
);
|
|
1659
1738
|
const pairing = createPairing();
|
|
1660
1739
|
pairing.onMatch((link) => {
|
|
@@ -1677,7 +1756,23 @@ async function cmdLive(dating, any) {
|
|
|
1677
1756
|
hello,
|
|
1678
1757
|
topics,
|
|
1679
1758
|
acceptLeague,
|
|
1680
|
-
|
|
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
|
+
}
|
|
1681
1776
|
});
|
|
1682
1777
|
process2.stdout.write(
|
|
1683
1778
|
` topic: ${TOPIC_PREFIX}${profile.league} \u2192 ${session.topic.toString("hex").slice(0, 12)}\u2026${topics.length > 1 ? ` (+${topics.length - 1} more)` : ""}
|
|
@@ -1723,13 +1818,144 @@ async function cmdLive(dating, any) {
|
|
|
1723
1818
|
process2.stdout.write("\n");
|
|
1724
1819
|
return 0;
|
|
1725
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
|
+
}
|
|
1726
1947
|
var HELP = `vibedating ${VERSION} \u2014 dating by tokens (local-first)
|
|
1727
1948
|
|
|
1728
1949
|
Usage:
|
|
1729
1950
|
vibedating connect Read your usage, compute + print your league
|
|
1730
1951
|
vibedating matches [--live] List candidates in your league (live peers if any)
|
|
1731
1952
|
vibedating discover [--live] [--any] Find live peers over the DHT (your league + adjacent; --any = everyone)
|
|
1732
|
-
vibedating live [--dating] [--any]
|
|
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
|
|
1733
1959
|
vibedating open [--port N] Serve the local web app (default: random port)
|
|
1734
1960
|
+ live A/V video with connected same-league peers
|
|
1735
1961
|
vibedating mcp Run the stdio MCP server (profile, matches)
|
|
@@ -1744,11 +1970,12 @@ Privacy:
|
|
|
1744
1970
|
Matching:
|
|
1745
1971
|
discover/live match your league + adjacent (\xB11) tiers by default, so thin
|
|
1746
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.
|
|
1747
1974
|
|
|
1748
1975
|
Env:
|
|
1749
1976
|
VIBEDATING_TOKENS=<n> Self-report a token count (e.g. 23400000 or 12M)
|
|
1750
1977
|
VIBEDATING_HARNESS=<h> Harness id (claude-code, codex, \u2026)
|
|
1751
|
-
VIBEDATING_HANDLE=<@id> Display handle
|
|
1978
|
+
VIBEDATING_HANDLE=<@id> Display handle (one-off override; not persisted)
|
|
1752
1979
|
`;
|
|
1753
1980
|
async function main(argv) {
|
|
1754
1981
|
const parsed = parseArgs(argv);
|
|
@@ -1763,6 +1990,14 @@ async function main(argv) {
|
|
|
1763
1990
|
return 0;
|
|
1764
1991
|
case "connect":
|
|
1765
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();
|
|
1766
2001
|
case "matches":
|
|
1767
2002
|
return cmdMatches(parsed.live);
|
|
1768
2003
|
case "discover":
|
|
@@ -1770,7 +2005,9 @@ async function main(argv) {
|
|
|
1770
2005
|
case "open":
|
|
1771
2006
|
return cmdOpen(parsed.port);
|
|
1772
2007
|
case "live":
|
|
1773
|
-
return cmdLive(parsed.dating, parsed.any);
|
|
2008
|
+
return cmdLive(parsed.dating, parsed.any, parsed.to);
|
|
2009
|
+
case "find":
|
|
2010
|
+
return cmdFind(parsed.arg, parsed.any);
|
|
1774
2011
|
case "mcp":
|
|
1775
2012
|
await runMcp();
|
|
1776
2013
|
return 0;
|
package/dist/index.d.ts
CHANGED
|
@@ -160,6 +160,13 @@ interface DiscoveryOptions {
|
|
|
160
160
|
* peers that arrive on a shared topic.
|
|
161
161
|
*/
|
|
162
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;
|
|
163
170
|
/** DHT bootstrap nodes; omit for the public DHT. Tests pass a local testnet. */
|
|
164
171
|
readonly bootstrap?: ReadonlyArray<{
|
|
165
172
|
readonly host: string;
|
package/dist/index.js
CHANGED
package/dist/mcp.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibedate",
|
|
3
|
-
"version": "0.3.
|
|
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",
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
"hyperdht": "^6.33.0",
|
|
38
38
|
"tsup": "^8.0.0",
|
|
39
39
|
"typescript": "^5.4.0",
|
|
40
|
-
"vitest": "^1.6.0"
|
|
40
|
+
"vitest": "^1.6.0",
|
|
41
|
+
"werift": "^0.24.1"
|
|
41
42
|
},
|
|
42
43
|
"homepage": "https://github.com/pooriaarab/vibedating#readme",
|
|
43
44
|
"repository": {
|