@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/dist/index.d.ts +7 -2
- package/dist/index.js +170 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/smithery.yaml +10 -5
- package/src/index.ts +174 -9
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Stopwords — filtered out during tokenization.
|
|
4
|
-
* Covers: English, German, French, Spanish, Italian, Portuguese
|
|
4
|
+
* Covers: English, German, French, Spanish, Italian, Portuguese,
|
|
5
|
+
* Chinese (Simplified + Traditional), Japanese, Korean.
|
|
5
6
|
* Keeps the index small and scores meaningful.
|
|
6
7
|
*/
|
|
7
8
|
declare const STOPWORDS: Set<string>;
|
|
8
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Tokenize text into meaningful keywords.
|
|
11
|
+
* Handles accented Latin characters and CJK (Chinese, Japanese, Korean).
|
|
12
|
+
* CJK text is split into character bigrams (the standard for indexing logographic languages).
|
|
13
|
+
*/
|
|
9
14
|
declare function tokenize(text: string): string[];
|
|
10
15
|
/**
|
|
11
16
|
* Split a multi-topic query into sub-queries.
|
package/dist/index.js
CHANGED
|
@@ -288,12 +288,14 @@ function embedProviderHint() {
|
|
|
288
288
|
// • Recency boost — newer entries rank higher (exponential decay, 7-day half-life)
|
|
289
289
|
// • Multi-query splitting — numbered lists, semicolons, conjunctions
|
|
290
290
|
// • Fuzzy matching with Levenshtein distance ≤ 2
|
|
291
|
-
// • Multilingual stopwords (EN, DE, FR, ES, IT, PT)
|
|
291
|
+
// • Multilingual stopwords (EN, DE, FR, ES, IT, PT, ZH, JA, KO)
|
|
292
|
+
// • CJK character bigram extraction — handles Chinese, Japanese, Korean
|
|
292
293
|
// • Pipeline Redis reads for performance
|
|
293
294
|
//
|
|
294
295
|
/**
|
|
295
296
|
* Stopwords — filtered out during tokenization.
|
|
296
|
-
* Covers: English, German, French, Spanish, Italian, Portuguese
|
|
297
|
+
* Covers: English, German, French, Spanish, Italian, Portuguese,
|
|
298
|
+
* Chinese (Simplified + Traditional), Japanese, Korean.
|
|
297
299
|
* Keeps the index small and scores meaningful.
|
|
298
300
|
*/
|
|
299
301
|
const STOPWORDS = new Set([
|
|
@@ -367,14 +369,61 @@ const STOPWORDS = new Set([
|
|
|
367
369
|
'eu', 'tu', 'ele', 'ela', 'nós', 'eles', 'elas', 'você', 'vocês',
|
|
368
370
|
'não', 'já', 'sim', 'bem', 'muito', 'também', 'ainda', 'sempre',
|
|
369
371
|
'todo', 'toda', 'todos', 'cada', 'outro', 'outra', 'quando', 'porque',
|
|
372
|
+
// ── Chinese (Simplified + Traditional) — high-frequency function characters ──
|
|
373
|
+
'的', '了', '是', '在', '不', '和', '我', '他', '这', '中', '大', '为',
|
|
374
|
+
'上', '个', '国', '以', '要', '就', '出', '说', '们', '有', '来', '到',
|
|
375
|
+
'时', '地', '年', '得', '着', '那', '过', '后', '还', '与', '也', '可',
|
|
376
|
+
'于', '从', '但', '而', '被', '把', '让', '使', '对', '很', '都', '一',
|
|
377
|
+
'会', '没', '人', '它', '这个', '那个', '什么', '如果', '因为', '所以',
|
|
378
|
+
'已经', '可以', '这些', '那些', '我们', '他们', '她们', '它们',
|
|
379
|
+
// ── Japanese — common hiragana particles and auxiliary verbs ──
|
|
380
|
+
'の', 'は', 'が', 'を', 'に', 'で', 'と', 'も', 'や', 'か', 'な', 'ね',
|
|
381
|
+
'よ', 'わ', 'て', 'い', 'う', 'え', 'お', 'き', 'く', 'け', 'こ', 'さ',
|
|
382
|
+
'し', 'す', 'せ', 'そ', 'た', 'ち', 'つ', 'ぬ', 'ん', 'から', 'まで',
|
|
383
|
+
'より', 'へ', 'です', 'ます', 'ない', 'ある', 'いる', 'する', 'こと',
|
|
384
|
+
'もの', 'ので', 'では', 'には', 'との', 'への', 'から', 'まで',
|
|
385
|
+
// ── Korean — common particles and auxiliary forms ──
|
|
386
|
+
'이', '가', '은', '는', '을', '를', '의', '에', '와', '과', '도',
|
|
387
|
+
'로', '에서', '한', '하', '있', '없', '것', '수', '않', '들',
|
|
388
|
+
'이다', '하다', '이고', '이며', '라고', '에게', '에서', '으로',
|
|
370
389
|
]);
|
|
371
|
-
|
|
390
|
+
// CJK Unicode ranges used for bigram extraction
|
|
391
|
+
const CJK_RE = /[\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uac00-\ud7af]/;
|
|
392
|
+
const SEGMENT_RE = /([\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uac00-\ud7af]+|[^\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uac00-\ud7af]+)/g;
|
|
393
|
+
/**
|
|
394
|
+
* Tokenize text into meaningful keywords.
|
|
395
|
+
* Handles accented Latin characters and CJK (Chinese, Japanese, Korean).
|
|
396
|
+
* CJK text is split into character bigrams (the standard for indexing logographic languages).
|
|
397
|
+
*/
|
|
372
398
|
function tokenize(text) {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
.
|
|
377
|
-
|
|
399
|
+
const tokens = [];
|
|
400
|
+
const lower = text.toLowerCase();
|
|
401
|
+
for (const [seg] of lower.matchAll(SEGMENT_RE)) {
|
|
402
|
+
if (CJK_RE.test(seg)) {
|
|
403
|
+
// CJK segment — extract overlapping character bigrams
|
|
404
|
+
for (let i = 0; i < seg.length - 1; i++) {
|
|
405
|
+
const bigram = seg[i] + seg[i + 1];
|
|
406
|
+
if (!STOPWORDS.has(seg[i]) && !STOPWORDS.has(bigram)) {
|
|
407
|
+
tokens.push(bigram);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// Trailing single character (for 1-char CJK terms)
|
|
411
|
+
if (seg.length >= 1) {
|
|
412
|
+
const last = seg[seg.length - 1];
|
|
413
|
+
if (!STOPWORDS.has(last))
|
|
414
|
+
tokens.push(last);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
// Latin / other — existing logic
|
|
419
|
+
const words = seg
|
|
420
|
+
.replace(/[^a-záàâãäåæçéèêëíìîïñóòôõöúùûüýÿßœ0-9:_\-./]/g, ' ')
|
|
421
|
+
.split(/\s+/)
|
|
422
|
+
.filter(w => w.length > 1 && !STOPWORDS.has(w));
|
|
423
|
+
tokens.push(...words);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return tokens;
|
|
378
427
|
}
|
|
379
428
|
/**
|
|
380
429
|
* Split a multi-topic query into sub-queries.
|
|
@@ -4650,6 +4699,8 @@ const EDITOR_FILES = {
|
|
|
4650
4699
|
windsurf: '.windsurf/mcp.json',
|
|
4651
4700
|
copilot: '.vscode/mcp.json',
|
|
4652
4701
|
continue: '.continue/config.json',
|
|
4702
|
+
cline: '.vscode/mcp.json', // Cline respects VS Code project MCP config
|
|
4703
|
+
zed: '.zed/settings.json', // Zed project-level context_servers
|
|
4653
4704
|
};
|
|
4654
4705
|
const CLAUDE_MD_MARKER_START = '<!-- cachly-brain-start -->';
|
|
4655
4706
|
const CLAUDE_MD_MARKER_END = '<!-- cachly-brain-end -->';
|
|
@@ -4664,6 +4715,20 @@ function buildMcpConfig(apiKey, instanceId, editor) {
|
|
|
4664
4715
|
},
|
|
4665
4716
|
}, null, 2);
|
|
4666
4717
|
}
|
|
4718
|
+
if (editor === 'zed') {
|
|
4719
|
+
return JSON.stringify({
|
|
4720
|
+
context_servers: {
|
|
4721
|
+
cachly: {
|
|
4722
|
+
command: {
|
|
4723
|
+
path: 'npx',
|
|
4724
|
+
args: ['-y', '@cachly-dev/mcp-server@latest'],
|
|
4725
|
+
env: { CACHLY_API_URL: 'https://api.cachly.dev', CACHLY_JWT: apiKey, CACHLY_BRAIN_INSTANCE_ID: instanceId },
|
|
4726
|
+
},
|
|
4727
|
+
settings: {},
|
|
4728
|
+
},
|
|
4729
|
+
},
|
|
4730
|
+
}, null, 2);
|
|
4731
|
+
}
|
|
4667
4732
|
return JSON.stringify({
|
|
4668
4733
|
mcpServers: {
|
|
4669
4734
|
cachly: {
|
|
@@ -4992,6 +5057,8 @@ if (process.argv[2] === 'setup') {
|
|
|
4992
5057
|
// ── Step 3: Detect editors ────────────────────────────────────────────────
|
|
4993
5058
|
const cwd = process.cwd();
|
|
4994
5059
|
const detected = [];
|
|
5060
|
+
const { homedir } = await import('node:os');
|
|
5061
|
+
const home = homedir();
|
|
4995
5062
|
// Claude Code: always include (CLAUDE.md is universal)
|
|
4996
5063
|
detected.push('claude');
|
|
4997
5064
|
if (existsSync(resolve(cwd, '.cursor')))
|
|
@@ -5002,7 +5069,21 @@ if (process.argv[2] === 'setup') {
|
|
|
5002
5069
|
detected.push('copilot');
|
|
5003
5070
|
if (existsSync(resolve(cwd, '.continue')))
|
|
5004
5071
|
detected.push('continue');
|
|
5005
|
-
|
|
5072
|
+
// Cline: VS Code extension — detect via globalStorage on the machine
|
|
5073
|
+
const clineGlobalDir = process.platform === 'darwin'
|
|
5074
|
+
? resolve(home, 'Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev')
|
|
5075
|
+
: resolve(home, '.vscode/extensions');
|
|
5076
|
+
if (existsSync(clineGlobalDir) && !detected.includes('copilot'))
|
|
5077
|
+
detected.push('cline');
|
|
5078
|
+
else if (existsSync(clineGlobalDir))
|
|
5079
|
+
detected.push('cline'); // both copilot + cline share .vscode/mcp.json — fine
|
|
5080
|
+
// Zed editor — detect via app data dir
|
|
5081
|
+
const zedDir = process.platform === 'darwin'
|
|
5082
|
+
? resolve(home, 'Library/Application Support/Zed')
|
|
5083
|
+
: resolve(home, '.config/zed');
|
|
5084
|
+
if (existsSync(zedDir))
|
|
5085
|
+
detected.push('zed');
|
|
5086
|
+
const editorLabel = (e) => ({ claude: 'Claude Code', cursor: 'Cursor', windsurf: 'Windsurf', copilot: 'GitHub Copilot', continue: 'Continue.dev', cline: 'Cline (VSCode)', zed: 'Zed' })[e] ?? e;
|
|
5006
5087
|
console.log(`Step 3: Detected editors: ${detected.map(editorLabel).join(', ')}`);
|
|
5007
5088
|
const editorsRaw = await ask(` Configure for which? [all / ${detected.join('/')}] (Enter = all): `);
|
|
5008
5089
|
const editorsToSetup = !editorsRaw || editorsRaw.toLowerCase() === 'all'
|
|
@@ -5043,6 +5124,86 @@ if (process.argv[2] === 'setup') {
|
|
|
5043
5124
|
rl.close();
|
|
5044
5125
|
console.log(`\n🧠 Done! Restart your editor — the \`cachly\` MCP tools will appear.`);
|
|
5045
5126
|
console.log(` Your AI now has persistent memory across every session.\n`);
|
|
5127
|
+
// ── Step 7: Show first session_start preview (Aha moment) ────────────────
|
|
5128
|
+
// Fetch brain health from the API to show the user what their agent will see.
|
|
5129
|
+
process.stdout.write('\n⏳ Fetching your Brain health preview...');
|
|
5130
|
+
try {
|
|
5131
|
+
const brainRes = await fetch(`${API_URL}/api/v1/instances/${instance.id}/brain/stats`, {
|
|
5132
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
5133
|
+
signal: AbortSignal.timeout(6000),
|
|
5134
|
+
});
|
|
5135
|
+
if (brainRes.ok) {
|
|
5136
|
+
const brainData = await brainRes.json();
|
|
5137
|
+
const lessons = brainData.lesson_count ?? 0;
|
|
5138
|
+
const contexts = brainData.context_count ?? 0;
|
|
5139
|
+
const score = brainData.quality_score ?? 0;
|
|
5140
|
+
const level = lessons === 0 ? 'Intern 🌱' :
|
|
5141
|
+
lessons < 10 ? 'Junior Dev 🔧' :
|
|
5142
|
+
lessons < 30 ? 'Mid Dev ⚡' :
|
|
5143
|
+
lessons < 60 ? 'Senior Dev 🧠' :
|
|
5144
|
+
lessons < 100 ? 'Staff Eng 🚀' : 'Principal Eng 🏆';
|
|
5145
|
+
console.log(' ✓\n');
|
|
5146
|
+
console.log('┌──────────────────────────────────────────────────────┐');
|
|
5147
|
+
console.log(`│ 🧠 Brain Health Report │`);
|
|
5148
|
+
console.log('├──────────────────────────────────────────────────────┤');
|
|
5149
|
+
console.log(`│ Lessons stored : ${String(lessons).padEnd(6)} │ Level: ${level.padEnd(20)}│`);
|
|
5150
|
+
console.log(`│ Context entries : ${String(contexts).padEnd(6)} │ Quality score: ${String(Math.round(score * 100)).padEnd(3)}% │`);
|
|
5151
|
+
if (lessons === 0) {
|
|
5152
|
+
console.log('├──────────────────────────────────────────────────────┤');
|
|
5153
|
+
console.log('│ Your Brain is empty and ready to learn. │');
|
|
5154
|
+
console.log('│ After your first coding session it will contain: │');
|
|
5155
|
+
console.log('│ • Lessons from every bug you fixed │');
|
|
5156
|
+
console.log('│ • Your project\'s indexed files │');
|
|
5157
|
+
console.log('│ • A session summary for next time │');
|
|
5158
|
+
}
|
|
5159
|
+
console.log('└──────────────────────────────────────────────────────┘');
|
|
5160
|
+
}
|
|
5161
|
+
else {
|
|
5162
|
+
console.log(' (skipped)');
|
|
5163
|
+
}
|
|
5164
|
+
}
|
|
5165
|
+
catch {
|
|
5166
|
+
console.log(' (skipped)');
|
|
5167
|
+
}
|
|
5168
|
+
console.log(`\n Your AI will call session_start automatically when it uses the first tool.`);
|
|
5169
|
+
console.log(` To see your Brain at any time: https://cachly.dev/instances/${instance.id}\n`);
|
|
5170
|
+
process.exit(0);
|
|
5171
|
+
}
|
|
5172
|
+
// ── CLI: cachly index <dir> ───────────────────────────────────────────────────
|
|
5173
|
+
// Usage: npx @cachly-dev/mcp-server@latest index [./path/to/project]
|
|
5174
|
+
// Indexes the project directory into the Brain — perfect for CI/CD cron jobs.
|
|
5175
|
+
if (process.argv[2] === 'index') {
|
|
5176
|
+
const { resolve } = await import('node:path');
|
|
5177
|
+
const argv = process.argv.slice(3);
|
|
5178
|
+
const flag = (name) => { const i = argv.indexOf(`--${name}`); return i !== -1 ? argv[i + 1] : undefined; };
|
|
5179
|
+
const dir = resolve(flag('dir') ?? argv.find(a => !a.startsWith('--')) ?? '.');
|
|
5180
|
+
const instanceId = flag('instance-id') ?? process.env.CACHLY_BRAIN_INSTANCE_ID;
|
|
5181
|
+
const maxFiles = parseInt(flag('max-files') ?? '500', 10);
|
|
5182
|
+
const namespace = flag('namespace') ?? 'cachly:sem:code';
|
|
5183
|
+
if (!instanceId || !JWT) {
|
|
5184
|
+
console.error('\n❌ CACHLY_BRAIN_INSTANCE_ID and CACHLY_JWT must be set\n');
|
|
5185
|
+
console.error(' export CACHLY_BRAIN_INSTANCE_ID=<uuid>');
|
|
5186
|
+
console.error(' export CACHLY_JWT=<cky_live_...>');
|
|
5187
|
+
console.error(' npx @cachly-dev/mcp-server@latest index ./my-project\n');
|
|
5188
|
+
process.exit(1);
|
|
5189
|
+
}
|
|
5190
|
+
console.log(`\n📂 Indexing: ${dir}`);
|
|
5191
|
+
console.log(` Instance: ${instanceId.slice(0, 8)}… Max files: ${maxFiles}\n`);
|
|
5192
|
+
try {
|
|
5193
|
+
const result = await handleTool('index_project', {
|
|
5194
|
+
instance_id: instanceId,
|
|
5195
|
+
dir,
|
|
5196
|
+
max_files: maxFiles,
|
|
5197
|
+
ttl: 86400 * 7,
|
|
5198
|
+
namespace,
|
|
5199
|
+
});
|
|
5200
|
+
console.log(result);
|
|
5201
|
+
console.log('\n✅ Indexing complete.\n');
|
|
5202
|
+
}
|
|
5203
|
+
catch (err) {
|
|
5204
|
+
console.error(`\n❌ Indexing failed: ${err.message}\n`);
|
|
5205
|
+
process.exit(1);
|
|
5206
|
+
}
|
|
5046
5207
|
process.exit(0);
|
|
5047
5208
|
}
|
|
5048
5209
|
// ── Start ─────────────────────────────────────────────────────────────────────
|