cf-memory-mcp 3.8.4 โ†’ 3.8.6

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.
@@ -25,20 +25,51 @@ const { URL } = require('url');
25
25
  const os = require('os');
26
26
  const process = require('process');
27
27
 
28
- const API_URL = process.env.CF_MEMORY_API_URL || 'https://cf-memory-mcp-simplified.johnlam90.workers.dev';
28
+ // Resolve API URL, overriding the dead workers.dev URL if it's still in the
29
+ // shell environment from before the memcp.ai migration.
30
+ function _resolveApiUrl() {
31
+ const raw = process.env.CF_MEMORY_API_URL || process.env.CF_MEMORY_BASE_URL;
32
+ if (!raw || raw.includes('workers.dev')) {
33
+ return 'https://memcp.ai';
34
+ }
35
+ return raw;
36
+ }
37
+ const API_URL = _resolveApiUrl();
29
38
  const API_KEY = process.env.CF_MEMORY_API_KEY;
30
39
  const CACHE_DIR = path.join(os.homedir(), '.cache', 'cf-memory-indexer');
31
40
 
32
41
  // File patterns to include/exclude
42
+ // Mirrors src-simplified/utils/index.ts DEFAULT_INCLUDE_EXTENSIONS (110+ languages)
33
43
  const DEFAULT_INCLUDE = [
34
- '**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx',
35
- '**/*.py', '**/*.go', '**/*.rs', '**/*.java',
36
- '**/*.md', '**/*.json'
44
+ // Mainstream
45
+ '**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.mjs', '**/*.cjs',
46
+ '**/*.py', '**/*.rb', '**/*.go', '**/*.rs', '**/*.java', '**/*.kt', '**/*.scala',
47
+ '**/*.cs', '**/*.cpp', '**/*.c', '**/*.h', '**/*.hpp', '**/*.swift', '**/*.php',
48
+ // Functional & systems
49
+ '**/*.lua', '**/*.ex', '**/*.exs', '**/*.hs', '**/*.lhs',
50
+ '**/*.clj', '**/*.cljs', '**/*.cljc', '**/*.jl', '**/*.elm', '**/*.purs',
51
+ '**/*.dart', '**/*.zig', '**/*.nim', '**/*.cr', '**/*.erl', '**/*.hrl',
52
+ '**/*.ml', '**/*.fs', '**/*.fsi',
53
+ // Web & data
54
+ '**/*.html', '**/*.css', '**/*.scss', '**/*.vue', '**/*.svelte', '**/*.astro',
55
+ '**/*.json', '**/*.yaml', '**/*.yml', '**/*.toml',
56
+ '**/*.md', '**/*.mdx',
57
+ // Infra & build
58
+ '**/*.tf', '**/*.hcl', '**/*.sql', '**/*.sh', '**/*.bash',
59
+ '**/*.proto', '**/*.graphql', '**/*.gql',
60
+ // Shaders & GPU
61
+ '**/*.glsl', '**/*.hlsl', '**/*.wgsl', '**/*.metal', '**/*.cu', '**/*.cuh',
37
62
  ];
63
+
38
64
  const DEFAULT_EXCLUDE = [
39
65
  '**/node_modules/**', '**/.git/**', '**/dist/**',
40
- '**/build/**', '**/.next/**', '**/coverage/**',
41
- '**/*.min.js', '**/*.d.ts'
66
+ '**/build/**', '**/out/**', '**/.next/**', '**/.nuxt/**',
67
+ '**/coverage/**', '**/__pycache__/**', '**/venv/**', '**/.venv/**',
68
+ '**/.wrangler/**', '**/.turbo/**', '**/.cache/**',
69
+ '**/.history/**', '**/.vscode/**', '**/.idea/**', '**/.vs/**',
70
+ '**/.augment/**', '**/.kiro/**', '**/.intent/**', '**/.husky/**', '**/.claude/**',
71
+ '**/*.min.js', '**/*.min.css', '**/*.map', '**/*.d.ts',
72
+ '**/package-lock.json', '**/yarn.lock', '**/pnpm-lock.yaml',
42
73
  ];
43
74
 
44
75
  class IncrementalIndexer {
@@ -205,6 +236,16 @@ class IncrementalIndexer {
205
236
  console.log(`๐Ÿ“ค Uploading ${changedFiles.length} files...`);
206
237
  await this.uploadFiles(changedFiles);
207
238
  }
239
+
240
+ if (this.cache.projectId && deletedFiles.length > 0) {
241
+ console.log(`๐Ÿงน Cleaning up ${deletedFiles.length} deleted files...`);
242
+ await this.cleanupDeletedFiles(deletedFiles);
243
+ }
244
+
245
+ if (this.cache.projectId && (changedFiles.length > 0 || deletedFiles.length > 0)) {
246
+ console.log('๐Ÿ“Œ Finalizing project stats...');
247
+ await this.finalizeProject();
248
+ }
208
249
 
209
250
  // Update cache
210
251
  for (const file of changedFiles) {
@@ -238,59 +279,101 @@ class IncrementalIndexer {
238
279
  }
239
280
 
240
281
  async uploadBatch(files) {
241
- return new Promise((resolve, reject) => {
242
- const payload = JSON.stringify({
282
+ if (!this.cache.projectId) {
283
+ const initResult = await this.callMcpTool('index_project', {
243
284
  project_path: this.projectPath,
244
- project_name: this.projectName,
245
- files: files.map(f => ({
246
- path: f.path,
247
- content: f.content
248
- }))
285
+ project_name: this.projectName
249
286
  });
250
-
251
- const url = new URL(`${API_URL}/mcp/tools/index_project`);
287
+ const initContent = this.parseToolTextResult(initResult);
288
+
289
+ if (!initContent.project_id) {
290
+ throw new Error('Project initialization did not return project_id');
291
+ }
292
+
293
+ this.cache.projectId = initContent.project_id;
294
+ this.saveCache();
295
+ }
296
+
297
+ return this.postJson(`/api/projects/${encodeURIComponent(this.cache.projectId)}/files/batch`, {
298
+ files: files.map(f => ({
299
+ file_path: f.path,
300
+ content: f.content
301
+ }))
302
+ });
303
+ }
304
+
305
+ async callMcpTool(name, args) {
306
+ return this.postJson('/mcp', {
307
+ jsonrpc: '2.0',
308
+ id: `idx-${Date.now()}`,
309
+ method: 'tools/call',
310
+ params: {
311
+ name,
312
+ arguments: args
313
+ }
314
+ });
315
+ }
316
+
317
+ async cleanupDeletedFiles(filePaths) {
318
+ return this.postJson(`/api/projects/${encodeURIComponent(this.cache.projectId)}/files/cleanup`, {
319
+ file_paths: filePaths
320
+ });
321
+ }
322
+
323
+ async finalizeProject() {
324
+ return this.postJson(`/api/projects/${encodeURIComponent(this.cache.projectId)}/finalize`, {});
325
+ }
326
+
327
+ parseToolTextResult(result) {
328
+ const text = result?.result?.content?.[0]?.text;
329
+ if (!text) {
330
+ throw new Error('Tool response did not contain text content');
331
+ }
332
+ return JSON.parse(text);
333
+ }
334
+
335
+ async postJson(route, payload) {
336
+ return new Promise((resolve, reject) => {
337
+ const body = JSON.stringify(payload);
338
+ const url = new URL(`${API_URL}${route}`);
252
339
  const options = {
253
340
  hostname: url.hostname,
254
- path: url.pathname,
341
+ port: url.port || 443,
342
+ path: url.pathname + url.search,
255
343
  method: 'POST',
256
344
  headers: {
257
345
  'Content-Type': 'application/json',
258
346
  'Authorization': `Bearer ${API_KEY}`,
259
- 'Content-Length': Buffer.byteLength(payload)
347
+ 'X-API-Key': API_KEY,
348
+ 'Content-Length': Buffer.byteLength(body)
260
349
  },
261
350
  timeout: 120000
262
351
  };
263
-
352
+
264
353
  const req = https.request(options, (res) => {
265
354
  let data = '';
266
355
  res.on('data', chunk => data += chunk);
267
356
  res.on('end', () => {
268
- if (res.statusCode >= 200 && res.statusCode < 300) {
269
- try {
270
- const result = JSON.parse(data);
271
- if (result.content && result.content[0]) {
272
- const content = JSON.parse(result.content[0].text);
273
- if (content.project_id) {
274
- this.cache.projectId = content.project_id;
275
- }
276
- }
277
- resolve(result);
278
- } catch (err) {
279
- reject(new Error(`Invalid response: ${err.message}`));
357
+ try {
358
+ const parsed = JSON.parse(data || '{}');
359
+ if (res.statusCode >= 200 && res.statusCode < 300) {
360
+ resolve(parsed);
361
+ return;
280
362
  }
281
- } else {
282
363
  reject(new Error(`HTTP ${res.statusCode}: ${data}`));
364
+ } catch (err) {
365
+ reject(new Error(`Invalid response: ${err.message}`));
283
366
  }
284
367
  });
285
368
  });
286
-
369
+
287
370
  req.on('error', reject);
288
371
  req.on('timeout', () => {
289
372
  req.destroy();
290
373
  reject(new Error('Request timeout'));
291
374
  });
292
-
293
- req.write(payload);
375
+
376
+ req.write(body);
294
377
  req.end();
295
378
  });
296
379
  }
@@ -302,49 +385,41 @@ class IncrementalIndexer {
302
385
  // Initial index
303
386
  await this.index();
304
387
 
305
- // Watch for changes
306
- const chokidar = await this.loadChokidar();
307
-
308
- const watcher = chokidar.watch(this.projectPath, {
309
- ignored: this.exclude,
310
- persistent: true,
311
- ignoreInitial: true
312
- });
313
-
314
- let debounceTimer = null;
315
-
316
- const handleChange = async () => {
317
- if (debounceTimer) clearTimeout(debounceTimer);
318
- debounceTimer = setTimeout(async () => {
319
- console.log('\n๐Ÿ”„ Changes detected, re-indexing...');
320
- await this.index();
321
- console.log('');
322
- }, 1000);
388
+ // Use Node's built-in fs.watch (no external deps needed)
389
+ const fs = require('fs');
390
+ const watchDir = (dir) => {
391
+ let watcher;
392
+ try {
393
+ watcher = fs.watch(dir, { recursive: true }, (eventType, filename) => {
394
+ if (filename) {
395
+ this.handleFileChange(dir, filename);
396
+ }
397
+ });
398
+ } catch (err) {
399
+ // Ignore errors for now
400
+ }
401
+ return watcher;
323
402
  };
324
403
 
325
- watcher
326
- .on('add', path => handleChange())
327
- .on('change', path => handleChange())
328
- .on('unlink', path => handleChange());
404
+ const watcher = watchDir(this.projectPath);
329
405
 
330
406
  // Keep process alive
331
407
  process.on('SIGINT', () => {
332
408
  console.log('\n๐Ÿ‘‹ Stopping watcher...');
333
- watcher.close();
409
+ if (watcher) watcher.close();
334
410
  process.exit(0);
335
411
  });
336
412
  }
337
413
 
338
- async loadChokidar() {
339
- try {
340
- return require('chokidar');
341
- } catch (err) {
342
- console.log('Installing chokidar for file watching...');
343
- const { execSync } = require('child_process');
344
- execSync('npm install chokidar --save-dev', { cwd: __dirname, stdio: 'inherit' });
345
- return require('chokidar');
346
- }
414
+ handleFileChange(baseDir, filename) {
415
+ // Debounce rapid changes
416
+ if (this._debounceTimer) clearTimeout(this._debounceTimer);
417
+ this._debounceTimer = setTimeout(async () => {
418
+ console.log(`\n๐Ÿ”„ File changed: ${filename}`);
419
+ await this.index();
420
+ }, 1000);
347
421
  }
422
+
348
423
  }
349
424
 
350
425
  // CLI
@@ -358,15 +433,20 @@ async function main() {
358
433
 
359
434
  // Detect command from how the script was called (cf-memory-index vs cf-memory-watch)
360
435
  const scriptName = path.basename(process.argv[1]);
361
- let command = args[0];
362
- let projectPath = args[1] || '.';
436
+ let command = 'index';
437
+ let projectPath = '.';
363
438
 
364
- if (scriptName.includes('watch')) {
439
+ if (args[0] === 'watch' || args[0] === 'index') {
440
+ command = args[0];
441
+ projectPath = args[1] || '.';
442
+ } else if (scriptName.includes('watch')) {
365
443
  command = 'watch';
366
444
  projectPath = args[0] || '.';
367
445
  } else if (scriptName.includes('index')) {
368
446
  command = 'index';
369
447
  projectPath = args[0] || '.';
448
+ } else {
449
+ projectPath = args[0] || '.';
370
450
  }
371
451
 
372
452
  const options = {