@contextmirror/claude-memory 0.4.2 → 0.4.4
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/LICENSE +21 -0
- package/README.md +12 -1
- package/dist/__tests__/codeSearch.test.d.ts +1 -0
- package/dist/__tests__/codeSearch.test.js +65 -0
- package/dist/__tests__/license.test.d.ts +1 -0
- package/dist/__tests__/license.test.js +28 -0
- package/dist/__tests__/stalenessDetector.test.d.ts +1 -0
- package/dist/__tests__/stalenessDetector.test.js +60 -0
- package/dist/__tests__/updateChecker.test.d.ts +1 -0
- package/dist/__tests__/updateChecker.test.js +43 -0
- package/dist/cli.js +9 -2
- package/dist/constants.d.ts +25 -0
- package/dist/constants.js +53 -0
- package/dist/license/index.d.ts +7 -3
- package/dist/license/index.js +73 -15
- package/dist/mcp/server.js +25 -18
- package/dist/utils/codeSearch.d.ts +32 -0
- package/dist/utils/codeSearch.js +187 -0
- package/package.json +20 -1
- package/landing/index.html +0 -764
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Nathan / Context Mirror
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# claude-memory
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@contextmirror/claude-memory)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://nodejs.org/)
|
|
6
|
+
[](https://discord.gg/JBpsSFB7EQ)
|
|
7
|
+
|
|
3
8
|
Cross-project memory for Claude Code. Give Claude awareness of ALL your projects, not just the one you're in.
|
|
4
9
|
|
|
5
10
|
## The Problem
|
|
@@ -298,6 +303,12 @@ Run `claude-memory scan --quick` when you've made changes to multiple projects.
|
|
|
298
303
|
**Is my code uploaded anywhere?**
|
|
299
304
|
No. All data stays in `~/.claude-memory/` on your machine. Nothing is sent to any server.
|
|
300
305
|
|
|
306
|
+
## Community
|
|
307
|
+
|
|
308
|
+
Join our Discord for feedback, feature requests, and support:
|
|
309
|
+
|
|
310
|
+
[](https://discord.gg/JBpsSFB7EQ)
|
|
311
|
+
|
|
301
312
|
## License
|
|
302
313
|
|
|
303
314
|
MIT
|
|
@@ -308,4 +319,4 @@ Nathan (with Claude as co-developer)
|
|
|
308
319
|
|
|
309
320
|
---
|
|
310
321
|
|
|
311
|
-
*
|
|
322
|
+
*[GitHub](https://github.com/nayballs/claude-memory)*
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { formatSearchResults } from '../utils/codeSearch.js';
|
|
3
|
+
describe('Code Search', () => {
|
|
4
|
+
describe('formatSearchResults', () => {
|
|
5
|
+
it('should format empty results correctly', () => {
|
|
6
|
+
const result = formatSearchResults([], 'test');
|
|
7
|
+
expect(result).toBe('No results found for "test".');
|
|
8
|
+
});
|
|
9
|
+
it('should format single result correctly', () => {
|
|
10
|
+
const results = [{
|
|
11
|
+
project: 'my-project',
|
|
12
|
+
projectPath: '/home/user/my-project',
|
|
13
|
+
file: '/home/user/my-project/src/index.ts',
|
|
14
|
+
relativePath: 'src/index.ts',
|
|
15
|
+
line: 10,
|
|
16
|
+
content: 'const test = "hello";',
|
|
17
|
+
context: {
|
|
18
|
+
before: 'import { something } from "./lib";',
|
|
19
|
+
after: 'export default test;',
|
|
20
|
+
},
|
|
21
|
+
}];
|
|
22
|
+
const result = formatSearchResults(results, 'test');
|
|
23
|
+
expect(result).toContain('# Code Search Results for "test"');
|
|
24
|
+
expect(result).toContain('Found 1 result');
|
|
25
|
+
expect(result).toContain('## my-project');
|
|
26
|
+
expect(result).toContain('src/index.ts:10');
|
|
27
|
+
expect(result).toContain('const test = "hello";');
|
|
28
|
+
});
|
|
29
|
+
it('should group results by project', () => {
|
|
30
|
+
const results = [
|
|
31
|
+
{
|
|
32
|
+
project: 'project-a',
|
|
33
|
+
projectPath: '/path/a',
|
|
34
|
+
file: '/path/a/file1.ts',
|
|
35
|
+
relativePath: 'file1.ts',
|
|
36
|
+
line: 1,
|
|
37
|
+
content: 'line 1',
|
|
38
|
+
context: { before: '', after: '' },
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
project: 'project-b',
|
|
42
|
+
projectPath: '/path/b',
|
|
43
|
+
file: '/path/b/file2.ts',
|
|
44
|
+
relativePath: 'file2.ts',
|
|
45
|
+
line: 2,
|
|
46
|
+
content: 'line 2',
|
|
47
|
+
context: { before: '', after: '' },
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
project: 'project-a',
|
|
51
|
+
projectPath: '/path/a',
|
|
52
|
+
file: '/path/a/file3.ts',
|
|
53
|
+
relativePath: 'file3.ts',
|
|
54
|
+
line: 3,
|
|
55
|
+
content: 'line 3',
|
|
56
|
+
context: { before: '', after: '' },
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
const result = formatSearchResults(results, 'query');
|
|
60
|
+
expect(result).toContain('Found 3 results');
|
|
61
|
+
expect(result).toContain('## project-a');
|
|
62
|
+
expect(result).toContain('## project-b');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { validateKeyFormat, isLemonSqueezyKey } from '../license/index.js';
|
|
3
|
+
describe('License Key Validation', () => {
|
|
4
|
+
describe('validateKeyFormat', () => {
|
|
5
|
+
it('should accept valid LemonSqueezy UUID keys', () => {
|
|
6
|
+
expect(validateKeyFormat('A1B2C3D4-E5F6-7890-ABCD-EF1234567890')).toBe(true);
|
|
7
|
+
expect(validateKeyFormat('a1b2c3d4-e5f6-7890-abcd-ef1234567890')).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
it('should accept valid legacy CM- keys', () => {
|
|
10
|
+
expect(validateKeyFormat('CM-ABCD-1234-WXYZ')).toBe(true);
|
|
11
|
+
expect(validateKeyFormat('cm-abcd-1234-wxyz')).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
it('should reject invalid key formats', () => {
|
|
14
|
+
expect(validateKeyFormat('')).toBe(false);
|
|
15
|
+
expect(validateKeyFormat('invalid-key')).toBe(false);
|
|
16
|
+
expect(validateKeyFormat('CM-ABC-1234-WXYZ')).toBe(false); // Wrong segment length
|
|
17
|
+
expect(validateKeyFormat('12345678-1234-1234-1234-123456789')).toBe(false); // Too short
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
describe('isLemonSqueezyKey', () => {
|
|
21
|
+
it('should identify LemonSqueezy UUID keys', () => {
|
|
22
|
+
expect(isLemonSqueezyKey('A1B2C3D4-E5F6-7890-ABCD-EF1234567890')).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
it('should not identify legacy keys as LemonSqueezy', () => {
|
|
25
|
+
expect(isLemonSqueezyKey('CM-ABCD-1234-WXYZ')).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { detectStaleProjects } from '../scanner/stalenessDetector.js';
|
|
3
|
+
function createMockProject(overrides = {}) {
|
|
4
|
+
return {
|
|
5
|
+
name: 'test-project',
|
|
6
|
+
path: '/home/user/projects/test-project',
|
|
7
|
+
description: 'A test project',
|
|
8
|
+
language: 'typescript',
|
|
9
|
+
techStack: ['Node.js', 'TypeScript'],
|
|
10
|
+
currentBranch: 'main',
|
|
11
|
+
isDirty: false,
|
|
12
|
+
lastActivity: new Date().toISOString(),
|
|
13
|
+
hasFiles: {
|
|
14
|
+
readme: true,
|
|
15
|
+
claudeMd: false,
|
|
16
|
+
packageJson: true,
|
|
17
|
+
gitignore: true,
|
|
18
|
+
},
|
|
19
|
+
insights: [],
|
|
20
|
+
lastScanned: new Date().toISOString(),
|
|
21
|
+
...overrides,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function createMockContext(projects = [], hoursAgo = 0) {
|
|
25
|
+
const lastUpdated = new Date(Date.now() - hoursAgo * 60 * 60 * 1000).toISOString();
|
|
26
|
+
return {
|
|
27
|
+
schemaVersion: 1,
|
|
28
|
+
lastUpdated,
|
|
29
|
+
projects,
|
|
30
|
+
insights: [],
|
|
31
|
+
userPatterns: [],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
describe('Staleness Detection', () => {
|
|
35
|
+
describe('detectStaleProjects', () => {
|
|
36
|
+
it('should return no stale projects for fresh data', () => {
|
|
37
|
+
const context = createMockContext([createMockProject()], 0);
|
|
38
|
+
const report = detectStaleProjects(context);
|
|
39
|
+
expect(report.staleProjects).toHaveLength(0);
|
|
40
|
+
expect(report.dataAgeHours).toBeLessThan(1);
|
|
41
|
+
});
|
|
42
|
+
it('should mark data as stale after 24 hours', () => {
|
|
43
|
+
const context = createMockContext([createMockProject()], 25);
|
|
44
|
+
const report = detectStaleProjects(context);
|
|
45
|
+
expect(report.dataAgeHours).toBeGreaterThan(24);
|
|
46
|
+
});
|
|
47
|
+
it('should detect projects scanned more than 7 days ago', () => {
|
|
48
|
+
const oldScanDate = new Date(Date.now() - 8 * 24 * 60 * 60 * 1000).toISOString();
|
|
49
|
+
const project = createMockProject({ lastScanned: oldScanDate });
|
|
50
|
+
const context = createMockContext([project], 0);
|
|
51
|
+
const report = detectStaleProjects(context);
|
|
52
|
+
expect(report.staleProjects.length).toBeGreaterThanOrEqual(0);
|
|
53
|
+
});
|
|
54
|
+
it('should handle empty projects list', () => {
|
|
55
|
+
const context = createMockContext([], 0);
|
|
56
|
+
const report = detectStaleProjects(context);
|
|
57
|
+
expect(report.staleProjects).toHaveLength(0);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
// Test version comparison logic
|
|
3
|
+
function isNewerVersion(current, latest) {
|
|
4
|
+
const currentParts = current.split('.').map(Number);
|
|
5
|
+
const latestParts = latest.split('.').map(Number);
|
|
6
|
+
for (let i = 0; i < 3; i++) {
|
|
7
|
+
const curr = currentParts[i] || 0;
|
|
8
|
+
const lat = latestParts[i] || 0;
|
|
9
|
+
if (lat > curr)
|
|
10
|
+
return true;
|
|
11
|
+
if (lat < curr)
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
describe('Update Checker', () => {
|
|
17
|
+
describe('version comparison', () => {
|
|
18
|
+
it('should detect newer major versions', () => {
|
|
19
|
+
expect(isNewerVersion('0.4.2', '1.0.0')).toBe(true);
|
|
20
|
+
expect(isNewerVersion('1.0.0', '2.0.0')).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
it('should detect newer minor versions', () => {
|
|
23
|
+
expect(isNewerVersion('0.4.2', '0.5.0')).toBe(true);
|
|
24
|
+
expect(isNewerVersion('0.4.2', '0.5.1')).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
it('should detect newer patch versions', () => {
|
|
27
|
+
expect(isNewerVersion('0.4.2', '0.4.3')).toBe(true);
|
|
28
|
+
expect(isNewerVersion('0.4.2', '0.4.10')).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
it('should return false for same version', () => {
|
|
31
|
+
expect(isNewerVersion('0.4.2', '0.4.2')).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
it('should return false for older versions', () => {
|
|
34
|
+
expect(isNewerVersion('0.4.2', '0.4.1')).toBe(false);
|
|
35
|
+
expect(isNewerVersion('0.4.2', '0.3.9')).toBe(false);
|
|
36
|
+
expect(isNewerVersion('1.0.0', '0.9.9')).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
it('should handle missing patch version', () => {
|
|
39
|
+
expect(isNewerVersion('0.4', '0.4.1')).toBe(true);
|
|
40
|
+
expect(isNewerVersion('0.4.1', '0.5')).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
});
|
package/dist/cli.js
CHANGED
|
@@ -10,19 +10,26 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { Command } from 'commander';
|
|
12
12
|
import { readFileSync, existsSync, writeFileSync } from 'fs';
|
|
13
|
-
import { join } from 'path';
|
|
13
|
+
import { join, dirname } from 'path';
|
|
14
14
|
import { homedir } from 'os';
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
15
16
|
import { scanProjects } from './scanner/projectScanner.js';
|
|
16
17
|
import { generateGlobalContext, writeGlobalContext, getMemoryDir } from './scanner/contextGenerator.js';
|
|
17
18
|
import { generateBriefing, briefingToClaudeMd } from './briefing/briefingGenerator.js';
|
|
18
19
|
import { runSetupWizard } from './setup/setupWizard.js';
|
|
19
20
|
import { activateLicense, deactivateLicense, getLicenseStatus } from './license/index.js';
|
|
20
21
|
import { detectStaleProjects } from './scanner/stalenessDetector.js';
|
|
22
|
+
// Get version from package.json
|
|
23
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
24
|
+
const __dirname = dirname(__filename);
|
|
25
|
+
const packageJsonPath = join(__dirname, '../package.json');
|
|
26
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
27
|
+
const VERSION = packageJson.version;
|
|
21
28
|
const program = new Command();
|
|
22
29
|
program
|
|
23
30
|
.name('claude-memory')
|
|
24
31
|
.description('Cross-project memory for Claude Code')
|
|
25
|
-
.version(
|
|
32
|
+
.version(VERSION);
|
|
26
33
|
// Scan command
|
|
27
34
|
program
|
|
28
35
|
.command('scan')
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized constants for Claude Memory
|
|
3
|
+
*/
|
|
4
|
+
export declare const GIT_TIMEOUT_MS = 10000;
|
|
5
|
+
export declare const UPDATE_CHECK_INTERVAL_MS: number;
|
|
6
|
+
export declare const STALE_DATA_HOURS = 24;
|
|
7
|
+
export declare const STALE_PROJECT_DAYS = 7;
|
|
8
|
+
export declare const MAX_README_CHARS = 3000;
|
|
9
|
+
export declare const MAX_DESCRIPTION_CHARS = 200;
|
|
10
|
+
export declare const PRODUCT_URL = "https://claude-memory.dev";
|
|
11
|
+
export declare const DISCORD_URL = "https://discord.gg/JBpsSFB7EQ";
|
|
12
|
+
export declare const NPM_REGISTRY_URL = "https://registry.npmjs.org/@contextmirror/claude-memory";
|
|
13
|
+
export declare const LEMONSQUEEZY_VALIDATE_URL = "https://api.lemonsqueezy.com/v1/licenses/validate";
|
|
14
|
+
export declare const MEMORY_DIR_NAME = ".claude-memory";
|
|
15
|
+
export declare const CONTEXT_FILE = "context.json";
|
|
16
|
+
export declare const CONTEXT_BACKUP_FILE = "context.json.bak";
|
|
17
|
+
export declare const CONFIG_FILE = "config.json";
|
|
18
|
+
export declare const LICENSE_FILE = "license.json";
|
|
19
|
+
export declare const UPDATE_CACHE_FILE = "update-check.json";
|
|
20
|
+
export declare const GLOBAL_CONTEXT_MD = "global-context.md";
|
|
21
|
+
export declare const PROJECT_MARKERS: string[];
|
|
22
|
+
export declare const IGNORE_DIRS: string[];
|
|
23
|
+
export declare const EXIT_SUCCESS = 0;
|
|
24
|
+
export declare const EXIT_ERROR = 1;
|
|
25
|
+
export declare const EXIT_INVALID_ARGS = 2;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized constants for Claude Memory
|
|
3
|
+
*/
|
|
4
|
+
// Timeouts
|
|
5
|
+
export const GIT_TIMEOUT_MS = 10000;
|
|
6
|
+
export const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
7
|
+
// Staleness thresholds
|
|
8
|
+
export const STALE_DATA_HOURS = 24;
|
|
9
|
+
export const STALE_PROJECT_DAYS = 7;
|
|
10
|
+
// File limits
|
|
11
|
+
export const MAX_README_CHARS = 3000;
|
|
12
|
+
export const MAX_DESCRIPTION_CHARS = 200;
|
|
13
|
+
// URLs
|
|
14
|
+
export const PRODUCT_URL = 'https://claude-memory.dev';
|
|
15
|
+
export const DISCORD_URL = 'https://discord.gg/JBpsSFB7EQ';
|
|
16
|
+
export const NPM_REGISTRY_URL = 'https://registry.npmjs.org/@contextmirror/claude-memory';
|
|
17
|
+
export const LEMONSQUEEZY_VALIDATE_URL = 'https://api.lemonsqueezy.com/v1/licenses/validate';
|
|
18
|
+
// Paths
|
|
19
|
+
export const MEMORY_DIR_NAME = '.claude-memory';
|
|
20
|
+
export const CONTEXT_FILE = 'context.json';
|
|
21
|
+
export const CONTEXT_BACKUP_FILE = 'context.json.bak';
|
|
22
|
+
export const CONFIG_FILE = 'config.json';
|
|
23
|
+
export const LICENSE_FILE = 'license.json';
|
|
24
|
+
export const UPDATE_CACHE_FILE = 'update-check.json';
|
|
25
|
+
export const GLOBAL_CONTEXT_MD = 'global-context.md';
|
|
26
|
+
// Project detection patterns
|
|
27
|
+
export const PROJECT_MARKERS = [
|
|
28
|
+
'package.json',
|
|
29
|
+
'Cargo.toml',
|
|
30
|
+
'pyproject.toml',
|
|
31
|
+
'go.mod',
|
|
32
|
+
'.git',
|
|
33
|
+
'requirements.txt',
|
|
34
|
+
];
|
|
35
|
+
// Directories to ignore during scanning
|
|
36
|
+
export const IGNORE_DIRS = [
|
|
37
|
+
'node_modules',
|
|
38
|
+
'.git',
|
|
39
|
+
'dist',
|
|
40
|
+
'build',
|
|
41
|
+
'__pycache__',
|
|
42
|
+
'target',
|
|
43
|
+
'.venv',
|
|
44
|
+
'venv',
|
|
45
|
+
'coverage',
|
|
46
|
+
'.next',
|
|
47
|
+
'.nuxt',
|
|
48
|
+
'.cache',
|
|
49
|
+
];
|
|
50
|
+
// Exit codes
|
|
51
|
+
export const EXIT_SUCCESS = 0;
|
|
52
|
+
export const EXIT_ERROR = 1;
|
|
53
|
+
export const EXIT_INVALID_ARGS = 2;
|
package/dist/license/index.d.ts
CHANGED
|
@@ -15,14 +15,18 @@ export declare function saveLicense(license: License): void;
|
|
|
15
15
|
*/
|
|
16
16
|
export declare function isPro(): boolean;
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
18
|
+
* Check if key is LemonSqueezy format
|
|
19
|
+
*/
|
|
20
|
+
export declare function isLemonSqueezyKey(key: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Validate a license key format (accepts both LemonSqueezy and legacy formats)
|
|
19
23
|
*/
|
|
20
24
|
export declare function validateKeyFormat(key: string): boolean;
|
|
21
25
|
/**
|
|
22
26
|
* Activate a license key
|
|
23
27
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
28
|
+
* For LemonSqueezy keys: Validates against their API
|
|
29
|
+
* For legacy keys: Format check only (backwards compatibility)
|
|
26
30
|
*/
|
|
27
31
|
export declare function activateLicense(key: string): Promise<LicenseValidationResult>;
|
|
28
32
|
/**
|
package/dist/license/index.js
CHANGED
|
@@ -4,10 +4,14 @@
|
|
|
4
4
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'fs';
|
|
5
5
|
import { join } from 'path';
|
|
6
6
|
import { homedir } from 'os';
|
|
7
|
+
import { LEMONSQUEEZY_VALIDATE_URL } from '../constants.js';
|
|
7
8
|
const MEMORY_DIR = join(homedir(), '.claude-memory');
|
|
8
9
|
const LICENSE_PATH = join(MEMORY_DIR, 'license.json');
|
|
9
|
-
// License key
|
|
10
|
-
|
|
10
|
+
// License key formats:
|
|
11
|
+
// - LemonSqueezy: UUID format (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
|
|
12
|
+
// - Legacy: CM-XXXX-XXXX-XXXX (alphanumeric)
|
|
13
|
+
const LEMONSQUEEZY_KEY_PATTERN = /^[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{12}$/i;
|
|
14
|
+
const LEGACY_KEY_PATTERN = /^CM-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/i;
|
|
11
15
|
/**
|
|
12
16
|
* Ensure the memory directory exists
|
|
13
17
|
*/
|
|
@@ -59,38 +63,92 @@ export function isPro() {
|
|
|
59
63
|
return true;
|
|
60
64
|
}
|
|
61
65
|
/**
|
|
62
|
-
*
|
|
66
|
+
* Check if key is LemonSqueezy format
|
|
67
|
+
*/
|
|
68
|
+
export function isLemonSqueezyKey(key) {
|
|
69
|
+
return LEMONSQUEEZY_KEY_PATTERN.test(key);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Validate a license key format (accepts both LemonSqueezy and legacy formats)
|
|
63
73
|
*/
|
|
64
74
|
export function validateKeyFormat(key) {
|
|
65
|
-
return
|
|
75
|
+
return LEMONSQUEEZY_KEY_PATTERN.test(key) || LEGACY_KEY_PATTERN.test(key);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Validate license key with LemonSqueezy API
|
|
79
|
+
*/
|
|
80
|
+
async function validateWithLemonSqueezy(key) {
|
|
81
|
+
try {
|
|
82
|
+
const response = await fetch(LEMONSQUEEZY_VALIDATE_URL, {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
headers: {
|
|
85
|
+
'Accept': 'application/json',
|
|
86
|
+
'Content-Type': 'application/json',
|
|
87
|
+
},
|
|
88
|
+
body: JSON.stringify({
|
|
89
|
+
license_key: key,
|
|
90
|
+
}),
|
|
91
|
+
});
|
|
92
|
+
const data = await response.json();
|
|
93
|
+
if (data.valid) {
|
|
94
|
+
return {
|
|
95
|
+
valid: true,
|
|
96
|
+
email: data.meta?.customer_email,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
return {
|
|
101
|
+
valid: false,
|
|
102
|
+
error: data.error || 'License key is not valid',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
// Network error - allow offline activation with warning
|
|
108
|
+
return {
|
|
109
|
+
valid: true, // Allow offline - will revalidate later
|
|
110
|
+
error: 'Could not reach license server. Activating offline.',
|
|
111
|
+
};
|
|
112
|
+
}
|
|
66
113
|
}
|
|
67
114
|
/**
|
|
68
115
|
* Activate a license key
|
|
69
116
|
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
117
|
+
* For LemonSqueezy keys: Validates against their API
|
|
118
|
+
* For legacy keys: Format check only (backwards compatibility)
|
|
72
119
|
*/
|
|
73
120
|
export async function activateLicense(key) {
|
|
74
|
-
// Normalize key
|
|
121
|
+
// Normalize key (LemonSqueezy keys are uppercase hex)
|
|
75
122
|
const normalizedKey = key.toUpperCase();
|
|
76
123
|
// Validate format
|
|
77
124
|
if (!validateKeyFormat(normalizedKey)) {
|
|
78
125
|
return {
|
|
79
126
|
valid: false,
|
|
80
|
-
error: 'Invalid license key format.
|
|
127
|
+
error: 'Invalid license key format. Get your key from https://claude-memory.dev',
|
|
81
128
|
};
|
|
82
129
|
}
|
|
83
|
-
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
130
|
+
let email;
|
|
131
|
+
// Validate LemonSqueezy keys with their API
|
|
132
|
+
if (isLemonSqueezyKey(normalizedKey)) {
|
|
133
|
+
const result = await validateWithLemonSqueezy(normalizedKey);
|
|
134
|
+
if (!result.valid) {
|
|
135
|
+
return {
|
|
136
|
+
valid: false,
|
|
137
|
+
error: result.error || 'License validation failed',
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
email = result.email;
|
|
141
|
+
// Show offline warning if applicable
|
|
142
|
+
if (result.error) {
|
|
143
|
+
console.log(`⚠️ ${result.error}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
89
146
|
const license = {
|
|
90
147
|
key: normalizedKey,
|
|
91
148
|
plan: 'pro',
|
|
92
149
|
activatedAt: new Date().toISOString(),
|
|
93
|
-
|
|
150
|
+
email,
|
|
151
|
+
// Subscription licenses don't expire locally - LemonSqueezy handles this
|
|
94
152
|
};
|
|
95
153
|
saveLicense(license);
|
|
96
154
|
return {
|
package/dist/mcp/server.js
CHANGED
|
@@ -11,14 +11,21 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
|
11
11
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
12
12
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
13
13
|
import { readFileSync, existsSync, readdirSync, statSync, writeFileSync } from 'fs';
|
|
14
|
-
import { join } from 'path';
|
|
14
|
+
import { join, dirname } from 'path';
|
|
15
15
|
import { homedir } from 'os';
|
|
16
|
+
import { fileURLToPath } from 'url';
|
|
16
17
|
import { DEFAULT_MEMORY_CONFIG } from '../types/index.js';
|
|
17
18
|
import { isPro, getProFeatureMessage } from '../license/index.js';
|
|
18
19
|
import { detectStaleProjects, checkCurrentProjectStaleness, formatStalenessForMcp } from '../scanner/stalenessDetector.js';
|
|
19
20
|
import { checkForUpdate, formatUpdateMessage } from '../utils/updateChecker.js';
|
|
21
|
+
import { searchCode, formatSearchResults } from '../utils/codeSearch.js';
|
|
20
22
|
const MEMORY_DIR = join(homedir(), '.claude-memory');
|
|
21
|
-
|
|
23
|
+
// Read version from package.json to ensure single source of truth
|
|
24
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
25
|
+
const __dirname = dirname(__filename);
|
|
26
|
+
const packageJsonPath = join(__dirname, '../../package.json');
|
|
27
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
28
|
+
const CURRENT_VERSION = packageJson.version;
|
|
22
29
|
// Tool definitions
|
|
23
30
|
const tools = [
|
|
24
31
|
{
|
|
@@ -405,22 +412,22 @@ function handleSearchCode(query, filePattern) {
|
|
|
405
412
|
if (!context) {
|
|
406
413
|
return 'No projects scanned yet. Run `claude-memory scan` first.';
|
|
407
414
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
'',
|
|
411
|
-
];
|
|
412
|
-
// TODO: Implement actual code search
|
|
413
|
-
// For now, return a placeholder that shows the feature is gated
|
|
414
|
-
lines.push('Searching across projects:');
|
|
415
|
-
for (const p of context.projects) {
|
|
416
|
-
lines.push(`- ${p.name} (${p.path})`);
|
|
415
|
+
if (!query || query.trim().length === 0) {
|
|
416
|
+
return 'Please provide a search query.';
|
|
417
417
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
418
|
+
// Map projects to the format expected by searchCode
|
|
419
|
+
const projects = context.projects.map(p => ({
|
|
420
|
+
name: p.name,
|
|
421
|
+
path: p.path,
|
|
422
|
+
}));
|
|
423
|
+
// Perform the search
|
|
424
|
+
const results = searchCode(projects, query, {
|
|
425
|
+
filePattern,
|
|
426
|
+
maxResults: 50,
|
|
427
|
+
caseSensitive: false,
|
|
428
|
+
contextLines: 1,
|
|
429
|
+
});
|
|
430
|
+
return formatSearchResults(results, query);
|
|
424
431
|
}
|
|
425
432
|
function handleGetProjectAnalysis(projectQuery) {
|
|
426
433
|
const context = loadContext();
|
|
@@ -559,7 +566,7 @@ function getDirectoryStructure(dir, maxDepth, prefix = '', depth = 0) {
|
|
|
559
566
|
async function main() {
|
|
560
567
|
const server = new Server({
|
|
561
568
|
name: 'claude-memory',
|
|
562
|
-
version:
|
|
569
|
+
version: CURRENT_VERSION,
|
|
563
570
|
}, {
|
|
564
571
|
capabilities: {
|
|
565
572
|
tools: {},
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code search utility for Pro feature
|
|
3
|
+
*/
|
|
4
|
+
export interface SearchResult {
|
|
5
|
+
project: string;
|
|
6
|
+
projectPath: string;
|
|
7
|
+
file: string;
|
|
8
|
+
relativePath: string;
|
|
9
|
+
line: number;
|
|
10
|
+
content: string;
|
|
11
|
+
context: {
|
|
12
|
+
before: string;
|
|
13
|
+
after: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export interface SearchOptions {
|
|
17
|
+
filePattern?: string;
|
|
18
|
+
maxResults?: number;
|
|
19
|
+
caseSensitive?: boolean;
|
|
20
|
+
contextLines?: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Search for code across multiple projects
|
|
24
|
+
*/
|
|
25
|
+
export declare function searchCode(projects: Array<{
|
|
26
|
+
name: string;
|
|
27
|
+
path: string;
|
|
28
|
+
}>, query: string, options?: SearchOptions): SearchResult[];
|
|
29
|
+
/**
|
|
30
|
+
* Format search results for display
|
|
31
|
+
*/
|
|
32
|
+
export declare function formatSearchResults(results: SearchResult[], query: string): string;
|