vibedate 0.4.1 → 0.6.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/index.d.ts CHANGED
@@ -238,6 +238,119 @@ 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
+
241
354
  /**
242
355
  * Persistent ed25519 identity — binds a handle to a keypair so a peer cannot
243
356
  * impersonate it.
@@ -307,6 +420,28 @@ type IdentityVerdict = 'verified' | 'legacy' | 'drop';
307
420
  * caller (discovery) turns 'drop' into "never recorded, never paired".
308
421
  */
309
422
  declare function classifyHelloIdentity(hello: HelloClaims & Partial<IdentityProof>): IdentityVerdict;
423
+ /**
424
+ * A separate secp256k1 keypair for the Nostr relay fallback (see relay.ts).
425
+ * Kept DISTINCT from the ed25519 {@link Identity} on purpose: NIP-04 encryption
426
+ * requires a secp256k1 key, and reusing or deriving the ed25519 key for that
427
+ * would cross two unrelated cryptographic curves. The private key is persisted
428
+ * at `<dir>/nostr.json` (mode 0600) and never leaves the file; only the
429
+ * secp256k1 public key (hex) is ever published on a relay.
430
+ */
431
+ interface NostrKey {
432
+ /** 32-byte secp256k1 secret key — the NIP-04 decrypt/encrypt key. Never sent. */
433
+ readonly sk: Uint8Array;
434
+ /** secp256k1 public key, hex (64 chars) — safe to publish; the relay sees it. */
435
+ readonly pubkey: string;
436
+ }
437
+ /**
438
+ * Load the persistent secp256k1 Nostr key, generating + storing it (mode 0600)
439
+ * on first use. A missing or corrupt file is (re)generated — never throws on
440
+ * disk content. Idempotent across runs: same file → same key. `nostr-tools` is
441
+ * imported LAZILY (only when a key must be generated) so non-relay commands
442
+ * never pay for the secp256k1 stack.
443
+ */
444
+ declare function loadOrCreateNostrKey(dir?: string): Promise<NostrKey>;
310
445
 
311
446
  /**
312
447
  * A usage league (volume bucket). `max` is inclusive; the top tier is open-ended
@@ -423,4 +558,4 @@ declare const CANDIDATES: readonly Candidate[];
423
558
  */
424
559
  declare function matches(myLeague: string, candidates?: readonly Candidate[]): Candidate[];
425
560
 
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 };
561
+ 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, type RelayEvent, type RelayFilter, type RelayTransport, 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, serializeHandshake, signHelloClaims, startDiscovery, verifyHelloClaims };
package/dist/index.js CHANGED
@@ -1,20 +1,27 @@
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,
7
8
  TOKENS_ENV,
8
9
  TOPIC_PREFIX,
10
+ VIBEDATE_MESSAGE_KIND,
11
+ VIBEDATE_PRESENCE_KIND,
9
12
  allLeagueNames,
10
13
  canonicalHelloClaims,
11
14
  classifyHelloIdentity,
15
+ conversationTag,
12
16
  createConsentLedger,
17
+ createNostrPoolTransport,
18
+ createNostrRelayLink,
13
19
  league,
14
20
  leagueIndex,
15
21
  leagueTopic,
16
22
  leaguesWithin,
17
23
  loadOrCreateIdentity,
24
+ loadOrCreateNostrKey,
18
25
  loadPeers,
19
26
  matches,
20
27
  parseHandshake,
@@ -26,24 +33,31 @@ import {
26
33
  signHelloClaims,
27
34
  startDiscovery,
28
35
  verifyHelloClaims
29
- } from "./chunk-43IYNWES.js";
36
+ } from "./chunk-VZXPYU2C.js";
30
37
  export {
31
38
  BELOW_LEAGUE,
32
39
  CANDIDATES,
40
+ DEFAULT_RELAYS,
33
41
  DEMO_TOTAL_TOKENS,
34
42
  LEAGUES,
35
43
  LIVE_NOTICE,
36
44
  TOKENS_ENV,
37
45
  TOPIC_PREFIX,
46
+ VIBEDATE_MESSAGE_KIND,
47
+ VIBEDATE_PRESENCE_KIND,
38
48
  allLeagueNames,
39
49
  canonicalHelloClaims,
40
50
  classifyHelloIdentity,
51
+ conversationTag,
41
52
  createConsentLedger,
53
+ createNostrPoolTransport,
54
+ createNostrRelayLink,
42
55
  league,
43
56
  leagueIndex,
44
57
  leagueTopic,
45
58
  leaguesWithin,
46
59
  loadOrCreateIdentity,
60
+ loadOrCreateNostrKey,
47
61
  loadPeers,
48
62
  matches,
49
63
  parseHandshake,
package/dist/mcp.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  runMcp
3
- } from "./chunk-ZB56ZBAR.js";
4
- import "./chunk-43IYNWES.js";
3
+ } from "./chunk-JIBC3OHV.js";
4
+ import "./chunk-VZXPYU2C.js";
5
5
  export {
6
6
  runMcp
7
7
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibedate",
3
- "version": "0.4.1",
3
+ "version": "0.6.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",