@cachly-dev/mcp-server 0.5.34 → 0.5.35

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": "@cachly-dev/mcp-server",
3
- "version": "0.5.34",
3
+ "version": "0.5.35",
4
4
  "mcpName": "io.github.cachly-dev/mcp-server",
5
5
  "description": "AI memory brain for Claude Code, Cursor, Copilot & Windsurf — persistent sessions, lessons learned, semantic search, team brain. 51 MCP tools.",
6
6
  "type": "module",
package/smithery.yaml CHANGED
@@ -5,16 +5,21 @@ startCommand:
5
5
  properties:
6
6
  CACHLY_JWT:
7
7
  type: string
8
- title: "API Key (JWT)"
9
- description: "Your Cachly API key from cachly.dev/dashboard → API Keys"
8
+ title: "API Key"
9
+ description: "Your Cachly API key. Get it free at cachly.dev/setup-ai → Dashboard → API Keys. Looks like: cky_live_..."
10
+ examples:
11
+ - "cky_live_abc123..."
10
12
  CACHLY_BRAIN_INSTANCE_ID:
11
13
  type: string
12
14
  title: "Brain Instance ID"
13
- description: "UUID of your Cachly Brain instance (e.g. 9d4077aa-bfa2-468b-89cd-0a8d8f3ec483)"
15
+ description: "UUID of your Cachly Brain instance. Find it at cachly.dev/instances — click your instance and copy the ID from the URL or details panel. Looks like: 9d4077aa-bfa2-468b-89cd-0a8d8f3ec483"
16
+ pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
17
+ examples:
18
+ - "9d4077aa-bfa2-468b-89cd-0a8d8f3ec483"
14
19
  CACHLY_API_URL:
15
20
  type: string
16
- title: "API URL"
17
- description: "Cachly API endpoint — leave as default"
21
+ title: "API URL (optional)"
22
+ description: "Cachly API endpoint — leave as default unless you self-host"
18
23
  default: "https://api.cachly.dev"
19
24
  required:
20
25
  - CACHLY_JWT
package/src/index.ts CHANGED
@@ -301,13 +301,15 @@ function embedProviderHint(): string {
301
301
  // • Recency boost — newer entries rank higher (exponential decay, 7-day half-life)
302
302
  // • Multi-query splitting — numbered lists, semicolons, conjunctions
303
303
  // • Fuzzy matching with Levenshtein distance ≤ 2
304
- // • Multilingual stopwords (EN, DE, FR, ES, IT, PT)
304
+ // • Multilingual stopwords (EN, DE, FR, ES, IT, PT, ZH, JA, KO)
305
+ // • CJK character bigram extraction — handles Chinese, Japanese, Korean
305
306
  // • Pipeline Redis reads for performance
306
307
  //
307
308
 
308
309
  /**
309
310
  * Stopwords — filtered out during tokenization.
310
- * Covers: English, German, French, Spanish, Italian, Portuguese.
311
+ * Covers: English, German, French, Spanish, Italian, Portuguese,
312
+ * Chinese (Simplified + Traditional), Japanese, Korean.
311
313
  * Keeps the index small and scores meaningful.
312
314
  */
313
315
  const STOPWORDS = new Set([
@@ -381,15 +383,63 @@ const STOPWORDS = new Set([
381
383
  'eu', 'tu', 'ele', 'ela', 'nós', 'eles', 'elas', 'você', 'vocês',
382
384
  'não', 'já', 'sim', 'bem', 'muito', 'também', 'ainda', 'sempre',
383
385
  'todo', 'toda', 'todos', 'cada', 'outro', 'outra', 'quando', 'porque',
386
+ // ── Chinese (Simplified + Traditional) — high-frequency function characters ──
387
+ '的', '了', '是', '在', '不', '和', '我', '他', '这', '中', '大', '为',
388
+ '上', '个', '国', '以', '要', '就', '出', '说', '们', '有', '来', '到',
389
+ '时', '地', '年', '得', '着', '那', '过', '后', '还', '与', '也', '可',
390
+ '于', '从', '但', '而', '被', '把', '让', '使', '对', '很', '都', '一',
391
+ '会', '没', '人', '它', '这个', '那个', '什么', '如果', '因为', '所以',
392
+ '已经', '可以', '这些', '那些', '我们', '他们', '她们', '它们',
393
+ // ── Japanese — common hiragana particles and auxiliary verbs ──
394
+ 'の', 'は', 'が', 'を', 'に', 'で', 'と', 'も', 'や', 'か', 'な', 'ね',
395
+ 'よ', 'わ', 'て', 'い', 'う', 'え', 'お', 'き', 'く', 'け', 'こ', 'さ',
396
+ 'し', 'す', 'せ', 'そ', 'た', 'ち', 'つ', 'ぬ', 'ん', 'から', 'まで',
397
+ 'より', 'へ', 'です', 'ます', 'ない', 'ある', 'いる', 'する', 'こと',
398
+ 'もの', 'ので', 'では', 'には', 'との', 'への', 'から', 'まで',
399
+ // ── Korean — common particles and auxiliary forms ──
400
+ '이', '가', '은', '는', '을', '를', '의', '에', '와', '과', '도',
401
+ '로', '에서', '한', '하', '있', '없', '것', '수', '않', '들',
402
+ '이다', '하다', '이고', '이며', '라고', '에게', '에서', '으로',
384
403
  ]);
385
404
 
386
- /** Tokenize text into meaningful keywords. Handles unicode (accented chars). */
405
+ // CJK Unicode ranges used for bigram extraction
406
+ const CJK_RE = /[\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uac00-\ud7af]/;
407
+ const SEGMENT_RE = /([\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uac00-\ud7af]+|[^\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uac00-\ud7af]+)/g;
408
+
409
+ /**
410
+ * Tokenize text into meaningful keywords.
411
+ * Handles accented Latin characters and CJK (Chinese, Japanese, Korean).
412
+ * CJK text is split into character bigrams (the standard for indexing logographic languages).
413
+ */
387
414
  function tokenize(text: string): string[] {
388
- return text
389
- .toLowerCase()
390
- .replace(/[^a-záàâãäåæçéèêëíìîïñóòôõöúùûüýÿßœ0-9:_\-./]/g, ' ')
391
- .split(/\s+/)
392
- .filter(w => w.length > 1 && !STOPWORDS.has(w));
415
+ const tokens: string[] = [];
416
+ const lower = text.toLowerCase();
417
+
418
+ for (const [seg] of lower.matchAll(SEGMENT_RE)) {
419
+ if (CJK_RE.test(seg)) {
420
+ // CJK segment — extract overlapping character bigrams
421
+ for (let i = 0; i < seg.length - 1; i++) {
422
+ const bigram = seg[i] + seg[i + 1];
423
+ if (!STOPWORDS.has(seg[i]) && !STOPWORDS.has(bigram)) {
424
+ tokens.push(bigram);
425
+ }
426
+ }
427
+ // Trailing single character (for 1-char CJK terms)
428
+ if (seg.length >= 1) {
429
+ const last = seg[seg.length - 1];
430
+ if (!STOPWORDS.has(last)) tokens.push(last);
431
+ }
432
+ } else {
433
+ // Latin / other — existing logic
434
+ const words = seg
435
+ .replace(/[^a-záàâãäåæçéèêëíìîïñóòôõöúùûüýÿßœ0-9:_\-./]/g, ' ')
436
+ .split(/\s+/)
437
+ .filter(w => w.length > 1 && !STOPWORDS.has(w));
438
+ tokens.push(...words);
439
+ }
440
+ }
441
+
442
+ return tokens;
393
443
  }
394
444
 
395
445
  /**
@@ -5208,6 +5258,8 @@ const EDITOR_FILES: Record<string, string> = {
5208
5258
  windsurf: '.windsurf/mcp.json',
5209
5259
  copilot: '.vscode/mcp.json',
5210
5260
  continue: '.continue/config.json',
5261
+ cline: '.vscode/mcp.json', // Cline respects VS Code project MCP config
5262
+ zed: '.zed/settings.json', // Zed project-level context_servers
5211
5263
  };
5212
5264
 
5213
5265
  const CLAUDE_MD_MARKER_START = '<!-- cachly-brain-start -->';
@@ -5224,6 +5276,20 @@ function buildMcpConfig(apiKey: string, instanceId: string, editor: string): str
5224
5276
  },
5225
5277
  }, null, 2);
5226
5278
  }
5279
+ if (editor === 'zed') {
5280
+ return JSON.stringify({
5281
+ context_servers: {
5282
+ cachly: {
5283
+ command: {
5284
+ path: 'npx',
5285
+ args: ['-y', '@cachly-dev/mcp-server@latest'],
5286
+ env: { CACHLY_API_URL: 'https://api.cachly.dev', CACHLY_JWT: apiKey, CACHLY_BRAIN_INSTANCE_ID: instanceId },
5287
+ },
5288
+ settings: {},
5289
+ },
5290
+ },
5291
+ }, null, 2);
5292
+ }
5227
5293
  return JSON.stringify({
5228
5294
  mcpServers: {
5229
5295
  cachly: {
@@ -5556,15 +5622,28 @@ if (process.argv[2] === 'setup') {
5556
5622
  // ── Step 3: Detect editors ────────────────────────────────────────────────
5557
5623
  const cwd = process.cwd();
5558
5624
  const detected: string[] = [];
5625
+ const { homedir } = await import('node:os');
5626
+ const home = homedir();
5559
5627
  // Claude Code: always include (CLAUDE.md is universal)
5560
5628
  detected.push('claude');
5561
5629
  if (existsSync(resolve(cwd, '.cursor'))) detected.push('cursor');
5562
5630
  if (existsSync(resolve(cwd, '.windsurf'))) detected.push('windsurf');
5563
5631
  if (existsSync(resolve(cwd, '.vscode'))) detected.push('copilot');
5564
5632
  if (existsSync(resolve(cwd, '.continue'))) detected.push('continue');
5633
+ // Cline: VS Code extension — detect via globalStorage on the machine
5634
+ const clineGlobalDir = process.platform === 'darwin'
5635
+ ? resolve(home, 'Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev')
5636
+ : resolve(home, '.vscode/extensions');
5637
+ if (existsSync(clineGlobalDir) && !detected.includes('copilot')) detected.push('cline');
5638
+ else if (existsSync(clineGlobalDir)) detected.push('cline'); // both copilot + cline share .vscode/mcp.json — fine
5639
+ // Zed editor — detect via app data dir
5640
+ const zedDir = process.platform === 'darwin'
5641
+ ? resolve(home, 'Library/Application Support/Zed')
5642
+ : resolve(home, '.config/zed');
5643
+ if (existsSync(zedDir)) detected.push('zed');
5565
5644
 
5566
5645
  const editorLabel = (e: string) =>
5567
- ({ claude: 'Claude Code', cursor: 'Cursor', windsurf: 'Windsurf', copilot: 'GitHub Copilot', continue: 'Continue.dev' })[e] ?? e;
5646
+ ({ claude: 'Claude Code', cursor: 'Cursor', windsurf: 'Windsurf', copilot: 'GitHub Copilot', continue: 'Continue.dev', cline: 'Cline (VSCode)', zed: 'Zed' })[e] ?? e;
5568
5647
 
5569
5648
  console.log(`Step 3: Detected editors: ${detected.map(editorLabel).join(', ')}`);
5570
5649
  const editorsRaw = await ask(` Configure for which? [all / ${detected.join('/')}] (Enter = all): `);
@@ -5604,6 +5683,92 @@ if (process.argv[2] === 'setup') {
5604
5683
  rl.close();
5605
5684
  console.log(`\n🧠 Done! Restart your editor — the \`cachly\` MCP tools will appear.`);
5606
5685
  console.log(` Your AI now has persistent memory across every session.\n`);
5686
+
5687
+ // ── Step 7: Show first session_start preview (Aha moment) ────────────────
5688
+ // Fetch brain health from the API to show the user what their agent will see.
5689
+ process.stdout.write('\n⏳ Fetching your Brain health preview...');
5690
+ try {
5691
+ const brainRes = await fetch(`${API_URL}/api/v1/instances/${instance.id}/brain/stats`, {
5692
+ headers: { Authorization: `Bearer ${token}` },
5693
+ signal: AbortSignal.timeout(6000),
5694
+ });
5695
+ if (brainRes.ok) {
5696
+ const brainData = await brainRes.json() as {
5697
+ lesson_count?: number; context_count?: number;
5698
+ open_failures?: number; quality_score?: number;
5699
+ };
5700
+ const lessons = brainData.lesson_count ?? 0;
5701
+ const contexts = brainData.context_count ?? 0;
5702
+ const score = brainData.quality_score ?? 0;
5703
+ const level = lessons === 0 ? 'Intern 🌱' :
5704
+ lessons < 10 ? 'Junior Dev 🔧' :
5705
+ lessons < 30 ? 'Mid Dev ⚡' :
5706
+ lessons < 60 ? 'Senior Dev 🧠' :
5707
+ lessons < 100 ? 'Staff Eng 🚀' : 'Principal Eng 🏆';
5708
+ console.log(' ✓\n');
5709
+ console.log('┌──────────────────────────────────────────────────────┐');
5710
+ console.log(`│ 🧠 Brain Health Report │`);
5711
+ console.log('├──────────────────────────────────────────────────────┤');
5712
+ console.log(`│ Lessons stored : ${String(lessons).padEnd(6)} │ Level: ${level.padEnd(20)}│`);
5713
+ console.log(`│ Context entries : ${String(contexts).padEnd(6)} │ Quality score: ${String(Math.round(score * 100)).padEnd(3)}% │`);
5714
+ if (lessons === 0) {
5715
+ console.log('├──────────────────────────────────────────────────────┤');
5716
+ console.log('│ Your Brain is empty and ready to learn. │');
5717
+ console.log('│ After your first coding session it will contain: │');
5718
+ console.log('│ • Lessons from every bug you fixed │');
5719
+ console.log('│ • Your project\'s indexed files │');
5720
+ console.log('│ • A session summary for next time │');
5721
+ }
5722
+ console.log('└──────────────────────────────────────────────────────┘');
5723
+ } else {
5724
+ console.log(' (skipped)');
5725
+ }
5726
+ } catch { console.log(' (skipped)'); }
5727
+
5728
+ console.log(`\n Your AI will call session_start automatically when it uses the first tool.`);
5729
+ console.log(` To see your Brain at any time: https://cachly.dev/instances/${instance.id}\n`);
5730
+ process.exit(0);
5731
+ }
5732
+
5733
+ // ── CLI: cachly index <dir> ───────────────────────────────────────────────────
5734
+ // Usage: npx @cachly-dev/mcp-server@latest index [./path/to/project]
5735
+ // Indexes the project directory into the Brain — perfect for CI/CD cron jobs.
5736
+
5737
+ if (process.argv[2] === 'index') {
5738
+ const { resolve } = await import('node:path');
5739
+ const argv = process.argv.slice(3);
5740
+ const flag = (name: string) => { const i = argv.indexOf(`--${name}`); return i !== -1 ? argv[i + 1] : undefined; };
5741
+
5742
+ const dir = resolve(flag('dir') ?? argv.find(a => !a.startsWith('--')) ?? '.');
5743
+ const instanceId = flag('instance-id') ?? process.env.CACHLY_BRAIN_INSTANCE_ID;
5744
+ const maxFiles = parseInt(flag('max-files') ?? '500', 10);
5745
+ const namespace = flag('namespace') ?? 'cachly:sem:code';
5746
+
5747
+ if (!instanceId || !JWT) {
5748
+ console.error('\n❌ CACHLY_BRAIN_INSTANCE_ID and CACHLY_JWT must be set\n');
5749
+ console.error(' export CACHLY_BRAIN_INSTANCE_ID=<uuid>');
5750
+ console.error(' export CACHLY_JWT=<cky_live_...>');
5751
+ console.error(' npx @cachly-dev/mcp-server@latest index ./my-project\n');
5752
+ process.exit(1);
5753
+ }
5754
+
5755
+ console.log(`\n📂 Indexing: ${dir}`);
5756
+ console.log(` Instance: ${instanceId.slice(0, 8)}… Max files: ${maxFiles}\n`);
5757
+
5758
+ try {
5759
+ const result = await handleTool('index_project', {
5760
+ instance_id: instanceId,
5761
+ dir,
5762
+ max_files: maxFiles,
5763
+ ttl: 86400 * 7,
5764
+ namespace,
5765
+ });
5766
+ console.log(result);
5767
+ console.log('\n✅ Indexing complete.\n');
5768
+ } catch (err) {
5769
+ console.error(`\n❌ Indexing failed: ${(err as Error).message}\n`);
5770
+ process.exit(1);
5771
+ }
5607
5772
  process.exit(0);
5608
5773
  }
5609
5774