vibedate 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-KKWP4DLY.js → chunk-HDHJLZZG.js} +343 -0
- package/dist/{chunk-I5U3O4RH.js → chunk-ZD6JBWEW.js} +1 -1
- package/dist/cli.d.ts +9 -1
- package/dist/cli.js +484 -34
- package/dist/index.d.ts +217 -1
- package/dist/index.js +21 -1
- package/dist/mcp.js +2 -2
- package/package.json +3 -2
package/dist/index.d.ts
CHANGED
|
@@ -238,6 +238,200 @@ interface DiscoverySession {
|
|
|
238
238
|
*/
|
|
239
239
|
declare function startDiscovery(opts: DiscoveryOptions): Promise<DiscoverySession>;
|
|
240
240
|
|
|
241
|
+
/**
|
|
242
|
+
* A Nostr event, reduced to the fields this module touches. Structurally
|
|
243
|
+
* compatible with `nostr-tools`'s `Event`, so a finalized/verified event can be
|
|
244
|
+
* published and a relay-delivered event can be received without conversion.
|
|
245
|
+
*/
|
|
246
|
+
interface RelayEvent {
|
|
247
|
+
readonly kind: number;
|
|
248
|
+
/** Sender's secp256k1 (Nostr) pubkey, hex — public; the relay sees it. */
|
|
249
|
+
readonly pubkey: string;
|
|
250
|
+
/** Ciphertext for kind-4 DMs; a presence marker for the presence kind. */
|
|
251
|
+
readonly content: string;
|
|
252
|
+
readonly tags: readonly (readonly string[])[];
|
|
253
|
+
readonly created_at: number;
|
|
254
|
+
readonly id: string;
|
|
255
|
+
readonly sig: string;
|
|
256
|
+
}
|
|
257
|
+
/** A subscription filter: kinds + a `#t` conversation-tag match. */
|
|
258
|
+
interface RelayFilter {
|
|
259
|
+
readonly kinds?: readonly number[];
|
|
260
|
+
readonly '#t'?: readonly string[];
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* The injection point between the relay link and the wire. The production
|
|
264
|
+
* implementation is {@link createNostrPoolTransport} (real wss:// relays via
|
|
265
|
+
* `nostr-tools` `SimplePool`); tests pass an in-memory fan-out. A real relay
|
|
266
|
+
* STORES events and replays matching stored events to new subscribers (that
|
|
267
|
+
* store-and-replay semantics is what makes the presence handshake work across
|
|
268
|
+
* arrival order) — a faithful mock must do the same.
|
|
269
|
+
*/
|
|
270
|
+
interface RelayTransport {
|
|
271
|
+
/** Publish a signed event to the relay(s). Fire-and-forget / best-effort. */
|
|
272
|
+
publish(event: RelayEvent): void;
|
|
273
|
+
/**
|
|
274
|
+
* Subscribe to events matching `filter`. Returns an unsubscribe. A faithful
|
|
275
|
+
* relay replays matching STORED events to a brand-new subscriber before any
|
|
276
|
+
* future ones, so the presence handshake is order-independent.
|
|
277
|
+
*/
|
|
278
|
+
subscribe(filter: RelayFilter, onEvent: (event: RelayEvent) => void): () => void;
|
|
279
|
+
/** Close all relay connections. Idempotent. */
|
|
280
|
+
close(): void;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Default public Nostr relays (the public commons — we host none of them).
|
|
284
|
+
* Overridable via {@link createNostrPoolTransport}. These are well-known,
|
|
285
|
+
* generally-open relays; any reachable public relay works.
|
|
286
|
+
*/
|
|
287
|
+
declare const DEFAULT_RELAYS: readonly string[];
|
|
288
|
+
/** Kind for encrypted DM payloads — the standard NIP-04 sealed-direct-message kind. */
|
|
289
|
+
declare const VIBEDATE_MESSAGE_KIND = 4;
|
|
290
|
+
/**
|
|
291
|
+
* Kind for the presence/rendezvous ping (parameterized-replaceable range). Its
|
|
292
|
+
* content is a constant marker that reveals nothing about the conversation; its
|
|
293
|
+
* `event.pubkey` is the sender's Nostr key (public by definition), and an
|
|
294
|
+
* `ed25519` tag binds it to the sender's identity so the receiver only accepts
|
|
295
|
+
* presence from the peer it expects.
|
|
296
|
+
*/
|
|
297
|
+
declare const VIBEDATE_PRESENCE_KIND = 30078;
|
|
298
|
+
/**
|
|
299
|
+
* Derive the deterministic conversation tag two peers rendezvous on, from BOTH
|
|
300
|
+
* ed25519 identity pubkeys (sorted, hashed). Pure: both sides compute it
|
|
301
|
+
* byte-identically from data each already holds (their own identity + the
|
|
302
|
+
* peer's advertised pubkey), so NO extra exchange is needed to agree on the
|
|
303
|
+
* rendezvous point. The hash hides the underlying identities from anyone
|
|
304
|
+
* scanning the relay by tag.
|
|
305
|
+
*/
|
|
306
|
+
declare function conversationTag(myEd25519Hex: string, peerEd25519Hex: string): string;
|
|
307
|
+
/**
|
|
308
|
+
* A relay-fallback link with the SAME shape as {@link PeerLink}, so callers can
|
|
309
|
+
* drop it in wherever a direct P2P link is used (pairing, the `live` chat loop).
|
|
310
|
+
*
|
|
311
|
+
* Implemented in v0: `send`/`onMessage`/`onClose`/`close` + `.hello` (text chat
|
|
312
|
+
* over NIP-04). `sendMedia`/`sendSignal`/`onMedia`/`onSignal` are accepted as
|
|
313
|
+
* no-ops so the link is type-interchangeable with a direct PeerLink — chunked
|
|
314
|
+
* media + WebRTC signaling over the relay are `// ponytail:` follow-ups (a relay
|
|
315
|
+
* link is a fallback for peers who can't connect at all, where plain text chat
|
|
316
|
+
* is the priority).
|
|
317
|
+
*/
|
|
318
|
+
type NostrRelayLink = PeerLink;
|
|
319
|
+
/** Options for {@link createNostrRelayLink}. */
|
|
320
|
+
interface CreateNostrRelayLinkOptions {
|
|
321
|
+
/** This peer's Nostr secp256k1 keypair (see {@link loadOrCreateNostrKey}). */
|
|
322
|
+
readonly myNostr: {
|
|
323
|
+
readonly sk: Uint8Array;
|
|
324
|
+
readonly pubkey: string;
|
|
325
|
+
};
|
|
326
|
+
/** This peer's ed25519 identity pubkey (hex) — half of the convTag input. */
|
|
327
|
+
readonly myEd25519Hex: string;
|
|
328
|
+
/** The REMOTE peer's ed25519 identity pubkey (hex) — the other convTag half. */
|
|
329
|
+
readonly peerEd25519Hex: string;
|
|
330
|
+
/** The remote peer's identity, surfaced as the link's `.hello`. */
|
|
331
|
+
readonly hello: PeerHello;
|
|
332
|
+
/** The injected relay transport (real pool OR an in-memory mock). */
|
|
333
|
+
readonly transport: RelayTransport;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Build a {@link NostrRelayLink} over the injected {@link RelayTransport}. The
|
|
337
|
+
* link immediately subscribes to the conversation tag and publishes its presence
|
|
338
|
+
* so the peer can learn this side's Nostr pubkey (the bootstrap for NIP-04).
|
|
339
|
+
* Resolves once subscribed + presence published. Async only because
|
|
340
|
+
* `nostr-tools` (NIP-04 + event signing) is lazy-imported on first use.
|
|
341
|
+
*/
|
|
342
|
+
declare function createNostrRelayLink(opts: CreateNostrRelayLinkOptions): Promise<NostrRelayLink>;
|
|
343
|
+
/**
|
|
344
|
+
* Build a real {@link RelayTransport} over public Nostr relays via
|
|
345
|
+
* `nostr-tools`'s `SimplePool`. `nostr-tools` (and its WebSocket stack) is
|
|
346
|
+
* imported lazily here. Async because constructing the pool may need the
|
|
347
|
+
* lazy module load. The returned transport:
|
|
348
|
+
* - `publish` is fire-and-forget (publishes to all relays, awaits none);
|
|
349
|
+
* - `subscribe` mirrors `SimplePool.subscribeMany` and returns an unsub;
|
|
350
|
+
* - `close` tears the pool down.
|
|
351
|
+
*/
|
|
352
|
+
declare function createNostrPoolTransport(urls?: readonly string[]): Promise<RelayTransport>;
|
|
353
|
+
|
|
354
|
+
/** Namespace prefix so room topics never collide with league topics (or anything
|
|
355
|
+
* else on the DHT). Mirrors {@link p2p.TOPIC_PREFIX} for the 1:1 path. */
|
|
356
|
+
declare const ROOM_TOPIC_PREFIX = "vibedate-room:";
|
|
357
|
+
/**
|
|
358
|
+
* Derive the 32-byte DHT topic for a named room. Deterministic: everyone who
|
|
359
|
+
* joins the same room name anywhere in the world hashes to the same topic,
|
|
360
|
+
* which is the entire discovery mechanism. Pure (mirrors {@link p2p.leagueTopic}).
|
|
361
|
+
*/
|
|
362
|
+
declare function roomTopic(name: string): Buffer;
|
|
363
|
+
/** A room member = a connected, handshaken peer. Same shape as a live peer. */
|
|
364
|
+
type RoomMember = PeerHello;
|
|
365
|
+
/** One group chat message: a `msg` frame's payload tagged with the sender. */
|
|
366
|
+
interface RoomMessage {
|
|
367
|
+
/** Sender's handle (from the validated hello — UNTRUSTED display data). */
|
|
368
|
+
readonly from: string;
|
|
369
|
+
readonly id: string;
|
|
370
|
+
readonly text: string;
|
|
371
|
+
readonly at: number;
|
|
372
|
+
}
|
|
373
|
+
interface RoomOptions {
|
|
374
|
+
/** What we broadcast. Must already be consent-gated by the caller. */
|
|
375
|
+
readonly hello: PeerHello;
|
|
376
|
+
/** Room name → its own DHT topic (see {@link roomTopic}). */
|
|
377
|
+
readonly room: string;
|
|
378
|
+
/** DHT bootstrap nodes; omit for the public DHT. Tests pass a local testnet. */
|
|
379
|
+
readonly bootstrap?: ReadonlyArray<{
|
|
380
|
+
readonly host: string;
|
|
381
|
+
readonly port: number;
|
|
382
|
+
}>;
|
|
383
|
+
/** Where peers.json lives. Defaults to ~/.vibedating. */
|
|
384
|
+
readonly stateDir?: string;
|
|
385
|
+
/** Predicate over a blocked handle — a blocked peer's hello is dropped exactly
|
|
386
|
+
* like in 1:1 discovery. Default: nothing blocked. */
|
|
387
|
+
readonly isBlocked?: (handle: string) => boolean;
|
|
388
|
+
/** Match-notification sink (tests capture with a fake). Best-effort. */
|
|
389
|
+
readonly notify?: NotifySink;
|
|
390
|
+
}
|
|
391
|
+
interface RoomSession {
|
|
392
|
+
/** The room name. */
|
|
393
|
+
readonly room: string;
|
|
394
|
+
/** The joined DHT topic (see {@link roomTopic}). */
|
|
395
|
+
readonly topic: Buffer;
|
|
396
|
+
/** What we broadcast. */
|
|
397
|
+
readonly hello: PeerHello;
|
|
398
|
+
/** Live member set, keyed by handle (excludes self). Mirrors the live view
|
|
399
|
+
* semantics of {@link p2p.DiscoverySession.peers}. */
|
|
400
|
+
readonly members: ReadonlyMap<string, RoomMember>;
|
|
401
|
+
/** Resolves when the first DHT announce/lookup round completes. */
|
|
402
|
+
readonly ready: Promise<unknown>;
|
|
403
|
+
/**
|
|
404
|
+
* Broadcast a text message to ALL room members (fan-out: one `msg` frame per
|
|
405
|
+
* member's {@link PeerLink}). Returns the handles the message was sent to.
|
|
406
|
+
* Best-effort: a member whose link has just closed is skipped silently.
|
|
407
|
+
*/
|
|
408
|
+
broadcast(text: string): readonly string[];
|
|
409
|
+
/** Register a callback fired for each incoming group message (with sender). */
|
|
410
|
+
onMessage(cb: (m: RoomMessage) => void): void;
|
|
411
|
+
/** Register a callback fired whenever the member roster changes (join/leave). */
|
|
412
|
+
onRoster(cb: (members: readonly RoomMember[]) => void): void;
|
|
413
|
+
/** Register a callback fired for each incoming `rtc-*` signaling frame,
|
|
414
|
+
* tagged with the sender's handle (full-mesh video signaling). */
|
|
415
|
+
onSignal(cb: (from: string, frame: RtcFrame) => void): void;
|
|
416
|
+
/** Relay one `rtc-*` signaling frame to one member (by handle). */
|
|
417
|
+
sendSignal(handle: string, frame: RtcFrame): void;
|
|
418
|
+
/** The underlying {@link PeerLink} to a member (by handle), or undefined. */
|
|
419
|
+
linkFor(handle: string): PeerLink | undefined;
|
|
420
|
+
/** Leave the room and destroy the node. Idempotent. */
|
|
421
|
+
close(): Promise<void>;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Join (or create) a named room on the DHT and discover ALL members. CONSENT
|
|
425
|
+
* GATE LIVES WITH THE CALLER — never call this without the `share:live` grant
|
|
426
|
+
* (or an explicit opt-in in the same breath), exactly like
|
|
427
|
+
* {@link p2p.startDiscovery}.
|
|
428
|
+
*
|
|
429
|
+
* Resolves once the first DHT announce/lookup round completes; the returned
|
|
430
|
+
* session's {@link RoomSession.members} map + {@link RoomSession.onRoster}
|
|
431
|
+
* callback track every member that joins or leaves thereafter.
|
|
432
|
+
*/
|
|
433
|
+
declare function startRoom(opts: RoomOptions): Promise<RoomSession>;
|
|
434
|
+
|
|
241
435
|
/**
|
|
242
436
|
* Persistent ed25519 identity — binds a handle to a keypair so a peer cannot
|
|
243
437
|
* impersonate it.
|
|
@@ -307,6 +501,28 @@ type IdentityVerdict = 'verified' | 'legacy' | 'drop';
|
|
|
307
501
|
* caller (discovery) turns 'drop' into "never recorded, never paired".
|
|
308
502
|
*/
|
|
309
503
|
declare function classifyHelloIdentity(hello: HelloClaims & Partial<IdentityProof>): IdentityVerdict;
|
|
504
|
+
/**
|
|
505
|
+
* A separate secp256k1 keypair for the Nostr relay fallback (see relay.ts).
|
|
506
|
+
* Kept DISTINCT from the ed25519 {@link Identity} on purpose: NIP-04 encryption
|
|
507
|
+
* requires a secp256k1 key, and reusing or deriving the ed25519 key for that
|
|
508
|
+
* would cross two unrelated cryptographic curves. The private key is persisted
|
|
509
|
+
* at `<dir>/nostr.json` (mode 0600) and never leaves the file; only the
|
|
510
|
+
* secp256k1 public key (hex) is ever published on a relay.
|
|
511
|
+
*/
|
|
512
|
+
interface NostrKey {
|
|
513
|
+
/** 32-byte secp256k1 secret key — the NIP-04 decrypt/encrypt key. Never sent. */
|
|
514
|
+
readonly sk: Uint8Array;
|
|
515
|
+
/** secp256k1 public key, hex (64 chars) — safe to publish; the relay sees it. */
|
|
516
|
+
readonly pubkey: string;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Load the persistent secp256k1 Nostr key, generating + storing it (mode 0600)
|
|
520
|
+
* on first use. A missing or corrupt file is (re)generated — never throws on
|
|
521
|
+
* disk content. Idempotent across runs: same file → same key. `nostr-tools` is
|
|
522
|
+
* imported LAZILY (only when a key must be generated) so non-relay commands
|
|
523
|
+
* never pay for the secp256k1 stack.
|
|
524
|
+
*/
|
|
525
|
+
declare function loadOrCreateNostrKey(dir?: string): Promise<NostrKey>;
|
|
310
526
|
|
|
311
527
|
/**
|
|
312
528
|
* A usage league (volume bucket). `max` is inclusive; the top tier is open-ended
|
|
@@ -423,4 +639,4 @@ declare const CANDIDATES: readonly Candidate[];
|
|
|
423
639
|
*/
|
|
424
640
|
declare function matches(myLeague: string, candidates?: readonly Candidate[]): Candidate[];
|
|
425
641
|
|
|
426
|
-
export { BELOW_LEAGUE, CANDIDATES, type Candidate, DEMO_TOTAL_TOKENS, type DiscoveryOptions, type DiscoverySession, type HelloClaims, type Identity, type IdentityProof, type IdentityVerdict, LEAGUES, LIVE_NOTICE, type League, type LocalUsageSnapshot, type PeerHello, type StoredPeer, TOKENS_ENV, TOPIC_PREFIX, allLeagueNames, canonicalHelloClaims, classifyHelloIdentity, league, leagueIndex, leagueTopic, leaguesWithin, loadOrCreateIdentity, loadPeers, matches, parseHandshake, parseTokensEnv, readUsage, recordPeer, recordPeerMessage, serializeHandshake, signHelloClaims, startDiscovery, verifyHelloClaims };
|
|
642
|
+
export { BELOW_LEAGUE, CANDIDATES, type Candidate, type CreateNostrRelayLinkOptions, DEFAULT_RELAYS, DEMO_TOTAL_TOKENS, type DiscoveryOptions, type DiscoverySession, type HelloClaims, type Identity, type IdentityProof, type IdentityVerdict, LEAGUES, LIVE_NOTICE, type League, type LocalUsageSnapshot, type NostrKey, type NostrRelayLink, type PeerHello, ROOM_TOPIC_PREFIX, type RelayEvent, type RelayFilter, type RelayTransport, type RoomMember, type RoomMessage, type RoomOptions, type RoomSession, type StoredPeer, TOKENS_ENV, TOPIC_PREFIX, VIBEDATE_MESSAGE_KIND, VIBEDATE_PRESENCE_KIND, allLeagueNames, canonicalHelloClaims, classifyHelloIdentity, conversationTag, createNostrPoolTransport, createNostrRelayLink, league, leagueIndex, leagueTopic, leaguesWithin, loadOrCreateIdentity, loadOrCreateNostrKey, loadPeers, matches, parseHandshake, parseTokensEnv, readUsage, recordPeer, recordPeerMessage, roomTopic, serializeHandshake, signHelloClaims, startDiscovery, startRoom, verifyHelloClaims };
|
package/dist/index.js
CHANGED
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BELOW_LEAGUE,
|
|
3
3
|
CANDIDATES,
|
|
4
|
+
DEFAULT_RELAYS,
|
|
4
5
|
DEMO_TOTAL_TOKENS,
|
|
5
6
|
LEAGUES,
|
|
6
7
|
LIVE_NOTICE,
|
|
8
|
+
ROOM_TOPIC_PREFIX,
|
|
7
9
|
TOKENS_ENV,
|
|
8
10
|
TOPIC_PREFIX,
|
|
11
|
+
VIBEDATE_MESSAGE_KIND,
|
|
12
|
+
VIBEDATE_PRESENCE_KIND,
|
|
9
13
|
allLeagueNames,
|
|
10
14
|
canonicalHelloClaims,
|
|
11
15
|
classifyHelloIdentity,
|
|
16
|
+
conversationTag,
|
|
12
17
|
createConsentLedger,
|
|
18
|
+
createNostrPoolTransport,
|
|
19
|
+
createNostrRelayLink,
|
|
13
20
|
league,
|
|
14
21
|
leagueIndex,
|
|
15
22
|
leagueTopic,
|
|
16
23
|
leaguesWithin,
|
|
17
24
|
loadOrCreateIdentity,
|
|
25
|
+
loadOrCreateNostrKey,
|
|
18
26
|
loadPeers,
|
|
19
27
|
matches,
|
|
20
28
|
parseHandshake,
|
|
@@ -22,28 +30,38 @@ import {
|
|
|
22
30
|
readUsage,
|
|
23
31
|
recordPeer,
|
|
24
32
|
recordPeerMessage,
|
|
33
|
+
roomTopic,
|
|
25
34
|
serializeHandshake,
|
|
26
35
|
signHelloClaims,
|
|
27
36
|
startDiscovery,
|
|
37
|
+
startRoom,
|
|
28
38
|
verifyHelloClaims
|
|
29
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-HDHJLZZG.js";
|
|
30
40
|
export {
|
|
31
41
|
BELOW_LEAGUE,
|
|
32
42
|
CANDIDATES,
|
|
43
|
+
DEFAULT_RELAYS,
|
|
33
44
|
DEMO_TOTAL_TOKENS,
|
|
34
45
|
LEAGUES,
|
|
35
46
|
LIVE_NOTICE,
|
|
47
|
+
ROOM_TOPIC_PREFIX,
|
|
36
48
|
TOKENS_ENV,
|
|
37
49
|
TOPIC_PREFIX,
|
|
50
|
+
VIBEDATE_MESSAGE_KIND,
|
|
51
|
+
VIBEDATE_PRESENCE_KIND,
|
|
38
52
|
allLeagueNames,
|
|
39
53
|
canonicalHelloClaims,
|
|
40
54
|
classifyHelloIdentity,
|
|
55
|
+
conversationTag,
|
|
41
56
|
createConsentLedger,
|
|
57
|
+
createNostrPoolTransport,
|
|
58
|
+
createNostrRelayLink,
|
|
42
59
|
league,
|
|
43
60
|
leagueIndex,
|
|
44
61
|
leagueTopic,
|
|
45
62
|
leaguesWithin,
|
|
46
63
|
loadOrCreateIdentity,
|
|
64
|
+
loadOrCreateNostrKey,
|
|
47
65
|
loadPeers,
|
|
48
66
|
matches,
|
|
49
67
|
parseHandshake,
|
|
@@ -51,8 +69,10 @@ export {
|
|
|
51
69
|
readUsage,
|
|
52
70
|
recordPeer,
|
|
53
71
|
recordPeerMessage,
|
|
72
|
+
roomTopic,
|
|
54
73
|
serializeHandshake,
|
|
55
74
|
signHelloClaims,
|
|
56
75
|
startDiscovery,
|
|
76
|
+
startRoom,
|
|
57
77
|
verifyHelloClaims
|
|
58
78
|
};
|
package/dist/mcp.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibedate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
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",
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
32
32
|
"@pooriaarab/vibe-core": "^0.3.0",
|
|
33
|
-
"hyperswarm": "^4.17.0"
|
|
33
|
+
"hyperswarm": "^4.17.0",
|
|
34
|
+
"nostr-tools": "^2.24.1"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@types/node": "^20.11.0",
|