@contextmirror/claude-memory 0.2.1 ā 0.3.0
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/cli.js +234 -1
- package/dist/license/index.d.ts +39 -0
- package/dist/license/index.js +153 -0
- package/dist/license/types.d.ts +37 -0
- package/dist/license/types.js +4 -0
- package/dist/mcp/server.js +75 -1
- package/dist/scanner/contextGenerator.d.ts +7 -0
- package/dist/scanner/contextGenerator.js +97 -10
- package/dist/scanner/projectScanner.d.ts +9 -1
- package/dist/scanner/projectScanner.js +84 -15
- package/dist/scanner/stalenessDetector.d.ts +40 -0
- package/dist/scanner/stalenessDetector.js +198 -0
- package/dist/setup/setupWizard.js +28 -3
- package/dist/types/index.d.ts +7 -1
- package/dist/types/index.js +35 -2
- package/landing/index.html +478 -169
- package/package.json +2 -1
- package/.claude/settings.local.json +0 -10
package/dist/cli.js
CHANGED
|
@@ -16,6 +16,8 @@ import { scanProjects } from './scanner/projectScanner.js';
|
|
|
16
16
|
import { generateGlobalContext, writeGlobalContext, getMemoryDir } from './scanner/contextGenerator.js';
|
|
17
17
|
import { generateBriefing, briefingToClaudeMd } from './briefing/briefingGenerator.js';
|
|
18
18
|
import { runSetupWizard } from './setup/setupWizard.js';
|
|
19
|
+
import { activateLicense, deactivateLicense, getLicenseStatus } from './license/index.js';
|
|
20
|
+
import { detectStaleProjects } from './scanner/stalenessDetector.js';
|
|
19
21
|
const program = new Command();
|
|
20
22
|
program
|
|
21
23
|
.name('claude-memory')
|
|
@@ -28,14 +30,83 @@ program
|
|
|
28
30
|
.argument('[directory]', 'Root directory to scan', `${homedir()}/Project`)
|
|
29
31
|
.option('-d, --depth <number>', 'Max depth to search', '2')
|
|
30
32
|
.option('--generate-claude-md', 'Generate CLAUDE.md for projects without one')
|
|
33
|
+
.option('-q, --quick', 'Only rescan stale projects (faster)')
|
|
34
|
+
.option('--check', 'Only check for stale projects, don\'t scan')
|
|
31
35
|
.action(async (directory, options) => {
|
|
36
|
+
// Check-only mode: just report staleness
|
|
37
|
+
if (options.check) {
|
|
38
|
+
const context = loadContext();
|
|
39
|
+
if (!context) {
|
|
40
|
+
console.log('No projects scanned yet. Run: claude-memory scan');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const report = detectStaleProjects(context);
|
|
44
|
+
console.log('š§ Claude Memory - Staleness Check\n');
|
|
45
|
+
console.log(`Last full scan: ${new Date(context.lastUpdated).toLocaleString()}`);
|
|
46
|
+
console.log(`Data age: ${report.dataAgeHours} hours\n`);
|
|
47
|
+
if (report.staleProjects.length === 0) {
|
|
48
|
+
console.log('ā
All projects are up to date!');
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
console.log(`ā ļø ${report.staleProjects.length} project(s) need updating:\n`);
|
|
52
|
+
for (const p of report.staleProjects) {
|
|
53
|
+
const icon = p.reason === 'git_activity' ? 'š' :
|
|
54
|
+
p.reason === 'file_changed' ? 'š' : 'ā°';
|
|
55
|
+
console.log(` ${icon} ${p.name}`);
|
|
56
|
+
console.log(` ${p.details}`);
|
|
57
|
+
}
|
|
58
|
+
console.log(`\n${report.freshCount} project(s) are fresh.`);
|
|
59
|
+
console.log('\nRun `claude-memory scan --quick` to refresh only stale projects.');
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
32
63
|
console.log('š§ Claude Memory Scanner\n');
|
|
64
|
+
// Quick mode: only scan stale projects
|
|
65
|
+
let projectsToScan;
|
|
66
|
+
if (options.quick) {
|
|
67
|
+
const existingContext = loadContext();
|
|
68
|
+
if (existingContext) {
|
|
69
|
+
const report = detectStaleProjects(existingContext);
|
|
70
|
+
if (report.staleProjects.length === 0) {
|
|
71
|
+
console.log('ā
All projects are up to date! Nothing to scan.');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
projectsToScan = report.staleProjects.map(p => p.path);
|
|
75
|
+
console.log(`š Quick mode: Rescanning ${projectsToScan.length} stale project(s)...\n`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
33
78
|
const projects = await scanProjects({
|
|
34
79
|
rootDir: directory,
|
|
35
80
|
maxDepth: parseInt(options.depth, 10),
|
|
36
81
|
generateClaudeMd: options.generateClaudeMd,
|
|
82
|
+
onlyPaths: projectsToScan,
|
|
37
83
|
});
|
|
38
|
-
|
|
84
|
+
if (projects.length === 0) {
|
|
85
|
+
console.log('\nā ļø No projects found. Context not updated.');
|
|
86
|
+
console.log(' Check the directory path and try again.');
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
// For quick mode, merge with existing context
|
|
90
|
+
let context;
|
|
91
|
+
if (options.quick && projectsToScan) {
|
|
92
|
+
const existingContext = loadContext();
|
|
93
|
+
if (existingContext) {
|
|
94
|
+
// Replace only the rescanned projects
|
|
95
|
+
const rescannedPaths = new Set(projects.map(p => p.path));
|
|
96
|
+
const unchangedProjects = existingContext.projects.filter(p => !rescannedPaths.has(p.path));
|
|
97
|
+
context = {
|
|
98
|
+
...existingContext,
|
|
99
|
+
lastUpdated: new Date().toISOString(),
|
|
100
|
+
projects: [...unchangedProjects, ...projects],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
context = generateGlobalContext(projects);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
context = generateGlobalContext(projects);
|
|
109
|
+
}
|
|
39
110
|
writeGlobalContext(context);
|
|
40
111
|
console.log('\nā
Done! Global context updated.');
|
|
41
112
|
console.log('\nTo use with Claude Code, add the MCP server to your config:');
|
|
@@ -89,6 +160,106 @@ program
|
|
|
89
160
|
console.log(` CLAUDE.md: ${project.hasFiles.claudeMd ? 'ā' : 'ā'}`);
|
|
90
161
|
console.log(` README.md: ${project.hasFiles.readme ? 'ā' : 'ā'}`);
|
|
91
162
|
});
|
|
163
|
+
// Exclude command - exclude a project from scanning
|
|
164
|
+
program
|
|
165
|
+
.command('exclude')
|
|
166
|
+
.description('Exclude a project from future scans')
|
|
167
|
+
.argument('<project>', 'Project path or name')
|
|
168
|
+
.action((projectQuery) => {
|
|
169
|
+
const context = loadContext();
|
|
170
|
+
if (!context) {
|
|
171
|
+
console.log('No projects scanned yet. Run: claude-memory scan');
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
// Find the project
|
|
175
|
+
const project = context.projects.find((p) => p.name.toLowerCase() === projectQuery.toLowerCase() || p.path.includes(projectQuery));
|
|
176
|
+
if (!project) {
|
|
177
|
+
// Maybe it's a direct path
|
|
178
|
+
if (existsSync(projectQuery)) {
|
|
179
|
+
addExclusion(projectQuery);
|
|
180
|
+
console.log(`ā
Excluded: ${projectQuery}`);
|
|
181
|
+
console.log(' This project will be skipped in future scans.');
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
console.log(`Project not found: ${projectQuery}`);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
addExclusion(project.path);
|
|
188
|
+
console.log(`ā
Excluded: ${project.name}`);
|
|
189
|
+
console.log(` Path: ${project.path}`);
|
|
190
|
+
console.log(' This project will be skipped in future scans.');
|
|
191
|
+
console.log(' Run `claude-memory include` to re-add it.');
|
|
192
|
+
});
|
|
193
|
+
// Include command - remove a project from exclusion list
|
|
194
|
+
program
|
|
195
|
+
.command('include')
|
|
196
|
+
.description('Remove a project from the exclusion list')
|
|
197
|
+
.argument('<project>', 'Project path or name')
|
|
198
|
+
.action((projectQuery) => {
|
|
199
|
+
const context = loadContext();
|
|
200
|
+
if (!context) {
|
|
201
|
+
console.log('No projects scanned yet. Run: claude-memory scan');
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const excluded = context.excludedProjects || [];
|
|
205
|
+
const match = excluded.find(p => p.toLowerCase().includes(projectQuery.toLowerCase()));
|
|
206
|
+
if (!match) {
|
|
207
|
+
console.log(`Project not in exclusion list: ${projectQuery}`);
|
|
208
|
+
if (excluded.length > 0) {
|
|
209
|
+
console.log('\nCurrently excluded:');
|
|
210
|
+
excluded.forEach(p => console.log(` - ${p}`));
|
|
211
|
+
}
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
removeExclusion(match);
|
|
215
|
+
console.log(`ā
Removed from exclusion list: ${match}`);
|
|
216
|
+
console.log(' Run `claude-memory scan` to add it back to your projects.');
|
|
217
|
+
});
|
|
218
|
+
// Excluded command - list excluded projects
|
|
219
|
+
program
|
|
220
|
+
.command('excluded')
|
|
221
|
+
.description('List excluded projects')
|
|
222
|
+
.action(() => {
|
|
223
|
+
const context = loadContext();
|
|
224
|
+
const excluded = context?.excludedProjects || [];
|
|
225
|
+
if (excluded.length === 0) {
|
|
226
|
+
console.log('No excluded projects.');
|
|
227
|
+
console.log('Use `claude-memory exclude <project>` to exclude a project.');
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
console.log('š« Excluded Projects\n');
|
|
231
|
+
excluded.forEach(p => console.log(` ${p}`));
|
|
232
|
+
console.log('\nUse `claude-memory include <project>` to re-add.');
|
|
233
|
+
});
|
|
234
|
+
function addExclusion(projectPath) {
|
|
235
|
+
const contextPath = join(getMemoryDir(), 'context.json');
|
|
236
|
+
if (!existsSync(contextPath))
|
|
237
|
+
return;
|
|
238
|
+
try {
|
|
239
|
+
const context = JSON.parse(readFileSync(contextPath, 'utf-8'));
|
|
240
|
+
context.excludedProjects = context.excludedProjects || [];
|
|
241
|
+
if (!context.excludedProjects.includes(projectPath)) {
|
|
242
|
+
context.excludedProjects.push(projectPath);
|
|
243
|
+
writeFileSync(contextPath, JSON.stringify(context, null, 2), 'utf-8');
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
console.error('Failed to update exclusion list');
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function removeExclusion(projectPath) {
|
|
251
|
+
const contextPath = join(getMemoryDir(), 'context.json');
|
|
252
|
+
if (!existsSync(contextPath))
|
|
253
|
+
return;
|
|
254
|
+
try {
|
|
255
|
+
const context = JSON.parse(readFileSync(contextPath, 'utf-8'));
|
|
256
|
+
context.excludedProjects = (context.excludedProjects || []).filter((p) => p !== projectPath);
|
|
257
|
+
writeFileSync(contextPath, JSON.stringify(context, null, 2), 'utf-8');
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
console.error('Failed to update exclusion list');
|
|
261
|
+
}
|
|
262
|
+
}
|
|
92
263
|
// Briefing command - deep project analysis and CLAUDE.md generation
|
|
93
264
|
program
|
|
94
265
|
.command('briefing')
|
|
@@ -193,6 +364,68 @@ program
|
|
|
193
364
|
interactive: !options.nonInteractive,
|
|
194
365
|
});
|
|
195
366
|
});
|
|
367
|
+
// Activate command - activate a Pro license
|
|
368
|
+
program
|
|
369
|
+
.command('activate')
|
|
370
|
+
.description('Activate a Pro license key')
|
|
371
|
+
.argument('<key>', 'License key (format: CM-XXXX-XXXX-XXXX)')
|
|
372
|
+
.action(async (key) => {
|
|
373
|
+
console.log('Activating license...\n');
|
|
374
|
+
const result = await activateLicense(key);
|
|
375
|
+
if (!result.valid) {
|
|
376
|
+
console.log(`ā ${result.error}`);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
console.log('ā
License activated successfully!');
|
|
380
|
+
console.log(` Plan: Pro`);
|
|
381
|
+
if (result.license?.email) {
|
|
382
|
+
console.log(` Email: ${result.license.email}`);
|
|
383
|
+
}
|
|
384
|
+
console.log('\nPro features are now unlocked.');
|
|
385
|
+
});
|
|
386
|
+
// Status command - show license status
|
|
387
|
+
program
|
|
388
|
+
.command('status')
|
|
389
|
+
.description('Show license status')
|
|
390
|
+
.action(() => {
|
|
391
|
+
const status = getLicenseStatus();
|
|
392
|
+
console.log('š§ Claude Memory License Status\n');
|
|
393
|
+
console.log(` Plan: ${status.plan === 'pro' ? 'Pro' : 'Free'}`);
|
|
394
|
+
if (status.isPro) {
|
|
395
|
+
if (status.email) {
|
|
396
|
+
console.log(` Email: ${status.email}`);
|
|
397
|
+
}
|
|
398
|
+
if (status.activatedAt) {
|
|
399
|
+
console.log(` Activated: ${new Date(status.activatedAt).toLocaleDateString()}`);
|
|
400
|
+
}
|
|
401
|
+
if (status.expiresAt) {
|
|
402
|
+
console.log(` Expires: ${new Date(status.expiresAt).toLocaleDateString()}`);
|
|
403
|
+
if (status.daysRemaining) {
|
|
404
|
+
console.log(` Days remaining: ${status.daysRemaining}`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
console.log(` Status: Active`);
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
console.log(` Status: ${status.expiresAt ? 'Expired' : 'Not activated'}`);
|
|
411
|
+
console.log('\n Upgrade at https://claude-memory.dev');
|
|
412
|
+
console.log(' Or run: claude-memory activate <your-key>');
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
// Deactivate command - remove license
|
|
416
|
+
program
|
|
417
|
+
.command('deactivate')
|
|
418
|
+
.description('Remove Pro license')
|
|
419
|
+
.action(() => {
|
|
420
|
+
const removed = deactivateLicense();
|
|
421
|
+
if (removed) {
|
|
422
|
+
console.log('ā
License removed.');
|
|
423
|
+
console.log(' You are now on the Free plan.');
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
console.log('No license to remove.');
|
|
427
|
+
}
|
|
428
|
+
});
|
|
196
429
|
function loadContext() {
|
|
197
430
|
const contextPath = join(getMemoryDir(), 'context.json');
|
|
198
431
|
if (!existsSync(contextPath)) {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* License management for Claude Memory Pro
|
|
3
|
+
*/
|
|
4
|
+
import { License, LicenseStatus, LicenseValidationResult } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Load license from disk
|
|
7
|
+
*/
|
|
8
|
+
export declare function loadLicense(): License | null;
|
|
9
|
+
/**
|
|
10
|
+
* Save license to disk
|
|
11
|
+
*/
|
|
12
|
+
export declare function saveLicense(license: License): void;
|
|
13
|
+
/**
|
|
14
|
+
* Check if user has Pro license
|
|
15
|
+
*/
|
|
16
|
+
export declare function isPro(): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Validate a license key format
|
|
19
|
+
*/
|
|
20
|
+
export declare function validateKeyFormat(key: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Activate a license key
|
|
23
|
+
*
|
|
24
|
+
* Phase 1: Offline validation (format check only)
|
|
25
|
+
* Phase 2: Will call LemonSqueezy API for server validation
|
|
26
|
+
*/
|
|
27
|
+
export declare function activateLicense(key: string): Promise<LicenseValidationResult>;
|
|
28
|
+
/**
|
|
29
|
+
* Deactivate (remove) the current license
|
|
30
|
+
*/
|
|
31
|
+
export declare function deactivateLicense(): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Get current license status
|
|
34
|
+
*/
|
|
35
|
+
export declare function getLicenseStatus(): LicenseStatus;
|
|
36
|
+
/**
|
|
37
|
+
* Get a user-friendly error message for Pro features
|
|
38
|
+
*/
|
|
39
|
+
export declare function getProFeatureMessage(featureName: string): string;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* License management for Claude Memory Pro
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { homedir } from 'os';
|
|
7
|
+
const MEMORY_DIR = join(homedir(), '.claude-memory');
|
|
8
|
+
const LICENSE_PATH = join(MEMORY_DIR, 'license.json');
|
|
9
|
+
// License key format: CM-XXXX-XXXX-XXXX (alphanumeric)
|
|
10
|
+
const LICENSE_KEY_PATTERN = /^CM-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/i;
|
|
11
|
+
/**
|
|
12
|
+
* Ensure the memory directory exists
|
|
13
|
+
*/
|
|
14
|
+
function ensureMemoryDir() {
|
|
15
|
+
if (!existsSync(MEMORY_DIR)) {
|
|
16
|
+
mkdirSync(MEMORY_DIR, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Load license from disk
|
|
21
|
+
*/
|
|
22
|
+
export function loadLicense() {
|
|
23
|
+
if (!existsSync(LICENSE_PATH)) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const data = readFileSync(LICENSE_PATH, 'utf-8');
|
|
28
|
+
return JSON.parse(data);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Save license to disk
|
|
36
|
+
*/
|
|
37
|
+
export function saveLicense(license) {
|
|
38
|
+
ensureMemoryDir();
|
|
39
|
+
writeFileSync(LICENSE_PATH, JSON.stringify(license, null, 2), 'utf-8');
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Check if user has Pro license
|
|
43
|
+
*/
|
|
44
|
+
export function isPro() {
|
|
45
|
+
const license = loadLicense();
|
|
46
|
+
if (!license) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (license.plan !== 'pro') {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
// Check expiration
|
|
53
|
+
if (license.expiresAt) {
|
|
54
|
+
const expiresAt = new Date(license.expiresAt);
|
|
55
|
+
if (expiresAt < new Date()) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Validate a license key format
|
|
63
|
+
*/
|
|
64
|
+
export function validateKeyFormat(key) {
|
|
65
|
+
return LICENSE_KEY_PATTERN.test(key);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Activate a license key
|
|
69
|
+
*
|
|
70
|
+
* Phase 1: Offline validation (format check only)
|
|
71
|
+
* Phase 2: Will call LemonSqueezy API for server validation
|
|
72
|
+
*/
|
|
73
|
+
export async function activateLicense(key) {
|
|
74
|
+
// Normalize key to uppercase
|
|
75
|
+
const normalizedKey = key.toUpperCase();
|
|
76
|
+
// Validate format
|
|
77
|
+
if (!validateKeyFormat(normalizedKey)) {
|
|
78
|
+
return {
|
|
79
|
+
valid: false,
|
|
80
|
+
error: 'Invalid license key format. Expected: CM-XXXX-XXXX-XXXX',
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
// TODO: Phase 2 - Call LemonSqueezy API to validate
|
|
84
|
+
// const response = await fetch('https://api.claude-memory.dev/validate', {
|
|
85
|
+
// method: 'POST',
|
|
86
|
+
// body: JSON.stringify({ key: normalizedKey }),
|
|
87
|
+
// });
|
|
88
|
+
// For now, accept any valid format (Phase 1 - offline)
|
|
89
|
+
const license = {
|
|
90
|
+
key: normalizedKey,
|
|
91
|
+
plan: 'pro',
|
|
92
|
+
activatedAt: new Date().toISOString(),
|
|
93
|
+
// No expiration for now
|
|
94
|
+
};
|
|
95
|
+
saveLicense(license);
|
|
96
|
+
return {
|
|
97
|
+
valid: true,
|
|
98
|
+
license,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Deactivate (remove) the current license
|
|
103
|
+
*/
|
|
104
|
+
export function deactivateLicense() {
|
|
105
|
+
if (!existsSync(LICENSE_PATH)) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
unlinkSync(LICENSE_PATH);
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get current license status
|
|
118
|
+
*/
|
|
119
|
+
export function getLicenseStatus() {
|
|
120
|
+
const license = loadLicense();
|
|
121
|
+
if (!license) {
|
|
122
|
+
return {
|
|
123
|
+
isPro: false,
|
|
124
|
+
plan: 'free',
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
let daysRemaining;
|
|
128
|
+
let isExpired = false;
|
|
129
|
+
if (license.expiresAt) {
|
|
130
|
+
const expiresAt = new Date(license.expiresAt);
|
|
131
|
+
const now = new Date();
|
|
132
|
+
const diffMs = expiresAt.getTime() - now.getTime();
|
|
133
|
+
daysRemaining = Math.ceil(diffMs / (1000 * 60 * 60 * 24));
|
|
134
|
+
isExpired = daysRemaining < 0;
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
isPro: license.plan === 'pro' && !isExpired,
|
|
138
|
+
plan: isExpired ? 'free' : license.plan,
|
|
139
|
+
email: license.email,
|
|
140
|
+
expiresAt: license.expiresAt,
|
|
141
|
+
daysRemaining: daysRemaining && daysRemaining > 0 ? daysRemaining : undefined,
|
|
142
|
+
activatedAt: license.activatedAt,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get a user-friendly error message for Pro features
|
|
147
|
+
*/
|
|
148
|
+
export function getProFeatureMessage(featureName) {
|
|
149
|
+
return `${featureName} is a Pro feature.
|
|
150
|
+
|
|
151
|
+
Upgrade at https://claude-memory.dev or activate your license:
|
|
152
|
+
claude-memory activate <your-key>`;
|
|
153
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* License types for Claude Memory Pro
|
|
3
|
+
*/
|
|
4
|
+
export interface License {
|
|
5
|
+
/** License key (format: CM-XXXX-XXXX-XXXX) */
|
|
6
|
+
key: string;
|
|
7
|
+
/** Email associated with the license */
|
|
8
|
+
email?: string;
|
|
9
|
+
/** License plan */
|
|
10
|
+
plan: 'free' | 'pro';
|
|
11
|
+
/** When the license was activated */
|
|
12
|
+
activatedAt: string;
|
|
13
|
+
/** When the license expires (if applicable) */
|
|
14
|
+
expiresAt?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface LicenseStatus {
|
|
17
|
+
/** Whether the user has Pro features */
|
|
18
|
+
isPro: boolean;
|
|
19
|
+
/** Current plan */
|
|
20
|
+
plan: 'free' | 'pro';
|
|
21
|
+
/** Email associated with license */
|
|
22
|
+
email?: string;
|
|
23
|
+
/** Expiration date */
|
|
24
|
+
expiresAt?: string;
|
|
25
|
+
/** Days remaining until expiration */
|
|
26
|
+
daysRemaining?: number;
|
|
27
|
+
/** When activated */
|
|
28
|
+
activatedAt?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface LicenseValidationResult {
|
|
31
|
+
/** Whether the key is valid */
|
|
32
|
+
valid: boolean;
|
|
33
|
+
/** Error message if invalid */
|
|
34
|
+
error?: string;
|
|
35
|
+
/** License details if valid */
|
|
36
|
+
license?: License;
|
|
37
|
+
}
|
package/dist/mcp/server.js
CHANGED
|
@@ -14,7 +14,10 @@ import { readFileSync, existsSync, readdirSync, statSync, writeFileSync } from '
|
|
|
14
14
|
import { join } from 'path';
|
|
15
15
|
import { homedir } from 'os';
|
|
16
16
|
import { DEFAULT_MEMORY_CONFIG } from '../types/index.js';
|
|
17
|
+
import { isPro, getProFeatureMessage } from '../license/index.js';
|
|
18
|
+
import { detectStaleProjects, checkCurrentProjectStaleness, formatStalenessForMcp } from '../scanner/stalenessDetector.js';
|
|
17
19
|
const MEMORY_DIR = join(homedir(), '.claude-memory');
|
|
20
|
+
const CURRENT_VERSION = '0.2.3'; // Update this with each release
|
|
18
21
|
// Tool definitions
|
|
19
22
|
const tools = [
|
|
20
23
|
{
|
|
@@ -92,6 +95,25 @@ const tools = [
|
|
|
92
95
|
required: ['project'],
|
|
93
96
|
},
|
|
94
97
|
},
|
|
98
|
+
// Pro tools
|
|
99
|
+
{
|
|
100
|
+
name: 'search_code',
|
|
101
|
+
description: '[PRO] Search for code patterns across all your projects. Find implementations, discover how you solved similar problems before. Requires Pro license.',
|
|
102
|
+
inputSchema: {
|
|
103
|
+
type: 'object',
|
|
104
|
+
properties: {
|
|
105
|
+
query: {
|
|
106
|
+
type: 'string',
|
|
107
|
+
description: 'Code pattern or text to search for',
|
|
108
|
+
},
|
|
109
|
+
filePattern: {
|
|
110
|
+
type: 'string',
|
|
111
|
+
description: 'File pattern to search (e.g., "*.ts", "*.py"). Optional.',
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
required: ['query'],
|
|
115
|
+
},
|
|
116
|
+
},
|
|
95
117
|
];
|
|
96
118
|
function loadContext() {
|
|
97
119
|
const contextPath = join(MEMORY_DIR, 'context.json');
|
|
@@ -218,6 +240,13 @@ function handleGetGlobalContext(cwd) {
|
|
|
218
240
|
lines.push(`**Status:** ${cwdStatus.status}`);
|
|
219
241
|
if (cwdStatus.projectName) {
|
|
220
242
|
lines.push(`**Project:** ${cwdStatus.projectName}`);
|
|
243
|
+
// Check if current project is stale
|
|
244
|
+
if (context) {
|
|
245
|
+
const currentStaleness = checkCurrentProjectStaleness(context, cwd);
|
|
246
|
+
if (currentStaleness.isStale && currentStaleness.reason) {
|
|
247
|
+
lines.push(`**ā ļø Stale:** ${currentStaleness.reason}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
221
250
|
}
|
|
222
251
|
if (cwdStatus.needsSetup) {
|
|
223
252
|
lines.push(`**Needs Setup:** Yes`);
|
|
@@ -243,9 +272,22 @@ function handleGetGlobalContext(cwd) {
|
|
|
243
272
|
lines.push('No projects scanned yet. Run `claude-memory scan` first.');
|
|
244
273
|
return lines.join('\n');
|
|
245
274
|
}
|
|
275
|
+
// Check for stale data
|
|
276
|
+
const hoursSinceUpdate = (Date.now() - new Date(context.lastUpdated).getTime()) / (1000 * 60 * 60);
|
|
246
277
|
lines.push('# Your Development Environment');
|
|
247
278
|
lines.push('');
|
|
248
|
-
lines.push(`Last scanned: ${new Date(context.lastUpdated).toLocaleString()}`);
|
|
279
|
+
lines.push(`Last scanned: ${new Date(context.lastUpdated).toLocaleString()} | claude-memory v${CURRENT_VERSION}`);
|
|
280
|
+
// Run detailed staleness check
|
|
281
|
+
const stalenessReport = detectStaleProjects(context);
|
|
282
|
+
// Add staleness warnings if needed
|
|
283
|
+
if (stalenessReport.staleProjects.length > 0) {
|
|
284
|
+
lines.push('');
|
|
285
|
+
lines.push(formatStalenessForMcp(stalenessReport));
|
|
286
|
+
}
|
|
287
|
+
else if (hoursSinceUpdate > 24) {
|
|
288
|
+
lines.push('');
|
|
289
|
+
lines.push(`> ā ļø **Data may be stale** (${Math.floor(hoursSinceUpdate)} hours old). Run \`claude-memory scan\` to refresh.`);
|
|
290
|
+
}
|
|
249
291
|
lines.push('');
|
|
250
292
|
lines.push('## Active Projects');
|
|
251
293
|
lines.push('');
|
|
@@ -344,6 +386,35 @@ function handleRecordInsight(content, relatedProjects) {
|
|
|
344
386
|
return `Failed to record insight: ${err instanceof Error ? err.message : 'Unknown error'}`;
|
|
345
387
|
}
|
|
346
388
|
}
|
|
389
|
+
/**
|
|
390
|
+
* Pro feature: Search code across all projects
|
|
391
|
+
*/
|
|
392
|
+
function handleSearchCode(query, filePattern) {
|
|
393
|
+
// Check Pro license
|
|
394
|
+
if (!isPro()) {
|
|
395
|
+
return getProFeatureMessage('Cross-project code search');
|
|
396
|
+
}
|
|
397
|
+
const context = loadContext();
|
|
398
|
+
if (!context) {
|
|
399
|
+
return 'No projects scanned yet. Run `claude-memory scan` first.';
|
|
400
|
+
}
|
|
401
|
+
const lines = [
|
|
402
|
+
`# Code Search Results for "${query}"`,
|
|
403
|
+
'',
|
|
404
|
+
];
|
|
405
|
+
// TODO: Implement actual code search
|
|
406
|
+
// For now, return a placeholder that shows the feature is gated
|
|
407
|
+
lines.push('Searching across projects:');
|
|
408
|
+
for (const p of context.projects) {
|
|
409
|
+
lines.push(`- ${p.name} (${p.path})`);
|
|
410
|
+
}
|
|
411
|
+
lines.push('');
|
|
412
|
+
lines.push(`Pattern: ${filePattern || '*'}`);
|
|
413
|
+
lines.push('');
|
|
414
|
+
lines.push('> Note: Full code search implementation coming soon.');
|
|
415
|
+
lines.push('> This will index and search actual file contents across all your projects.');
|
|
416
|
+
return lines.join('\n');
|
|
417
|
+
}
|
|
347
418
|
function handleGetProjectAnalysis(projectQuery) {
|
|
348
419
|
const context = loadContext();
|
|
349
420
|
if (!context) {
|
|
@@ -512,6 +583,9 @@ async function main() {
|
|
|
512
583
|
case 'get_project_analysis':
|
|
513
584
|
result = handleGetProjectAnalysis(args.project);
|
|
514
585
|
break;
|
|
586
|
+
case 'search_code':
|
|
587
|
+
result = handleSearchCode(args.query, args.filePattern);
|
|
588
|
+
break;
|
|
515
589
|
default:
|
|
516
590
|
throw new Error(`Unknown tool: ${name}`);
|
|
517
591
|
}
|
|
@@ -2,12 +2,19 @@
|
|
|
2
2
|
* Context Generator - Produces markdown context files
|
|
3
3
|
*/
|
|
4
4
|
import { ProjectInfo, GlobalContext } from '../types/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* Load existing context from disk (if any)
|
|
7
|
+
*/
|
|
8
|
+
export declare function loadExistingContext(): GlobalContext | null;
|
|
5
9
|
/**
|
|
6
10
|
* Generate global context from scanned projects
|
|
11
|
+
* Preserves existing insights and user patterns
|
|
7
12
|
*/
|
|
8
13
|
export declare function generateGlobalContext(projects: ProjectInfo[]): GlobalContext;
|
|
9
14
|
/**
|
|
10
15
|
* Write global context to ~/.claude-memory/
|
|
16
|
+
* Uses atomic write (write to temp, then rename) to prevent corruption
|
|
17
|
+
* Creates backup before overwriting
|
|
11
18
|
*/
|
|
12
19
|
export declare function writeGlobalContext(context: GlobalContext): void;
|
|
13
20
|
/**
|