claude-rpc 0.6.1 → 0.6.2
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 +1 -1
- package/src/format.js +78 -35
- package/src/version.js +1 -1
package/package.json
CHANGED
package/src/format.js
CHANGED
|
@@ -140,6 +140,37 @@ function fmtHour(h) {
|
|
|
140
140
|
return `${hh}:00`;
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
// Detect a cwd that would leak the user's OS username if rendered on
|
|
144
|
+
// Discord. Examples:
|
|
145
|
+
// /home/lucas → basename matches $USER → leak
|
|
146
|
+
// C:\Users\lucas → equals $USERPROFILE → leak
|
|
147
|
+
// /Users/lucas/projects/x → basename "x" ≠ user → fine
|
|
148
|
+
// On a real privacy-sensitive cwd (the home dir itself, with no project
|
|
149
|
+
// scoping), buildVars falls back to `appName` so the card reads
|
|
150
|
+
// "Idle in Claude Code" instead of "Idle in lucas".
|
|
151
|
+
function looksLikeUsernameLeak(cwd) {
|
|
152
|
+
if (!cwd) return false;
|
|
153
|
+
// Check both POSIX and Windows env vars unconditionally — a test or
|
|
154
|
+
// edge case might have one without the other, and over-suppressing
|
|
155
|
+
// the leak side is the safe direction.
|
|
156
|
+
const homes = [process.env.HOME, process.env.USERPROFILE].filter(Boolean);
|
|
157
|
+
const users = [process.env.USER, process.env.USERNAME].filter(Boolean);
|
|
158
|
+
// Normalize path separators so Windows-style cwds work on POSIX
|
|
159
|
+
// basename (which doesn't split on '\').
|
|
160
|
+
const norm = (p) => p.replace(/\\/g, '/').replace(/\/+$/, '').toLowerCase();
|
|
161
|
+
const cwdN = norm(cwd);
|
|
162
|
+
for (const home of homes) {
|
|
163
|
+
if (cwdN === norm(home)) return true;
|
|
164
|
+
}
|
|
165
|
+
if (users.length) {
|
|
166
|
+
const base = cwdN.split('/').pop() || '';
|
|
167
|
+
for (const u of users) {
|
|
168
|
+
if (base === u.toLowerCase()) return true;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
|
|
143
174
|
// Trim "C:\repo\src\app\page.tsx" → "src/app/page.tsx" (3 trailing segments).
|
|
144
175
|
function prettyFilePath(p) {
|
|
145
176
|
if (!p) return '';
|
|
@@ -156,7 +187,14 @@ export function buildVars(state, config, aggregate) {
|
|
|
156
187
|
// {tokens} / {tokensFmt} now means the grand total (in + out + cache).
|
|
157
188
|
const sessionTokens = sessionReal + sessionCacheRead + sessionCacheWrite;
|
|
158
189
|
const duration = state.sessionStart ? Date.now() - state.sessionStart : 0;
|
|
159
|
-
|
|
190
|
+
// Privacy: when cwd is the user's home dir (or its basename matches the
|
|
191
|
+
// OS username), don't render it. "Idle in lucas" on Discord is a username
|
|
192
|
+
// leak to anyone viewing the card. Fall back to the configured app name.
|
|
193
|
+
const cwdIsLeaky = looksLikeUsernameLeak(state.cwd);
|
|
194
|
+
const safeCwd = cwdIsLeaky ? '' : (state.cwd || '');
|
|
195
|
+
const projectPretty = cwdIsLeaky
|
|
196
|
+
? (config?.appName || 'Claude Code')
|
|
197
|
+
: (humanProject(state.cwd) || 'Claude Code');
|
|
160
198
|
const currentToolPretty = humanTool(state.currentTool);
|
|
161
199
|
const modelPretty = humanModel(state.model);
|
|
162
200
|
|
|
@@ -298,7 +336,7 @@ export function buildVars(state, config, aggregate) {
|
|
|
298
336
|
statusIcon: config?.statusIcons?.[state.status] || state.status || 'idle',
|
|
299
337
|
project: projectPretty,
|
|
300
338
|
projectPretty,
|
|
301
|
-
cwd:
|
|
339
|
+
cwd: safeCwd,
|
|
302
340
|
model: state.model || 'claude',
|
|
303
341
|
modelPretty,
|
|
304
342
|
messages,
|
|
@@ -527,6 +565,27 @@ export function fillTemplate(tpl, vars) {
|
|
|
527
565
|
return tpl.replace(/\{(\w+)\}/g, (_, key) => (key in vars ? String(vars[key]) : `{${key}}`));
|
|
528
566
|
}
|
|
529
567
|
|
|
568
|
+
// Helper used by every "go stale" branch in applyIdle. Wipes the current-
|
|
569
|
+
// activity slots so rotation frames can't render yesterday's project /
|
|
570
|
+
// file / tool names, and zeroes the session counters that are tied to
|
|
571
|
+
// the now-dead session.
|
|
572
|
+
function staleWipe(state) {
|
|
573
|
+
return {
|
|
574
|
+
...state,
|
|
575
|
+
status: 'stale',
|
|
576
|
+
currentTool: null,
|
|
577
|
+
currentFile: null,
|
|
578
|
+
sessionStart: null,
|
|
579
|
+
cwd: '',
|
|
580
|
+
messages: 0,
|
|
581
|
+
tools: 0,
|
|
582
|
+
filesOpened: [],
|
|
583
|
+
filesEdited: [],
|
|
584
|
+
filesRead: [],
|
|
585
|
+
tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
530
589
|
// Apply idle/stale transitions based on lastActivity age. Used by both daemon
|
|
531
590
|
// and the `preview` CLI command so they agree.
|
|
532
591
|
//
|
|
@@ -546,22 +605,7 @@ export function applyIdle(state, cfg = {}) {
|
|
|
546
605
|
// Authoritative close signal from the SessionEnd hook — trust it instead
|
|
547
606
|
// of waiting on staleSessionMin. Any other hook clears the flag, so a
|
|
548
607
|
// sibling session staying alive will reset us out of this branch.
|
|
549
|
-
if (state.claudeClosed)
|
|
550
|
-
return {
|
|
551
|
-
...state,
|
|
552
|
-
status: 'stale',
|
|
553
|
-
currentTool: null,
|
|
554
|
-
currentFile: null,
|
|
555
|
-
sessionStart: null,
|
|
556
|
-
cwd: '',
|
|
557
|
-
messages: 0,
|
|
558
|
-
tools: 0,
|
|
559
|
-
filesOpened: [],
|
|
560
|
-
filesEdited: [],
|
|
561
|
-
filesRead: [],
|
|
562
|
-
tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
563
|
-
};
|
|
564
|
-
}
|
|
608
|
+
if (state.claudeClosed) return staleWipe(state);
|
|
565
609
|
|
|
566
610
|
// Notification is a brief status — hold it for ~8s after the hook fires,
|
|
567
611
|
// then fall through to normal idle/stale processing.
|
|
@@ -578,22 +622,7 @@ export function applyIdle(state, cfg = {}) {
|
|
|
578
622
|
const liveAgeMs = mostRecentLiveMs ? now - mostRecentLiveMs : Infinity;
|
|
579
623
|
|
|
580
624
|
// Truly dormant: no live transcripts AND local state is old → stale.
|
|
581
|
-
if (ageMs > staleMs && liveAgeMs > staleMs)
|
|
582
|
-
return {
|
|
583
|
-
...state,
|
|
584
|
-
status: 'stale',
|
|
585
|
-
currentTool: null,
|
|
586
|
-
currentFile: null,
|
|
587
|
-
sessionStart: null,
|
|
588
|
-
cwd: '',
|
|
589
|
-
messages: 0,
|
|
590
|
-
tools: 0,
|
|
591
|
-
filesOpened: [],
|
|
592
|
-
filesEdited: [],
|
|
593
|
-
filesRead: [],
|
|
594
|
-
tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
595
|
-
};
|
|
596
|
-
}
|
|
625
|
+
if (ageMs > staleMs && liveAgeMs > staleMs) return staleWipe(state);
|
|
597
626
|
|
|
598
627
|
// Local state is stale but a live transcript exists somewhere on disk.
|
|
599
628
|
// Borrow the most-recent live session as our "active" context, since the
|
|
@@ -619,11 +648,25 @@ export function applyIdle(state, cfg = {}) {
|
|
|
619
648
|
}
|
|
620
649
|
|
|
621
650
|
// Local state is fresh.
|
|
622
|
-
if (state.status === 'idle')
|
|
651
|
+
if (state.status === 'idle') {
|
|
652
|
+
// Fast-path stale: if there are NO transcripts being written anywhere
|
|
653
|
+
// on disk, Claude Code isn't running. SessionEnd may not have fired
|
|
654
|
+
// (force-quit, OS sleep, crash). Going stale here clears Discord
|
|
655
|
+
// within ~90-120s of close instead of waiting the full staleMs (5min)
|
|
656
|
+
// — keeps the user's cwd off the card when they're away from their
|
|
657
|
+
// machine. The 5min legacy fallback below still catches the case
|
|
658
|
+
// where transcript mtime is fresh but the hook channel is silent.
|
|
659
|
+
if (liveSessions.length === 0) return staleWipe(state);
|
|
660
|
+
return state;
|
|
661
|
+
}
|
|
623
662
|
if (ageMs > idleMs) {
|
|
624
663
|
// Hook channel is quiet, but a live transcript was modified recently?
|
|
625
664
|
// Keep "working" instead of dropping to "idle".
|
|
626
665
|
if (liveAgeMs <= idleMs) return state;
|
|
666
|
+
// Hooks quiet AND no live transcripts → Claude is closed, not paused.
|
|
667
|
+
// Skip idle, go straight to stale. Same privacy reasoning as the
|
|
668
|
+
// idle-state fast-path above.
|
|
669
|
+
if (liveSessions.length === 0) return staleWipe(state);
|
|
627
670
|
// Going idle — wipe "current activity" indicators so rotation frames
|
|
628
671
|
// gated on filesEdited / currentFile / currentTool stop showing stale
|
|
629
672
|
// active-session data. Keep the session counters (messages/tools/tokens)
|