claude-rpc 0.13.2 → 0.13.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-rpc",
3
- "version": "0.13.2",
3
+ "version": "0.13.3",
4
4
  "description": "Discord Rich Presence for Claude Code — live model, project, tokens, and lifetime stats driven by Claude Code's hook system.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/cli.js CHANGED
@@ -1179,6 +1179,12 @@ async function profileVerify() {
1179
1179
  };
1180
1180
 
1181
1181
  try {
1182
+ // Make sure the profile row exists server-side before we verify it, so
1183
+ // verification works regardless of whether `profile publish` was run first.
1184
+ if (lb.profileIsPublishable(profile)) {
1185
+ const { flushProfile } = await import('./community.js');
1186
+ await flushProfile(cfg);
1187
+ }
1182
1188
  console.log(`${c.dim}requesting a verification token…${c.reset}`);
1183
1189
  const start = await post('/verify/start', { instanceId: community.instanceId, githubUser: profile.githubUser });
1184
1190
  if (!start.json?.token) return fail(`verify/start failed: ${start.json?.error || start.status}`, { code: EX_SYS_ERROR });
package/src/community.js CHANGED
@@ -26,7 +26,6 @@ import { VERSION } from './version.js';
26
26
  import { profileIsPublishable } from './leaderboard.js';
27
27
 
28
28
  const CURSOR_PATH = join(STATE_DIR, 'community-cursor.json');
29
- const PROFILE_CURSOR_PATH = join(STATE_DIR, 'profile-cursor.json');
30
29
 
31
30
  export function readCursor(path = CURSOR_PATH) {
32
31
  if (!existsSync(path)) return { sessions: 0, tokens: 0, ts: 0 };
@@ -87,25 +86,20 @@ function totalTokens(aggregate) {
87
86
  + (aggregate?.cacheWriteTokens || 0);
88
87
  }
89
88
 
90
- export function readProfileCursor(path = PROFILE_CURSOR_PATH) {
91
- const base = { sessions: 0, tokens: 0, activeMs: 0, ts: 0 };
92
- if (!existsSync(path)) return base;
93
- try { return { ...base, ...JSON.parse(readFileSync(path, 'utf8')) }; }
94
- catch { return base; }
95
- }
96
-
97
- export function buildProfilePayload(aggregate, profileCfg, cursor, { instanceId, now = Date.now() }) {
98
- const sessions = aggregate?.sessions || 0;
99
- const tokens = totalTokens(aggregate);
100
- const activeMs = aggregate?.activeMs || 0;
89
+ // A profile reports ABSOLUTE lifetime totals (not deltas). It's per-user and
90
+ // keyed by the instanceId, so the server just stores the latest value — no
91
+ // cursor, no double-count risk, and the board matches your real aggregate
92
+ // exactly. (Deltas were wrong here: the first publish carried the entire
93
+ // lifetime total, which blew past the per-report caps for any established user.)
94
+ export function buildProfilePayload(aggregate, profileCfg, { instanceId, now = Date.now() }) {
101
95
  return {
102
96
  instanceId,
103
97
  handle: profileCfg.handle,
104
98
  displayName: profileCfg.displayName || null,
105
99
  githubUser: profileCfg.githubUser || null,
106
- sessionsDelta: Math.max(0, sessions - (cursor.sessions || 0)),
107
- tokensDelta: Math.max(0, tokens - (cursor.tokens || 0)),
108
- activeMsDelta: Math.max(0, activeMs - (cursor.activeMs || 0)),
100
+ tokens: totalTokens(aggregate),
101
+ sessions: aggregate?.sessions || 0,
102
+ activeMs: aggregate?.activeMs || 0,
109
103
  streak: aggregate?.streak || 0,
110
104
  version: VERSION,
111
105
  osFamily: osFamily(),
@@ -115,7 +109,6 @@ export function buildProfilePayload(aggregate, profileCfg, cursor, { instanceId,
115
109
 
116
110
  export async function flushProfile(cfg, {
117
111
  aggregatePath = AGGREGATE_PATH,
118
- cursorPath = PROFILE_CURSOR_PATH,
119
112
  fetchImpl = globalThis.fetch,
120
113
  } = {}) {
121
114
  const profile = cfg?.profile || {};
@@ -130,9 +123,7 @@ export async function flushProfile(cfg, {
130
123
  try { aggregate = JSON.parse(readFileSync(aggregatePath, 'utf8')); }
131
124
  catch { return { ok: false, reason: 'unreadable-aggregate' }; }
132
125
 
133
- const cursor = readProfileCursor(cursorPath);
134
- const payload = buildProfilePayload(aggregate, profile, cursor, { instanceId });
135
-
126
+ const payload = buildProfilePayload(aggregate, profile, { instanceId });
136
127
  const url = community.endpoint.replace(/\/+$/, '') + '/profile';
137
128
  let res;
138
129
  try {
@@ -148,16 +139,7 @@ export async function flushProfile(cfg, {
148
139
  if (res.status === 429) return { ok: false, reason: 'rate-limited' };
149
140
  return { ok: false, reason: `http-${res.status}` };
150
141
  }
151
-
152
- // Advance the cursor only on acceptance (same reasoning as flushCommunity).
153
- writeCursor({
154
- sessions: (cursor.sessions || 0) + payload.sessionsDelta,
155
- tokens: (cursor.tokens || 0) + payload.tokensDelta,
156
- activeMs: (cursor.activeMs || 0) + payload.activeMsDelta,
157
- ts: payload.ts,
158
- }, cursorPath);
159
-
160
- return { ok: true, delta: { sessions: payload.sessionsDelta, tokens: payload.tokensDelta, activeMs: payload.activeMsDelta } };
142
+ return { ok: true, totals: { tokens: payload.tokens, sessions: payload.sessions, activeMs: payload.activeMs } };
161
143
  }
162
144
 
163
145
  // Single best-effort flush. Returns { ok, reason, delta? } — never throws.
package/src/daemon.js CHANGED
@@ -627,8 +627,8 @@ async function runCommunityFlush() {
627
627
  }
628
628
  if (config.profile?.enabled) {
629
629
  const pr = await flushProfile(config);
630
- if (pr.ok && pr.delta) {
631
- log(`profile: published @${config.profile.handle} (+${pr.delta.tokens} tokens)`);
630
+ if (pr.ok && pr.totals) {
631
+ log(`profile: published @${config.profile.handle} (${pr.totals.tokens} tokens)`);
632
632
  } else if (!pr.ok && pr.reason !== 'rate-limited' && pr.reason !== 'disabled') {
633
633
  log(`profile: ${pr.reason}${pr.error ? ' (' + pr.error + ')' : ''}`);
634
634
  }
package/src/version.js CHANGED
@@ -11,7 +11,7 @@ import { readFileSync } from 'node:fs';
11
11
  import { join } from 'node:path';
12
12
  import { ROOT } from './paths.js';
13
13
 
14
- const BAKED = '0.13.2';
14
+ const BAKED = '0.13.3';
15
15
 
16
16
  function readPkgVersion() {
17
17
  try {