@contextmirror/claude-memory 0.4.1 → 0.4.3
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 +196 -52
- 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 +51 -14
- 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 +29 -2
- package/landing/index.html +0 -764
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')
|
|
@@ -37,7 +44,10 @@ program
|
|
|
37
44
|
if (options.check) {
|
|
38
45
|
const context = loadContext();
|
|
39
46
|
if (!context) {
|
|
40
|
-
console.log('No projects scanned yet
|
|
47
|
+
console.log('❌ No projects scanned yet.\n');
|
|
48
|
+
console.log('Get started:');
|
|
49
|
+
console.log(' claude-memory setup # Interactive wizard (recommended)');
|
|
50
|
+
console.log(' claude-memory scan ~/Projects # Scan a specific directory');
|
|
41
51
|
return;
|
|
42
52
|
}
|
|
43
53
|
const report = detectStaleProjects(context);
|
|
@@ -119,7 +129,10 @@ program
|
|
|
119
129
|
.action(() => {
|
|
120
130
|
const context = loadContext();
|
|
121
131
|
if (!context) {
|
|
122
|
-
console.log('No projects scanned yet
|
|
132
|
+
console.log('❌ No projects scanned yet.\n');
|
|
133
|
+
console.log('Get started:');
|
|
134
|
+
console.log(' claude-memory setup # Interactive wizard (recommended)');
|
|
135
|
+
console.log(' claude-memory scan ~/Projects # Scan a specific directory');
|
|
123
136
|
return;
|
|
124
137
|
}
|
|
125
138
|
console.log('🧠 Known Projects\n');
|
|
@@ -139,14 +152,27 @@ program
|
|
|
139
152
|
.action((projectName) => {
|
|
140
153
|
const context = loadContext();
|
|
141
154
|
if (!context) {
|
|
142
|
-
console.log('No projects scanned yet
|
|
155
|
+
console.log('❌ No projects scanned yet.\n');
|
|
156
|
+
console.log('Get started:');
|
|
157
|
+
console.log(' claude-memory setup # Interactive wizard (recommended)');
|
|
158
|
+
console.log(' claude-memory scan ~/Projects # Scan a specific directory');
|
|
143
159
|
return;
|
|
144
160
|
}
|
|
145
161
|
const project = context.projects.find((p) => p.name.toLowerCase() === projectName.toLowerCase() || p.path.includes(projectName));
|
|
146
162
|
if (!project) {
|
|
147
|
-
console.log(
|
|
148
|
-
console.log('
|
|
149
|
-
|
|
163
|
+
console.log(`❌ Project not found: "${projectName}"\n`);
|
|
164
|
+
console.log('Did you mean one of these?\n');
|
|
165
|
+
// Show projects sorted by similarity to the query
|
|
166
|
+
const lowerQuery = projectName.toLowerCase();
|
|
167
|
+
const sorted = [...context.projects].sort((a, b) => {
|
|
168
|
+
const aScore = a.name.toLowerCase().includes(lowerQuery) ? 1 : 0;
|
|
169
|
+
const bScore = b.name.toLowerCase().includes(lowerQuery) ? 1 : 0;
|
|
170
|
+
return bScore - aScore;
|
|
171
|
+
});
|
|
172
|
+
sorted.slice(0, 5).forEach((p) => console.log(` ${p.name} (${p.path})`));
|
|
173
|
+
if (context.projects.length > 5) {
|
|
174
|
+
console.log(`\n ... and ${context.projects.length - 5} more. Run 'claude-memory list' to see all.`);
|
|
175
|
+
}
|
|
150
176
|
return;
|
|
151
177
|
}
|
|
152
178
|
console.log(`\n📁 ${project.name}\n`);
|
|
@@ -168,7 +194,10 @@ program
|
|
|
168
194
|
.action((projectQuery) => {
|
|
169
195
|
const context = loadContext();
|
|
170
196
|
if (!context) {
|
|
171
|
-
console.log('No projects scanned yet
|
|
197
|
+
console.log('❌ No projects scanned yet.\n');
|
|
198
|
+
console.log('Get started:');
|
|
199
|
+
console.log(' claude-memory setup # Interactive wizard (recommended)');
|
|
200
|
+
console.log(' claude-memory scan ~/Projects # Scan a specific directory');
|
|
172
201
|
return;
|
|
173
202
|
}
|
|
174
203
|
// Find the project
|
|
@@ -181,7 +210,8 @@ program
|
|
|
181
210
|
console.log(' This project will be skipped in future scans.');
|
|
182
211
|
return;
|
|
183
212
|
}
|
|
184
|
-
console.log(
|
|
213
|
+
console.log(`❌ Project not found: "${projectQuery}"\n`);
|
|
214
|
+
console.log('Tip: Use the project name or path. Run "claude-memory list" to see all projects.');
|
|
185
215
|
return;
|
|
186
216
|
}
|
|
187
217
|
addExclusion(project.path);
|
|
@@ -198,16 +228,23 @@ program
|
|
|
198
228
|
.action((projectQuery) => {
|
|
199
229
|
const context = loadContext();
|
|
200
230
|
if (!context) {
|
|
201
|
-
console.log('No projects scanned yet
|
|
231
|
+
console.log('❌ No projects scanned yet.\n');
|
|
232
|
+
console.log('Get started:');
|
|
233
|
+
console.log(' claude-memory setup # Interactive wizard (recommended)');
|
|
234
|
+
console.log(' claude-memory scan ~/Projects # Scan a specific directory');
|
|
202
235
|
return;
|
|
203
236
|
}
|
|
204
237
|
const excluded = context.excludedProjects || [];
|
|
205
238
|
const match = excluded.find(p => p.toLowerCase().includes(projectQuery.toLowerCase()));
|
|
206
239
|
if (!match) {
|
|
207
|
-
console.log(
|
|
240
|
+
console.log(`❌ Project not in exclusion list: "${projectQuery}"\n`);
|
|
208
241
|
if (excluded.length > 0) {
|
|
209
|
-
console.log('
|
|
210
|
-
excluded.forEach(p => console.log(`
|
|
242
|
+
console.log('Currently excluded:');
|
|
243
|
+
excluded.forEach(p => console.log(` ${p}`));
|
|
244
|
+
console.log('\nRun "claude-memory excluded" to see all excluded projects.');
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
console.log('No projects are currently excluded.');
|
|
211
248
|
}
|
|
212
249
|
return;
|
|
213
250
|
}
|
|
@@ -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;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code search utility for Pro feature
|
|
3
|
+
*/
|
|
4
|
+
import { readdirSync, readFileSync, statSync } from 'fs';
|
|
5
|
+
import { join, extname, relative } from 'path';
|
|
6
|
+
import { IGNORE_DIRS } from '../constants.js';
|
|
7
|
+
const DEFAULT_OPTIONS = {
|
|
8
|
+
maxResults: 50,
|
|
9
|
+
caseSensitive: false,
|
|
10
|
+
contextLines: 1,
|
|
11
|
+
};
|
|
12
|
+
// File extensions to search
|
|
13
|
+
const SEARCHABLE_EXTENSIONS = new Set([
|
|
14
|
+
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
|
|
15
|
+
'.py', '.pyw',
|
|
16
|
+
'.rs',
|
|
17
|
+
'.go',
|
|
18
|
+
'.java', '.kt', '.scala',
|
|
19
|
+
'.c', '.cpp', '.h', '.hpp',
|
|
20
|
+
'.rb',
|
|
21
|
+
'.php',
|
|
22
|
+
'.swift',
|
|
23
|
+
'.cs',
|
|
24
|
+
'.vue', '.svelte',
|
|
25
|
+
'.json', '.yaml', '.yml', '.toml',
|
|
26
|
+
'.md', '.txt',
|
|
27
|
+
'.sql',
|
|
28
|
+
'.sh', '.bash', '.zsh',
|
|
29
|
+
'.css', '.scss', '.sass', '.less',
|
|
30
|
+
'.html', '.htm',
|
|
31
|
+
'.xml',
|
|
32
|
+
]);
|
|
33
|
+
// Max file size to search (1MB)
|
|
34
|
+
const MAX_FILE_SIZE = 1024 * 1024;
|
|
35
|
+
/**
|
|
36
|
+
* Check if a file should be searched based on extension
|
|
37
|
+
*/
|
|
38
|
+
function shouldSearchFile(filePath, filePattern) {
|
|
39
|
+
const ext = extname(filePath).toLowerCase();
|
|
40
|
+
// If a pattern is specified, use glob-like matching
|
|
41
|
+
if (filePattern) {
|
|
42
|
+
if (filePattern.startsWith('*.')) {
|
|
43
|
+
const patternExt = filePattern.slice(1);
|
|
44
|
+
return ext === patternExt;
|
|
45
|
+
}
|
|
46
|
+
return filePath.includes(filePattern);
|
|
47
|
+
}
|
|
48
|
+
return SEARCHABLE_EXTENSIONS.has(ext);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Search for a pattern in a file
|
|
52
|
+
*/
|
|
53
|
+
function searchFile(filePath, query, options) {
|
|
54
|
+
const results = [];
|
|
55
|
+
try {
|
|
56
|
+
const stat = statSync(filePath);
|
|
57
|
+
if (stat.size > MAX_FILE_SIZE) {
|
|
58
|
+
return results;
|
|
59
|
+
}
|
|
60
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
61
|
+
const lines = content.split('\n');
|
|
62
|
+
const searchQuery = options.caseSensitive ? query : query.toLowerCase();
|
|
63
|
+
for (let i = 0; i < lines.length; i++) {
|
|
64
|
+
const line = lines[i];
|
|
65
|
+
const searchLine = options.caseSensitive ? line : line.toLowerCase();
|
|
66
|
+
if (searchLine.includes(searchQuery)) {
|
|
67
|
+
const contextLines = options.contextLines || 1;
|
|
68
|
+
const beforeLines = lines.slice(Math.max(0, i - contextLines), i);
|
|
69
|
+
const afterLines = lines.slice(i + 1, Math.min(lines.length, i + 1 + contextLines));
|
|
70
|
+
results.push({
|
|
71
|
+
line: i + 1,
|
|
72
|
+
content: line.trim(),
|
|
73
|
+
context: {
|
|
74
|
+
before: beforeLines.join('\n'),
|
|
75
|
+
after: afterLines.join('\n'),
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// Skip files that can't be read
|
|
83
|
+
}
|
|
84
|
+
return results;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Recursively search a directory
|
|
88
|
+
*/
|
|
89
|
+
function searchDirectory(dir, query, projectName, projectPath, options, results) {
|
|
90
|
+
if (results.length >= (options.maxResults || DEFAULT_OPTIONS.maxResults)) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const entries = readdirSync(dir);
|
|
95
|
+
for (const entry of entries) {
|
|
96
|
+
if (results.length >= (options.maxResults || DEFAULT_OPTIONS.maxResults)) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (IGNORE_DIRS.includes(entry) || entry.startsWith('.')) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const fullPath = join(dir, entry);
|
|
103
|
+
try {
|
|
104
|
+
const stat = statSync(fullPath);
|
|
105
|
+
if (stat.isDirectory()) {
|
|
106
|
+
searchDirectory(fullPath, query, projectName, projectPath, options, results);
|
|
107
|
+
}
|
|
108
|
+
else if (stat.isFile() && shouldSearchFile(fullPath, options.filePattern)) {
|
|
109
|
+
const fileResults = searchFile(fullPath, query, options);
|
|
110
|
+
for (const result of fileResults) {
|
|
111
|
+
if (results.length >= (options.maxResults || DEFAULT_OPTIONS.maxResults)) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
results.push({
|
|
115
|
+
project: projectName,
|
|
116
|
+
projectPath,
|
|
117
|
+
file: fullPath,
|
|
118
|
+
relativePath: relative(projectPath, fullPath),
|
|
119
|
+
line: result.line,
|
|
120
|
+
content: result.content,
|
|
121
|
+
context: result.context,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Skip inaccessible files
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// Skip inaccessible directories
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Search for code across multiple projects
|
|
137
|
+
*/
|
|
138
|
+
export function searchCode(projects, query, options = {}) {
|
|
139
|
+
const mergedOptions = { ...DEFAULT_OPTIONS, ...options };
|
|
140
|
+
const results = [];
|
|
141
|
+
for (const project of projects) {
|
|
142
|
+
if (results.length >= mergedOptions.maxResults) {
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
searchDirectory(project.path, query, project.name, project.path, mergedOptions, results);
|
|
146
|
+
}
|
|
147
|
+
return results;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Format search results for display
|
|
151
|
+
*/
|
|
152
|
+
export function formatSearchResults(results, query) {
|
|
153
|
+
if (results.length === 0) {
|
|
154
|
+
return `No results found for "${query}".`;
|
|
155
|
+
}
|
|
156
|
+
const lines = [
|
|
157
|
+
`# Code Search Results for "${query}"`,
|
|
158
|
+
'',
|
|
159
|
+
`Found ${results.length} result${results.length === 1 ? '' : 's'}:`,
|
|
160
|
+
'',
|
|
161
|
+
];
|
|
162
|
+
// Group by project
|
|
163
|
+
const byProject = new Map();
|
|
164
|
+
for (const result of results) {
|
|
165
|
+
const existing = byProject.get(result.project) || [];
|
|
166
|
+
existing.push(result);
|
|
167
|
+
byProject.set(result.project, existing);
|
|
168
|
+
}
|
|
169
|
+
for (const [project, projectResults] of byProject) {
|
|
170
|
+
lines.push(`## ${project}`);
|
|
171
|
+
lines.push('');
|
|
172
|
+
for (const result of projectResults) {
|
|
173
|
+
lines.push(`### ${result.relativePath}:${result.line}`);
|
|
174
|
+
lines.push('```');
|
|
175
|
+
if (result.context.before) {
|
|
176
|
+
lines.push(result.context.before);
|
|
177
|
+
}
|
|
178
|
+
lines.push(`> ${result.content}`);
|
|
179
|
+
if (result.context.after) {
|
|
180
|
+
lines.push(result.context.after);
|
|
181
|
+
}
|
|
182
|
+
lines.push('```');
|
|
183
|
+
lines.push('');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return lines.join('\n');
|
|
187
|
+
}
|