@cachly-dev/mcp-server 0.10.80 → 0.10.82

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/src/index.js CHANGED
@@ -65,7 +65,7 @@ import { Redis } from 'ioredis';
65
65
  let API_URL = process.env.CACHLY_API_URL ?? 'https://api.cachly.dev';
66
66
  let JWT = process.env.CACHLY_JWT ?? '';
67
67
  const _EMBED_MODEL = process.env.CACHLY_EMBED_MODEL ?? '';
68
- const CURRENT_VERSION = '0.10.80';
68
+ const CURRENT_VERSION = '0.10.82';
69
69
  // Max time to wait for a freshly-provisioned instance to become "running" before
70
70
  // giving up. Free-tier provisioning in high-latency regions can take 45–90s, so the
71
71
  // old hard-coded 25s caused spurious "instance_not_reachable" on first run.
@@ -624,15 +624,39 @@ function detectEditor() {
624
624
  : process.env.CLAUDE_CODE_ENTRYPOINT ? 'claude'
625
625
  : 'unknown';
626
626
  }
627
- function sendFunnelEvent(event, extra) {
627
+ /** Derive an anonymous, non-reversible fingerprint from the JWT sub claim. */
628
+ function _jwtUserFingerprint(jwt) {
629
+ try {
630
+ const parts = jwt.split('.');
631
+ if (parts.length !== 3)
632
+ return undefined;
633
+ const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf-8'));
634
+ if (!payload.sub)
635
+ return undefined;
636
+ // First 16 hex chars of a deterministic hash — no PII, not reversible.
637
+ let h = 0x811c9dc5;
638
+ for (let i = 0; i < payload.sub.length; i++) {
639
+ h ^= payload.sub.charCodeAt(i);
640
+ h = (h * 0x01000193) >>> 0;
641
+ }
642
+ return h.toString(16).padStart(8, '0') + payload.sub.length.toString(16).padStart(4, '0');
643
+ }
644
+ catch {
645
+ return undefined;
646
+ }
647
+ }
648
+ function sendFunnelEvent(event, extra, metrics) {
628
649
  if (process.env.CACHLY_NO_TELEMETRY === '1')
629
650
  return;
651
+ const fingerprint = JWT ? _jwtUserFingerprint(JWT) : undefined;
630
652
  void fetch(`${API_URL}/api/v1/telemetry/mcp`, {
631
653
  method: 'POST',
632
654
  headers: { 'Content-Type': 'application/json' },
633
655
  body: JSON.stringify({
634
656
  event, version: CURRENT_VERSION, editor: detectEditor(),
635
657
  ...(JWT ? { jwt: JWT } : {}),
658
+ ...(fingerprint ? { user_fingerprint: fingerprint } : {}),
659
+ ...(metrics ? { metrics } : {}),
636
660
  ...extra,
637
661
  }),
638
662
  signal: AbortSignal.timeout(3000),
@@ -749,7 +773,7 @@ async function handleTool(name, args) {
749
773
  sendFunnelEvent('learn_from_attempts', telemetryExtra);
750
774
  }
751
775
  else if (name === 'session_start') {
752
- sendFunnelEvent('session_start', telemetryExtra);
776
+ sendFunnelEvent('session_start', telemetryExtra, { born_at: undefined, recalls_total: undefined, starter_seeded: false });
753
777
  const resultText = typeof brainResult === 'object' && brainResult !== null && 'content' in brainResult
754
778
  ? JSON.stringify(brainResult)
755
779
  : String(brainResult ?? '');
@@ -779,7 +803,7 @@ async function handleTool(name, args) {
779
803
  try {
780
804
  const seed = await handleShareTool('brain_seed_starter', { instance_id: instanceId }, getConnection, apiFetch);
781
805
  if (seed) {
782
- sendFunnelEvent('brain_seed_starter', { ...telemetryExtra, auto: true });
806
+ sendFunnelEvent('brain_seed_starter', { ...telemetryExtra, auto: true }, { seeded_count: 16, auto: true });
783
807
  starterText = '\n---\n' + String(seed);
784
808
  }
785
809
  }
@@ -800,14 +824,15 @@ async function handleTool(name, args) {
800
824
  sendFunnelEvent('session_end', telemetryExtra);
801
825
  }
802
826
  else if (name === 'smart_recall') {
803
- sendFunnelEvent('smart_recall', telemetryExtra);
804
- // smart_recall is the primary recall tool in CLAUDE.md — it retrieves brain lessons
805
- // just like recall_best_solution. Count it toward BrainRecallCount so the dashboard
806
- // nudge clears and the first-recall email fires for users following CLAUDE.md.
807
827
  const srText = typeof brainResult === 'object' && brainResult !== null && 'content' in brainResult
808
828
  ? JSON.stringify(brainResult)
809
829
  : String(brainResult ?? '');
810
- if (srText.length > 50 && !srText.includes('No lessons found') && !srText.includes('no lessons') && !srText.includes('No matches found')) {
830
+ const srHit = srText.length > 50 && !srText.includes('No lessons found') && !srText.includes('no lessons') && !srText.includes('No matches found');
831
+ sendFunnelEvent('smart_recall', telemetryExtra, { hit: srHit, topic: String(args.query ?? '').slice(0, 80) });
832
+ // smart_recall is the primary recall tool in CLAUDE.md — it retrieves brain lessons
833
+ // just like recall_best_solution. Count it toward BrainRecallCount so the dashboard
834
+ // nudge clears and the first-recall email fires for users following CLAUDE.md.
835
+ if (srHit) {
811
836
  sendFunnelEvent('recall_best_solution', telemetryExtra);
812
837
  }
813
838
  // Self-healing: if the credential is degraded, the briefing may be thin or
@@ -864,7 +889,7 @@ async function handleTool(name, args) {
864
889
  const telemetryExtra = JWT ? { api_key: JWT, instance_id: instanceId } : { instance_id: instanceId };
865
890
  if (name === 'brain_from_git') {
866
891
  const gitCounts = _lastBrainFromGitCounts;
867
- sendFunnelEvent('brain_from_git', { ...telemetryExtra, ...(gitCounts ?? {}) });
892
+ sendFunnelEvent('brain_from_git', { ...telemetryExtra, ...(gitCounts ?? {}) }, gitCounts ? { fixes: gitCounts.fixes, features: gitCounts.features, refactors: gitCounts.refactors, total: gitCounts.total } : undefined);
868
893
  }
869
894
  else if (name === 'brain_predict_failures') {
870
895
  sendFunnelEvent('brain_predict_failures', telemetryExtra);