vibedate 0.3.1 → 0.4.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.
@@ -2,7 +2,7 @@ import {
2
2
  CANDIDATES,
3
3
  loadProfile,
4
4
  matches
5
- } from "./chunk-3VT4EOBX.js";
5
+ } from "./chunk-43IYNWES.js";
6
6
 
7
7
  // src/mcp.ts
8
8
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -29,7 +29,7 @@ async function runMcp() {
29
29
  `handle: ${p.handle}`,
30
30
  `harness: ${p.harness}`,
31
31
  `league: ${p.league} League`,
32
- `verified: ${p.verified ? "true (read-only OAuth)" : "false (self-reported)"}`,
32
+ `verified: ${p.verified ? "true (real local usage)" : "false (self-reported or demo)"}`,
33
33
  "privacy: raw token usage is local-only; only the league bucket is shared."
34
34
  ];
35
35
  return { content: [textBlock(lines.join("\n"))] };
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runMcp
4
- } from "./chunk-OVO2CQ7X.js";
4
+ } from "./chunk-ZB56ZBAR.js";
5
5
  import {
6
6
  CANDIDATES,
7
7
  LIVE_NOTICE,
@@ -17,6 +17,7 @@ import {
17
17
  leagueTopic,
18
18
  leaguesWithin,
19
19
  loadBlocklist,
20
+ loadOrCreateIdentity,
20
21
  loadPeers,
21
22
  loadProfile,
22
23
  matches,
@@ -27,8 +28,9 @@ import {
27
28
  resolveHandle,
28
29
  sameHandle,
29
30
  saveHandle,
31
+ signHelloClaims,
30
32
  startDiscovery
31
- } from "./chunk-3VT4EOBX.js";
33
+ } from "./chunk-43IYNWES.js";
32
34
 
33
35
  // src/cli.ts
34
36
  import readline from "readline";
@@ -1398,7 +1400,7 @@ async function handle(req, res, opts) {
1398
1400
  }
1399
1401
 
1400
1402
  // src/cli.ts
1401
- var VERSION = "0.3.1";
1403
+ var VERSION = "0.4.0";
1402
1404
  function parsePort(raw) {
1403
1405
  const n = Number(raw);
1404
1406
  if (!Number.isInteger(n) || n < 1 || n > 65535) return void 0;
@@ -1489,6 +1491,21 @@ function parseArgs(argv) {
1489
1491
  function leagueLabel(name) {
1490
1492
  return name === "below-1M" ? "below 1M (not yet in a league)" : `${name} League`;
1491
1493
  }
1494
+ function formatTokens(n) {
1495
+ const trim = (v) => String(Math.round(v * 10) / 10);
1496
+ const abs = Math.abs(n);
1497
+ if (abs >= 1e9) return `${trim(n / 1e9)}B`;
1498
+ if (abs >= 1e6) return `${trim(n / 1e6)}M`;
1499
+ if (abs >= 1e3) return `${trim(n / 1e3)}k`;
1500
+ return String(n);
1501
+ }
1502
+ function verificationText(snapshot) {
1503
+ if (snapshot.source === "real") {
1504
+ return `verified: real usage \u2014 ${formatTokens(snapshot.totalTokens)} tokens from ${snapshot.harness} logs`;
1505
+ }
1506
+ if (snapshot.source === "self-report") return "self-reported (unverified)";
1507
+ return "demo (unverified)";
1508
+ }
1492
1509
  function discoveryScope(myLeague, any) {
1493
1510
  const names = any ? allLeagueNames() : leaguesWithin(myLeague, 1);
1494
1511
  const ordered = [myLeague, ...names.filter((n) => n !== myLeague)];
@@ -1510,21 +1527,38 @@ function peerDirection(myLeague, peerLeague, sameBullet = "+") {
1510
1527
  if (d < 0) return { bullet: "\u2193", qual: " \xB7 lower league" };
1511
1528
  return { bullet: sameBullet, qual: "" };
1512
1529
  }
1530
+ function buildHello(profile) {
1531
+ const claims = {
1532
+ handle: resolveHandle(),
1533
+ league: profile.league,
1534
+ harness: profile.harness,
1535
+ verified: profile.verified
1536
+ };
1537
+ return { ...claims, ...signHelloClaims(loadOrCreateIdentity(), claims) };
1538
+ }
1539
+ function usageMark(peer) {
1540
+ return peer.verified === true ? "\u2713" : "~";
1541
+ }
1542
+ function idMark(peer) {
1543
+ return peer.identityVerified === true ? " \u{1F511}" : "";
1544
+ }
1545
+ var MARKS_LEGEND = "marks: \u2713 usage verified (real local logs) \xB7 ~ unverified (self-report/demo/legacy) \xB7 \u{1F511} identity-verified (signed hello)";
1513
1546
  async function cmdConnect() {
1514
1547
  const harness = process2.env["VIBEDATING_HARNESS"] ?? "claude-code";
1515
1548
  const handle2 = resolveHandle();
1516
1549
  const snapshot = await readUsage(harness);
1517
1550
  const profile = connectProfile(snapshot, handle2);
1551
+ const identity = loadOrCreateIdentity();
1518
1552
  const lg = league(snapshot.totalTokens);
1519
1553
  process2.stdout.write("\n");
1520
1554
  process2.stdout.write(` ${leagueLabel(lg.name)}
1521
1555
  `);
1522
1556
  process2.stdout.write(` handle: ${profile.handle} \xB7 harness: ${profile.harness}
1523
1557
  `);
1524
- process2.stdout.write(
1525
- ` verification: ${profile.verified ? "verified (read-only OAuth)" : "self-reported"}
1526
- `
1527
- );
1558
+ process2.stdout.write(` verification: ${verificationText(snapshot)}
1559
+ `);
1560
+ process2.stdout.write(` identity: ed25519 ${identity.publicKeyHex.slice(0, 12)}\u2026 \u2014 signs your hello (\u{1F511})
1561
+ `);
1528
1562
  process2.stdout.write("\n");
1529
1563
  process2.stdout.write(" \u2022 raw usage stays local \xB7 only league shared\n\n");
1530
1564
  return 0;
@@ -1573,11 +1607,13 @@ async function cmdMatches(live) {
1573
1607
  `);
1574
1608
  process2.stdout.write(
1575
1609
  `${livePeers.length} live peer${livePeers.length === 1 ? "" : "s"} in range (discovered over the DHT):
1576
-
1577
1610
  `
1578
1611
  );
1612
+ process2.stdout.write(` ${MARKS_LEGEND}
1613
+
1614
+ `);
1579
1615
  for (const p of livePeers) {
1580
- process2.stdout.write(` ${p.handle.padEnd(28)} ${p.league} \xB7 ${p.harness}
1616
+ process2.stdout.write(` ${p.handle.padEnd(28)} ${p.league} \xB7 ${p.harness} ${usageMark(p)}${idMark(p)}
1581
1617
  `);
1582
1618
  }
1583
1619
  process2.stdout.write("\n");
@@ -1613,17 +1649,15 @@ async function cmdDiscover(live, any) {
1613
1649
  if (live && !canShareLive()) grantLiveConsent();
1614
1650
  if (!canShareLive()) {
1615
1651
  process2.stderr.write(
1616
- "Live discovery is off. It shares ONLY your handle + league + harness (never raw usage)\nwith same-league peers on the public DHT. Opt in: `vibedating discover --live`\n"
1652
+ "Live discovery is off. It shares ONLY your handle + league + harness + verified flag + identity pubkey\n(never raw usage) with same-league peers on the public DHT. Opt in: `vibedating discover --live`\n"
1617
1653
  );
1618
1654
  return 1;
1619
1655
  }
1620
- const hello = {
1621
- handle: resolveHandle(),
1622
- league: profile.league,
1623
- harness: profile.harness
1624
- };
1656
+ const hello = buildHello(profile);
1625
1657
  process2.stdout.write("\n");
1626
1658
  process2.stdout.write(` ${LIVE_NOTICE}
1659
+ `);
1660
+ process2.stdout.write(` ${MARKS_LEGEND}
1627
1661
  `);
1628
1662
  const { topics, acceptLeague } = discoveryScope(profile.league, any);
1629
1663
  const session = await startDiscovery({
@@ -1634,7 +1668,7 @@ async function cmdDiscover(live, any) {
1634
1668
  onPeer: (peer, isNew) => {
1635
1669
  const { bullet, qual } = peerDirection(profile.league, peer.league);
1636
1670
  process2.stdout.write(
1637
- ` ${bullet} ${peer.handle} (${peer.league}${qual} \xB7 ${peer.harness})${isNew ? " \u2190 new match" : ""}
1671
+ ` ${bullet} ${peer.handle} (${peer.league}${qual} \xB7 ${peer.harness}) ${usageMark(peer)}${idMark(peer)}${isNew ? " \u2190 new match" : ""}
1638
1672
  `
1639
1673
  );
1640
1674
  }
@@ -1677,11 +1711,7 @@ async function cmdOpen(port) {
1677
1711
  process2.stdout.write(`
1678
1712
  ${LIVE_NOTICE}
1679
1713
  `);
1680
- const hello = {
1681
- handle: resolveHandle(),
1682
- league: profile.league,
1683
- harness: profile.harness
1684
- };
1714
+ const hello = buildHello(profile);
1685
1715
  void startDiscovery({ hello, isBlocked: blockedChecker(), onLink: (link) => live.addLink(link) }).then((s) => {
1686
1716
  session = s;
1687
1717
  }).catch(() => {
@@ -1723,13 +1753,11 @@ async function cmdLive(dating, any, to) {
1723
1753
  process2.stderr.write("Could not enable live discovery. Try `vibedating discover --live`.\n");
1724
1754
  return 1;
1725
1755
  }
1726
- const hello = {
1727
- handle: resolveHandle(),
1728
- league: profile.league,
1729
- harness: profile.harness
1730
- };
1756
+ const hello = buildHello(profile);
1731
1757
  process2.stdout.write("\n");
1732
1758
  process2.stdout.write(` ${LIVE_NOTICE}
1759
+ `);
1760
+ process2.stdout.write(` ${MARKS_LEGEND}
1733
1761
  `);
1734
1762
  process2.stdout.write(
1735
1763
  target !== null ? ` targeted \u2014 auto-opening ${target} when they connect (/quit to stop)
@@ -1743,7 +1771,7 @@ async function cmdLive(dating, any, to) {
1743
1771
  }
1744
1772
  const { qual } = peerDirection(profile.league, link.hello.league);
1745
1773
  process2.stdout.write(
1746
- ` \xB7 matched ${link.hello.handle} (${link.hello.league}${qual} \xB7 ${link.hello.harness})
1774
+ ` \xB7 matched ${link.hello.handle} (${link.hello.league}${qual} \xB7 ${link.hello.harness}) ${usageMark(link.hello)}${idMark(link.hello)}
1747
1775
  `
1748
1776
  );
1749
1777
  link.onMessage((m) => {
@@ -1761,7 +1789,7 @@ async function cmdLive(dating, any, to) {
1761
1789
  if (target !== null && !sameHandle(link.hello.handle, target)) {
1762
1790
  const { qual } = peerDirection(profile.league, link.hello.league);
1763
1791
  process2.stdout.write(
1764
- ` + ${link.hello.handle} (${link.hello.league}${qual}) \u2014 not your target
1792
+ ` + ${link.hello.handle} (${link.hello.league}${qual}) ${usageMark(link.hello)}${idMark(link.hello)} \u2014 not your target
1765
1793
  `
1766
1794
  );
1767
1795
  link.close();
@@ -1839,13 +1867,11 @@ async function cmdFind(targetArg, any) {
1839
1867
  process2.stderr.write("Could not enable live discovery. Try `vibedating discover --live`.\n");
1840
1868
  return 1;
1841
1869
  }
1842
- const hello = {
1843
- handle: resolveHandle(),
1844
- league: profile.league,
1845
- harness: profile.harness
1846
- };
1870
+ const hello = buildHello(profile);
1847
1871
  process2.stdout.write("\n");
1848
1872
  process2.stdout.write(` ${LIVE_NOTICE}
1873
+ `);
1874
+ process2.stdout.write(` ${MARKS_LEGEND}
1849
1875
  `);
1850
1876
  process2.stdout.write(
1851
1877
  ` looking for ${target} in your league${any ? " (+ every league)" : " + adjacent"}\u2026
@@ -1862,11 +1888,15 @@ async function cmdFind(targetArg, any) {
1862
1888
  const { qual } = peerDirection(profile.league, peer.league);
1863
1889
  if (sameHandle(peer.handle, target)) {
1864
1890
  found = true;
1865
- process2.stdout.write(` \u2605 found ${peer.handle} (${peer.league}${qual} \xB7 ${peer.harness})
1866
- `);
1891
+ process2.stdout.write(
1892
+ ` \u2605 found ${peer.handle} (${peer.league}${qual} \xB7 ${peer.harness}) ${usageMark(peer)}${idMark(peer)}
1893
+ `
1894
+ );
1867
1895
  } else {
1868
- process2.stdout.write(` + ${peer.handle} (${peer.league}${qual}) \u2014 not your target
1869
- `);
1896
+ process2.stdout.write(
1897
+ ` + ${peer.handle} (${peer.league}${qual}) ${usageMark(peer)}${idMark(peer)} \u2014 not your target
1898
+ `
1899
+ );
1870
1900
  }
1871
1901
  }
1872
1902
  });
@@ -1965,7 +1995,10 @@ Usage:
1965
1995
  Privacy:
1966
1996
  Raw token usage is read and stored LOCALLY (~/.vibedating). Only the league
1967
1997
  bucket is ever shared. Live discovery (off by default) shares ONLY your
1968
- handle + league + harness with same-league peers \u2014 opt in with --live.
1998
+ handle + league + harness + verified flag + identity pubkey (an ed25519 key
1999
+ generated on first connect, stored 0600 in ~/.vibedating/identity.json) with
2000
+ same-league peers \u2014 opt in with --live. Peers are marked: \u2713 usage verified
2001
+ (real local logs) \xB7 ~ unverified \xB7 \u{1F511} identity-verified (signed hello).
1969
2002
 
1970
2003
  Matching:
1971
2004
  discover/live match your league + adjacent (\xB11) tiers by default, so thin
package/dist/index.d.ts CHANGED
@@ -1,11 +1,16 @@
1
- import { VibeEvent, Harness, UsageSnapshot } from '@pooriaarab/vibe-core';
2
- export { Harness, UsageSnapshot, createConsentLedger } from '@pooriaarab/vibe-core';
1
+ import { VibeEvent, UsageSnapshot, UsageSource, Harness, HarnessUsageOptions } from '@pooriaarab/vibe-core';
2
+ export { Harness, UsageSnapshot, UsageSource, createConsentLedger } from '@pooriaarab/vibe-core';
3
+ import { KeyObject } from 'node:crypto';
3
4
 
4
5
  type Frame = {
5
6
  t: 'hello';
6
7
  handle: string;
7
8
  league: string;
8
9
  harness: string;
10
+ verified?: boolean;
11
+ pubkey?: string;
12
+ nonce?: string;
13
+ sig?: string;
9
14
  } | {
10
15
  t: 'msg';
11
16
  id: string;
@@ -57,11 +62,7 @@ interface ReceivedMedia {
57
62
 
58
63
  interface PeerLink {
59
64
  /** The validated identity of the remote peer (from the hello handshake). */
60
- readonly hello: {
61
- handle: string;
62
- league: string;
63
- harness: string;
64
- };
65
+ readonly hello: PeerHello;
65
66
  /** Send a line of text as a `msg` frame. */
66
67
  send(text: string): void;
67
68
  /** Read a file from disk and send it as a chunked media transfer. */
@@ -99,14 +100,36 @@ declare const TOPIC_PREFIX = "vibedate:";
99
100
  * entire discovery mechanism. Pure.
100
101
  */
101
102
  declare function leagueTopic(leagueName: string): Buffer;
102
- /** The ONLY three fields that ever leave the machine over a peer connection. */
103
+ /**
104
+ * The fields that ever leave the machine over a peer connection: handle, league,
105
+ * harness, and (optionally) the self-asserted usage-verification flag plus the
106
+ * identity proof (pubkey/nonce/sig). NEVER raw usage — no token totals, no logs.
107
+ * `verified`/`pubkey` are undefined for legacy peers that predate them; both
108
+ * `undefined` and `false` display as unverified (~).
109
+ */
103
110
  interface PeerHello {
104
111
  readonly handle: string;
105
112
  readonly league: string;
106
113
  readonly harness: string;
114
+ /**
115
+ * Self-asserted: the sender's usage came from real local logs (see readUsage).
116
+ * Bound to the sender's key by the identity signature when `pubkey` is present.
117
+ */
118
+ readonly verified?: boolean;
119
+ /** Raw ed25519 public key (64 hex) — the persistent identity this hello signs. */
120
+ readonly pubkey?: string;
121
+ /** Random per-hello nonce (hex) covered by the signature. */
122
+ readonly nonce?: string;
123
+ /** ed25519 signature (128 hex) over `handle|league|harness|verified|nonce`. */
124
+ readonly sig?: string;
125
+ /**
126
+ * LOCAL-DERIVED, never on the wire: true when this hello's signature verified
127
+ * against its pubkey (see classifyHelloIdentity). Marked 🔑 in the UI.
128
+ */
129
+ readonly identityVerified?: boolean;
107
130
  }
108
131
  /** One-line privacy notice printed before joining the swarm. */
109
- declare const LIVE_NOTICE = "live discovery: sharing only your handle + league + harness (never raw usage) with same-league peers on the public DHT";
132
+ declare const LIVE_NOTICE = "live discovery: sharing only your handle + league + harness + verified flag + identity pubkey (never raw usage) with same-league peers on the public DHT";
110
133
  /**
111
134
  * Serialize a hello to the single JSON line sent on connect. Built key-by-key
112
135
  * from the allowlist — even if a caller sneaks extra properties onto the
@@ -124,6 +147,8 @@ declare function parseHandshake(raw: string | Buffer): PeerHello | null;
124
147
  interface StoredPeer extends PeerHello {
125
148
  readonly firstSeenAt: string;
126
149
  readonly lastSeenAt: string;
150
+ /** LOCAL metadata: when the last `msg` from this peer arrived (never on the wire). */
151
+ readonly lastMessageAt?: string;
127
152
  }
128
153
  /** Load persisted live peers, or `[]` if none/corrupt. Local-only data. */
129
154
  declare function loadPeers(dir?: string): StoredPeer[];
@@ -135,6 +160,12 @@ declare function recordPeer(hello: PeerHello, dir?: string, now?: Date): {
135
160
  peer: StoredPeer;
136
161
  isNew: boolean;
137
162
  };
163
+ /**
164
+ * Stamp `lastMessageAt` on a stored peer (a `msg` just arrived from them).
165
+ * Local metadata only; never on the wire. Returns false when the handle isn't
166
+ * a known peer. Never throws — best-effort bookkeeping.
167
+ */
168
+ declare function recordPeerMessage(handle: string, dir?: string, now?: Date): boolean;
138
169
  /** Injection point for the match notification (defaults to vibe-core notify). */
139
170
  type NotifySink = (event: VibeEvent) => void;
140
171
  interface DiscoveryOptions {
@@ -208,14 +239,74 @@ interface DiscoverySession {
208
239
  declare function startDiscovery(opts: DiscoveryOptions): Promise<DiscoverySession>;
209
240
 
210
241
  /**
211
- * vibedatingdating by token usage.
242
+ * Persistent ed25519 identity binds a handle to a keypair so a peer cannot
243
+ * impersonate it.
212
244
  *
213
- * v0 library surface: league bucketing, local usage reading, and in-league
214
- * matching against a seeded set of candidate profiles. Raw usage never leaves
215
- * the machine; only the league bucket is shared.
245
+ * The keypair lives at `~/.vibedating/identity.json` (mode 0600), generated on
246
+ * first use and reused across runs. A hello that carries a `pubkey` must also
247
+ * carry a valid `sig` over the canonical claims string
248
+ * `handle|league|harness|verified|nonce`; anything else claiming a key is an
249
+ * impersonation attempt and the peer is DROPPED. A hello with no `pubkey` at
250
+ * all is a legacy peer — accepted, but never identity-verified.
216
251
  *
217
- * Builds on `@pooriaarab/vibe-core` (UsageSnapshot / Harness / consent ledger).
252
+ * node:crypto only no new dependency. The private key never leaves the file;
253
+ * only the raw public key (64 hex) and signatures (128 hex) travel on hellos.
254
+ */
255
+
256
+ /** A loaded identity: the raw public key (wire form) plus both key objects. */
257
+ interface Identity {
258
+ /** Raw 32-byte ed25519 public key, hex (64 chars) — the wire form. */
259
+ readonly publicKeyHex: string;
260
+ readonly publicKey: KeyObject;
261
+ readonly privateKey: KeyObject;
262
+ /** ISO timestamp of key generation (first connect). */
263
+ readonly createdAt: string;
264
+ }
265
+ /** The hello fields a signature commits to (a PeerHello minus its proof). */
266
+ interface HelloClaims {
267
+ readonly handle: string;
268
+ readonly league: string;
269
+ readonly harness: string;
270
+ readonly verified?: boolean;
271
+ }
272
+ /** The wire proof attached to a hello: pubkey + nonce + signature, all hex. */
273
+ interface IdentityProof {
274
+ readonly pubkey: string;
275
+ readonly nonce: string;
276
+ readonly sig: string;
277
+ }
278
+ /**
279
+ * Load the persistent keypair, generating + storing it (mode 0600) on first
280
+ * use. A missing or corrupt file is (re)generated — never throws on disk
281
+ * content. Idempotent across runs: same file → same key → same pubkey.
282
+ */
283
+ declare function loadOrCreateIdentity(dir?: string): Identity;
284
+ /**
285
+ * The canonical string an identity signature commits to:
286
+ * `handle|league|harness|verified|nonce` — verified rendered as `true`/`false`.
287
+ * Pure; both sides compute it byte-identically or the signature cannot verify.
288
+ */
289
+ declare function canonicalHelloClaims(claims: HelloClaims & {
290
+ nonce: string;
291
+ }): string;
292
+ /**
293
+ * Sign hello claims with the persistent identity. A fresh random 16-byte nonce
294
+ * per call, so two hellos never share a signature. Returns only the wire proof
295
+ * fields — the private key stays put.
218
296
  */
297
+ declare function signHelloClaims(identity: Identity, claims: HelloClaims): IdentityProof;
298
+ /**
299
+ * Verify a claimed proof against hello claims. NEVER throws — any anomaly
300
+ * (bad hex, bad key, bad signature) is simply `false`.
301
+ */
302
+ declare function verifyHelloClaims(claims: HelloClaims, proof: IdentityProof): boolean;
303
+ /** What an incoming hello's identity material amounts to. */
304
+ type IdentityVerdict = 'verified' | 'legacy' | 'drop';
305
+ /**
306
+ * Classify an incoming hello's identity claim. Pure decision, no IO — the
307
+ * caller (discovery) turns 'drop' into "never recorded, never paired".
308
+ */
309
+ declare function classifyHelloIdentity(hello: HelloClaims & Partial<IdentityProof>): IdentityVerdict;
219
310
 
220
311
  /**
221
312
  * A usage league (volume bucket). `max` is inclusive; the top tier is open-ended
@@ -277,26 +368,34 @@ declare const TOKENS_ENV = "VIBEDATING_TOKENS";
277
368
  */
278
369
  declare const DEMO_TOTAL_TOKENS = 23400000;
279
370
  /**
280
- * v0 usage reader. Resolution order:
281
- *
282
- * 1. (future) read-only OAuth `verified: true`. **NOT implemented in v0**
283
- * see {@link tryReadVerifiedUsage}. This arm is the deliberate seam.
284
- * 2. a self-reported value from the {@link TOKENS_ENV} env var → `verified: false`.
285
- * 3. the demo value {@link DEMO_TOTAL_TOKENS} → `verified: false`.
286
- *
287
- * The snapshot's `totalTokens` is the only thing that must never leave the
288
- * machine; everything downstream consumes only the league bucket.
371
+ * A usage snapshot with honest provenance. `source` says where `totalTokens`
372
+ * came from; `verified` is true ONLY for `source === 'real'` (measured from
373
+ * the harness's own local session logs). The token total is the one thing that
374
+ * must never leave the machine; everything downstream consumes only the league
375
+ * bucket plus the verified flag.
289
376
  */
290
- declare function readUsage(harness?: Harness): Promise<UsageSnapshot>;
377
+ interface LocalUsageSnapshot extends UsageSnapshot {
378
+ /** Where the total came from: measured locally, self-reported, or demo. */
379
+ readonly source: UsageSource;
380
+ /** Short human-facing provenance note from the reader (local display only). */
381
+ readonly detail?: string;
382
+ }
291
383
  /**
292
- * Future home of read-only OAuth verification. Returns `null` in v0.
384
+ * Usage reader, backed by vibe-core's {@link readHarnessUsage}. Resolution order:
385
+ *
386
+ * 1. a self-reported value — explicit `opts.selfReportTokens`, or the
387
+ * {@link TOKENS_ENV} env var (suffix-friendly: `12M`, `1.2B`), or vibe-core's
388
+ * own `VIBE_TOKENS` env → `source: 'self-report'` → `verified: false`.
389
+ * 2. the harness's REAL local session logs (claude-code / codex / gemini / pi /
390
+ * kimi), read from disk by vibe-core → `source: 'real'` → `verified: true`.
391
+ * 3. the demo value {@link DEMO_TOTAL_TOKENS} → `source: 'demo'` →
392
+ * `verified: false`.
293
393
  *
294
- * Implementing this (reading a real, provider-attested usage scope) flips the
295
- * snapshot to `verified: true` and is what makes the league trustworthy. Kept as
296
- * an explicit, named seam rather than a TODO comment so the privacy contract is
297
- * visible at the call site.
394
+ * The demo arm keeps vibedating's own demo total (10M league, non-empty demo
395
+ * pool) rather than vibe-core's smaller placeholder, so demo behavior is
396
+ * unchanged. Raw usage never leaves the machine.
298
397
  */
299
- declare function tryReadVerifiedUsage(_harness: Harness): Promise<UsageSnapshot | null>;
398
+ declare function readUsage(harness?: Harness, opts?: HarnessUsageOptions): Promise<LocalUsageSnapshot>;
300
399
  /**
301
400
  * Parse a self-reported token count: a plain integer (`23400000`), or a suffixed
302
401
  * value (`12M`, `1.2B`, `500k`, `500K`). Returns `undefined` for anything that is
@@ -324,4 +423,4 @@ declare const CANDIDATES: readonly Candidate[];
324
423
  */
325
424
  declare function matches(myLeague: string, candidates?: readonly Candidate[]): Candidate[];
326
425
 
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 };
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 };
package/dist/index.js CHANGED
@@ -7,21 +7,26 @@ import {
7
7
  TOKENS_ENV,
8
8
  TOPIC_PREFIX,
9
9
  allLeagueNames,
10
+ canonicalHelloClaims,
11
+ classifyHelloIdentity,
10
12
  createConsentLedger,
11
13
  league,
12
14
  leagueIndex,
13
15
  leagueTopic,
14
16
  leaguesWithin,
17
+ loadOrCreateIdentity,
15
18
  loadPeers,
16
19
  matches,
17
20
  parseHandshake,
18
21
  parseTokensEnv,
19
22
  readUsage,
20
23
  recordPeer,
24
+ recordPeerMessage,
21
25
  serializeHandshake,
26
+ signHelloClaims,
22
27
  startDiscovery,
23
- tryReadVerifiedUsage
24
- } from "./chunk-3VT4EOBX.js";
28
+ verifyHelloClaims
29
+ } from "./chunk-43IYNWES.js";
25
30
  export {
26
31
  BELOW_LEAGUE,
27
32
  CANDIDATES,
@@ -31,18 +36,23 @@ export {
31
36
  TOKENS_ENV,
32
37
  TOPIC_PREFIX,
33
38
  allLeagueNames,
39
+ canonicalHelloClaims,
40
+ classifyHelloIdentity,
34
41
  createConsentLedger,
35
42
  league,
36
43
  leagueIndex,
37
44
  leagueTopic,
38
45
  leaguesWithin,
46
+ loadOrCreateIdentity,
39
47
  loadPeers,
40
48
  matches,
41
49
  parseHandshake,
42
50
  parseTokensEnv,
43
51
  readUsage,
44
52
  recordPeer,
53
+ recordPeerMessage,
45
54
  serializeHandshake,
55
+ signHelloClaims,
46
56
  startDiscovery,
47
- tryReadVerifiedUsage
57
+ verifyHelloClaims
48
58
  };
package/dist/mcp.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  runMcp
3
- } from "./chunk-OVO2CQ7X.js";
4
- import "./chunk-3VT4EOBX.js";
3
+ } from "./chunk-ZB56ZBAR.js";
4
+ import "./chunk-43IYNWES.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.3.1",
3
+ "version": "0.4.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",
@@ -29,7 +29,7 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@modelcontextprotocol/sdk": "^1.0.0",
32
- "@pooriaarab/vibe-core": "^0.2.0",
32
+ "@pooriaarab/vibe-core": "^0.3.0",
33
33
  "hyperswarm": "^4.17.0"
34
34
  },
35
35
  "devDependencies": {