@cachly-dev/mcp-server 0.10.113 → 0.10.115

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/README.md CHANGED
@@ -101,6 +101,26 @@ Like what you see? Make it permanent in the next step.
101
101
 
102
102
  ---
103
103
 
104
+ ## Brain-first — Semantic Cache as Proof-Point
105
+
106
+ cachly is not a semantic cache with a brain bolt-on. The Brain is the product. The
107
+ Semantic Cache is the **proof-point** — it shows ROI in dollars from day one, with zero
108
+ trust required. It opens the door. The Brain is why teams never leave.
109
+
110
+ | | Wedge — Land | Moat — Retain |
111
+ |---|---|---|
112
+ | **Feature** | Semantic Cache | AI Brain (Lessons, Recall, Team-Sharing) |
113
+ | **Value** | Measurable cost savings from day one | Compounding team intelligence |
114
+ | **Metric** | Cache-hit rate, $/month saved | Lessons retained, WoW trend, recall quality |
115
+ | **Analogy** | Datadog APM (surfaces the problem) | Stripe (becomes critical infrastructure) |
116
+
117
+ **The org-level advantage:** Brain lessons and cache hits are shared across the whole
118
+ team — one person's fix becomes every agent's reflex. Anthropic Projects Memory is
119
+ per-user and model-locked. cachly is team-wide and model-neutral. That's the structural
120
+ moat no first-party tool can build.
121
+
122
+ ---
123
+
104
124
  ## Setup — one command
105
125
 
106
126
  ```bash
@@ -229,7 +249,7 @@ causal_trace(problem="auth breaks after restart")
229
249
  npx @cachly-dev/mcp-server@latest autopilot # One command — signs in, configures every editor, bootstraps from git
230
250
  npx @cachly-dev/mcp-server@latest demo # Preview your Brain (no account needed)
231
251
  npx @cachly-dev/mcp-server@latest bench # Recall quality vs flat-file memory (no auth required)
232
- npx @cachly-dev/mcp-server@latest setup # Interactive variant — pick editors yourself
252
+ npx @cachly-dev/mcp-server@latest autosetup # Interactive variant — pick editors yourself
233
253
  npx @cachly-dev/mcp-server@latest health # Check token, API, editors, git hook
234
254
  npx @cachly-dev/mcp-server@latest digest # Weekly Brain summary — shareable
235
255
  npx @cachly-dev/mcp-server@latest share # Generate a shareable stats card + tweet
@@ -378,17 +398,17 @@ following. Manual snippets are in the **Manual Setup** section below.
378
398
  | **Cline** | ✅ detected via VS Code | `.vscode/mcp.json` | — | Shares config with Copilot; restart VS Code |
379
399
  | **Continue.dev** | ✅ detected via `.continue/` | `.continue/config.json` | — | Uses `modelContextProtocolServers` key |
380
400
  | **Zed** | ✅ detected via `.zed/` | `.zed/settings.json` | — | Uses `context_servers` key |
381
- | **Windsurf (global)** | `setup --editor windsurf` | `~/.windsurf/mcp.json` | ✅ | Pass `--editor` to target global config |
382
- | **Any other MCP client** | `init --editor claude` | `.mcp.json` | — | Standard `mcpServers` stdio format |
401
+ | **Windsurf (global)** | `autosetup --editor windsurf` | `~/.windsurf/mcp.json` | ✅ | Pass `--editor` to target global config |
402
+ | **Any other MCP client** | `autosetup --editor claude` | `.mcp.json` | — | Standard `mcpServers` stdio format |
383
403
 
384
404
  **Which sign-in path each editor uses:**
385
405
 
386
406
  | Scenario | Path |
387
407
  |---|---|
388
- | `setup` from a real terminal (TTY) | OAuth device-flow → browser click → API key saved automatically |
389
- | `setup` from VSCode task / CI (non-TTY) | Auto-detects non-interactive, opens browser with step-by-step guide, prints `CACHLY_JWT=... setup` instruction |
408
+ | `autosetup` from a real terminal (TTY) | OAuth device-flow → browser click → API key saved automatically |
409
+ | `autosetup` from VSCode task / CI (non-TTY) | Auto-detects non-interactive, opens browser with step-by-step guide, prints `CACHLY_JWT=... autosetup` instruction |
390
410
  | First tool call from Claude Code (no JWT yet) | Inline device-flow: MCP returns URL + code, browser opens automatically, next call proceeds |
391
- | `CACHLY_JWT=cky_live_xxx npx ... setup` | Skips auth step entirely, uses provided key |
411
+ | `CACHLY_JWT=cky_live_xxx npx ... autosetup` | Skips auth step entirely, uses provided key |
392
412
 
393
413
  > **Tip — fastest per-project setup from inside Claude Code:**
394
414
  > ```
@@ -480,12 +500,12 @@ private backend instead of `api.cachly.dev`:
480
500
  npx @cachly-dev/mcp-server@latest autopilot --api-url https://cachly.mycorp.internal
481
501
 
482
502
  # Or non-interactively
483
- npx @cachly-dev/mcp-server@latest init \
503
+ npx @cachly-dev/mcp-server@latest autosetup \
484
504
  --instance-id <uuid> --api-key <cky_live_...> \
485
505
  --api-url https://cachly.mycorp.internal
486
506
  ```
487
507
 
488
- `setup`/`init` bake `CACHLY_API_URL` into the editor config **only** when it differs
508
+ `autosetup` bakes `CACHLY_API_URL` into the editor config **only** when it differs
489
509
  from the default cloud — so default installs stay clean, and self-hosted installs
490
510
  keep talking to your backend on every editor launch. All data stays in your infra.
491
511
 
@@ -7,8 +7,22 @@ import { keywordSearch, tokenize, splitMultiQuery, levenshtein, indexVocab as _i
7
7
  import { rerankByQuality } from '../rerank.js';
8
8
  import { computeEmbedding, hasEmbedProvider } from '../embeddings.js';
9
9
  // ── Changelog (shown once per version in session_start) ──────────────────────
10
- const MCP_VERSION = '0.10.113';
10
+ const MCP_VERSION = '0.10.115';
11
11
  const WHATS_NEW = {
12
+ '0.10.115': [
13
+ `🔧 **\`brain_get_pref\` + \`brain_set_pref\` — persistent Brain preferences**`,
14
+ ` 📖 \`brain_get_pref\` reads back any stored preference (omit key to list all).`,
15
+ ` ✏️ \`brain_set_pref\` saves a preference to Redis — survives restarts, scoped per Brain instance.`,
16
+ ` 💡 Known key: \`auto_changelog\` — set to "false" to silence the new-lessons digest at session_start.`,
17
+ ` 📊 141 MCP tools`,
18
+ ],
19
+ '0.10.114': [
20
+ `📋 **\`brain_changelog\` — weekly knowledge digest in one call**`,
21
+ ` 📅 Generates a grouped Markdown changelog of lessons learned in the last N days.`,
22
+ ` 👥 Grouped by topic category, annotated with author, recall count, and confidence — paste directly into standup or Slack.`,
23
+ ` ⚙️ Params: \`days\` (default 7), \`max_lessons\` (default 30), \`include_failures\` (default true).`,
24
+ ` 📊 138 MCP tools`,
25
+ ],
12
26
  '0.10.113': [
13
27
  `📈 **Week-over-week savings trend in \`cache_stats\`**`,
14
28
  ` 📅 \`cache_stats\` now shows **7-day hits** + **WoW trend %** (e.g. \`+23.0% vs prev week\`) so ROI momentum is visible, not just the cumulative total.`,
@@ -348,7 +362,8 @@ export const BRAIN_TOOL_NAMES = new Set([
348
362
  'session_start', 'session_start_summary', 'session_end', 'session_ping', 'session_handoff', 'auto_learn_session',
349
363
  'brain_who_knows', 'brain_file_map', 'team_expertise_map',
350
364
  'skill_gaps', 'brain_coverage', 'brain_metrics', 'brain_service_map',
351
- 'brain_collab_pairs', 'brain_portability',
365
+ 'brain_collab_pairs', 'brain_portability', 'brain_changelog',
366
+ 'brain_set_pref', 'brain_get_pref',
352
367
  ]);
353
368
  // ── Free-tier Teaser-Gate ──────────────────────────────────────────────────
354
369
  // When a Free-tier Brain crosses its recall limit, smart_recall still delivers
@@ -2005,6 +2020,49 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
2005
2020
  reasons.push('stale lessons');
2006
2021
  lines.push(`🩺 _Run \`brain_doctor\` to fix: ${reasons.join(', ')}._`);
2007
2022
  }
2023
+ // ── Auto-Changelog — new lessons since last session ──────────────────────
2024
+ // Appended at the end so it never competes with the core briefing.
2025
+ // Skipped on: first session, no new lessons, or when disabled via prefs.
2026
+ try {
2027
+ const autoChangelogPref = await redis.get(`cachly:prefs:auto_changelog:${instance_id}`).catch(() => null);
2028
+ const autoChangelogEnabled = autoChangelogPref !== 'false';
2029
+ if (autoChangelogEnabled && lastSession?.ts && !isFirstSession) {
2030
+ const lastTs = new Date(lastSession.ts).getTime();
2031
+ if (!Number.isNaN(lastTs)) {
2032
+ const newLessons = lessons.filter(l => {
2033
+ const t = new Date(l.ts).getTime();
2034
+ return !Number.isNaN(t) && t > lastTs;
2035
+ });
2036
+ if (newLessons.length > 0) {
2037
+ const ago = Math.round((Date.now() - lastTs) / 60000);
2038
+ const agoStr = ago < 60 ? `${ago}m` : ago < 1440 ? `${Math.round(ago / 60)}h` : `${Math.round(ago / 1440)}d`;
2039
+ lines.push('');
2040
+ lines.push(`📋 **${newLessons.length} new lesson${newLessons.length !== 1 ? 's' : ''} since your last session** (${agoStr} ago):`);
2041
+ // Group by category
2042
+ const catMap = new Map();
2043
+ for (const l of newLessons.slice(0, 20)) {
2044
+ const cat = l.topic.includes(':') ? l.topic.split(':')[0] : 'general';
2045
+ const arr = catMap.get(cat) ?? [];
2046
+ arr.push(l);
2047
+ catMap.set(cat, arr);
2048
+ }
2049
+ for (const [cat, catLessons] of catMap) {
2050
+ lines.push(` **${cat}**`);
2051
+ for (const l of catLessons) {
2052
+ const icon = l.outcome === 'failure' ? '❌' : l.severity === 'critical' ? '🚨' : l.severity === 'major' ? '⚠️' : '✅';
2053
+ const body = (l.outcome === 'failure' ? l.what_failed : l.what_worked) ?? '';
2054
+ lines.push(` ${icon} \`${l.topic}\`${body ? ` — ${body.slice(0, 80)}` : ''}`);
2055
+ }
2056
+ }
2057
+ if (newLessons.length > 20) {
2058
+ lines.push(` … and ${newLessons.length - 20} more. Call \`brain_changelog\` for the full list.`);
2059
+ }
2060
+ lines.push(` _Disable auto-changelog: \`brain_set_pref(instance_id="${instance_id}", key="auto_changelog", value="false")\`_`);
2061
+ }
2062
+ }
2063
+ }
2064
+ }
2065
+ catch { /* changelog must never block session_start */ }
2008
2066
  return lines.join('\n');
2009
2067
  }
2010
2068
  // ── session_start_summary ─────────────────────────────────────────────────
@@ -3265,6 +3323,110 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
3265
3323
  lines.push(`---`, ``, `### What travels with you`, ``, `| Data | Model-neutral? |`, `|------|---------------|`, `| Lessons (learn_from_attempts) | ✅ Full access in all clients |`, `| Memory Crystals (memory_crystalize) | ✅ Included in every session_start |`, `| Causal Knowledge Graph (brain_predict) | ✅ Same graph, same predictions |`, `| Team lessons (team_learn, team_confirm) | ✅ Team is per-Brain, not per-model |`, `| Domain Brain Marketplace installs | ✅ Installed once, available everywhere |`, `| Session handoffs (session_handoff) | ✅ Cross-client continuity |`, ``, `---`, ``, `### One-command setup for any new client`, ``, '```bash', `npx @cachly-dev/mcp-server@latest autopilot`, '```', ``, `Autopilot detects all installed editors and writes the config for each — with your \`${instance_id}\` Brain ID pre-filled.`, ``, `---`, `_Docs: cachly.dev/docs/model-neutral • Your Brain, Any Model_`);
3266
3324
  return lines.join('\n');
3267
3325
  }
3326
+ // ── brain_changelog ───────────────────────────────────────────────────────
3327
+ // Generates a human-readable Markdown changelog of recent lessons.
3328
+ // Useful for weekly standups, sprint reviews, or async team updates.
3329
+ case 'brain_changelog': {
3330
+ const { instance_id, days = 7, max_lessons = 30, include_failures = true, } = args;
3331
+ const redis = await getConnection(instance_id);
3332
+ const cutoff = Date.now() - Number(days) * 86_400_000;
3333
+ const keys = await scanKeys(redis, 'cachly:lesson:best:*', { max: 4000, timeoutMs: 4000 });
3334
+ const recent = [];
3335
+ for (const k of keys) {
3336
+ const raw = await redis.get(k).catch(() => null);
3337
+ if (!raw)
3338
+ continue;
3339
+ const lesson = safeJsonParse(raw, {});
3340
+ const ts = lesson.ts ? new Date(lesson.ts).getTime() : 0;
3341
+ if (ts < cutoff)
3342
+ continue;
3343
+ if (!include_failures && lesson.outcome === 'failure')
3344
+ continue;
3345
+ const topic = k.replace('cachly:lesson:best:', '');
3346
+ recent.push({ ...lesson, topic });
3347
+ }
3348
+ recent.sort((a, b) => {
3349
+ const ta = a.ts ? new Date(a.ts).getTime() : 0;
3350
+ const tb = b.ts ? new Date(b.ts).getTime() : 0;
3351
+ return tb - ta;
3352
+ });
3353
+ const lessons = recent.slice(0, Number(max_lessons));
3354
+ if (lessons.length === 0) {
3355
+ return [
3356
+ `## 📋 Brain Changelog — last ${days} day${Number(days) !== 1 ? 's' : ''}`,
3357
+ ``,
3358
+ `_No new lessons learned in this window. Your Brain already knows everything it knows — add new lessons with \`learn_from_attempts\`._`,
3359
+ ].join('\n');
3360
+ }
3361
+ const groups = new Map();
3362
+ for (const l of lessons) {
3363
+ const cat = l.topic.includes(':') ? l.topic.split(':')[0] : 'general';
3364
+ const arr = groups.get(cat) ?? [];
3365
+ arr.push(l);
3366
+ groups.set(cat, arr);
3367
+ }
3368
+ const severityEmoji = (s) => s === 'critical' ? '🚨' : s === 'major' ? '⚠️' : s === 'minor' ? '💡' : '✨';
3369
+ const outcomeEmoji = (o) => o === 'failure' ? '❌' : o === 'partial' ? '🔶' : '✅';
3370
+ const dateStr = new Date().toISOString().slice(0, 10);
3371
+ const lines = [
3372
+ `## 📋 Brain Changelog — ${dateStr} (last ${days} day${Number(days) !== 1 ? 's' : ''})`,
3373
+ ``,
3374
+ `_${lessons.length} lesson${lessons.length !== 1 ? 's' : ''} learned across ${groups.size} topic area${groups.size !== 1 ? 's' : ''}_`,
3375
+ ``,
3376
+ ];
3377
+ for (const [cat, catLessons] of Array.from(groups.entries()).sort((a, b) => b[1].length - a[1].length)) {
3378
+ lines.push(`### ${cat}`);
3379
+ lines.push('');
3380
+ for (const l of catLessons) {
3381
+ const icon = l.outcome === 'failure' ? outcomeEmoji(l.outcome) : severityEmoji(l.severity);
3382
+ const body = l.outcome === 'failure' ? l.what_failed : l.what_worked;
3383
+ const meta = [];
3384
+ if (l.author)
3385
+ meta.push(`by **${l.author}**`);
3386
+ if (l.recall_count)
3387
+ meta.push(`${l.recall_count} recall${l.recall_count !== 1 ? 's' : ''}`);
3388
+ if (l.confidence !== undefined)
3389
+ meta.push(`${Math.round(l.confidence * 100)}% confidence`);
3390
+ lines.push(`- ${icon} **${l.topic}**${body ? ` — ${body}` : ''}`);
3391
+ if (meta.length)
3392
+ lines.push(` _${meta.join(' · ')}_`);
3393
+ }
3394
+ lines.push('');
3395
+ }
3396
+ lines.push('---', `_Generated from Brain \`${instance_id}\` · Share this in your standup or Slack_`);
3397
+ return lines.join('\n');
3398
+ }
3399
+ // ── brain_set_pref ────────────────────────────────────────────────────────
3400
+ case 'brain_set_pref': {
3401
+ const { instance_id, key, value, } = args;
3402
+ if (!key || !key.trim())
3403
+ throw new Error('key is required');
3404
+ const redis = await getConnection(instance_id);
3405
+ const redisKey = `cachly:prefs:${key.trim()}:${instance_id}`;
3406
+ await redis.set(redisKey, String(value));
3407
+ return `✅ Preference \`${key}\` set to \`${value}\` for this Brain instance.`;
3408
+ }
3409
+ // ── brain_get_pref ────────────────────────────────────────────────────────
3410
+ case 'brain_get_pref': {
3411
+ const { instance_id, key } = args;
3412
+ const redis = await getConnection(instance_id);
3413
+ if (key && key.trim()) {
3414
+ const val = await redis.get(`cachly:prefs:${key.trim()}:${instance_id}`).catch(() => null);
3415
+ return val === null
3416
+ ? `ℹ️ Preference \`${key}\` is not set (using default).`
3417
+ : `🔧 Preference \`${key}\` = \`${val}\``;
3418
+ }
3419
+ const prefKeys = await scanKeys(redis, `cachly:prefs:*:${instance_id}`, { max: 200, timeoutMs: 2000 });
3420
+ if (prefKeys.length === 0)
3421
+ return 'ℹ️ No preferences set for this Brain instance.';
3422
+ const vals = await redis.mget(...prefKeys);
3423
+ const lines = ['🔧 **Brain preferences:**', ''];
3424
+ for (let i = 0; i < prefKeys.length; i++) {
3425
+ const k = prefKeys[i].replace('cachly:prefs:', '').replace(`:${instance_id}`, '');
3426
+ lines.push(`- \`${k}\` = \`${vals[i] ?? '(null)'}\``);
3427
+ }
3428
+ return lines.join('\n');
3429
+ }
3268
3430
  // ── sync_file_changes ─────────────────────────────────────────────────────
3269
3431
  default:
3270
3432
  return null;