claude-mem 3.2.0 → 3.2.1
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/claude-mem +0 -0
- package/package.json +1 -2
- package/dist/bin/cli.d.ts +0 -2
- package/dist/bin/cli.js +0 -129
- package/dist/commands/compress.d.ts +0 -2
- package/dist/commands/compress.js +0 -27
- package/dist/commands/hooks.d.ts +0 -19
- package/dist/commands/hooks.js +0 -131
- package/dist/commands/install.d.ts +0 -2
- package/dist/commands/install.js +0 -649
- package/dist/commands/load-context.d.ts +0 -2
- package/dist/commands/load-context.js +0 -108
- package/dist/commands/logs.d.ts +0 -2
- package/dist/commands/logs.js +0 -76
- package/dist/commands/migrate-to-jsonl.d.ts +0 -5
- package/dist/commands/migrate-to-jsonl.js +0 -99
- package/dist/commands/status.d.ts +0 -1
- package/dist/commands/status.js +0 -136
- package/dist/commands/uninstall.d.ts +0 -2
- package/dist/commands/uninstall.js +0 -107
- package/dist/constants.d.ts +0 -271
- package/dist/constants.js +0 -199
- package/dist/core/compression/TranscriptCompressor.d.ts +0 -83
- package/dist/core/compression/TranscriptCompressor.js +0 -602
- package/dist/core/orchestration/PromptOrchestrator.d.ts +0 -165
- package/dist/core/orchestration/PromptOrchestrator.js +0 -182
- package/dist/lib/time-utils.d.ts +0 -5
- package/dist/lib/time-utils.js +0 -70
- package/dist/prompts/constants.d.ts +0 -126
- package/dist/prompts/constants.js +0 -161
- package/dist/prompts/index.d.ts +0 -10
- package/dist/prompts/index.js +0 -11
- package/dist/prompts/templates/analysis/AnalysisTemplates.d.ts +0 -13
- package/dist/prompts/templates/analysis/AnalysisTemplates.js +0 -94
- package/dist/prompts/templates/context/ContextTemplates.d.ts +0 -119
- package/dist/prompts/templates/context/ContextTemplates.js +0 -399
- package/dist/prompts/templates/hooks/HookTemplates.d.ts +0 -175
- package/dist/prompts/templates/hooks/HookTemplates.js +0 -394
- package/dist/prompts/templates/hooks/HookTemplates.test.d.ts +0 -7
- package/dist/prompts/templates/hooks/HookTemplates.test.js +0 -127
- package/dist/shared/config.d.ts +0 -4
- package/dist/shared/config.js +0 -41
- package/dist/shared/error-handler.d.ts +0 -22
- package/dist/shared/error-handler.js +0 -142
- package/dist/shared/logger.d.ts +0 -19
- package/dist/shared/logger.js +0 -51
- package/dist/shared/paths.d.ts +0 -28
- package/dist/shared/paths.js +0 -100
- package/dist/shared/types.d.ts +0 -141
- package/dist/shared/types.js +0 -78
package/dist/commands/install.js
DELETED
|
@@ -1,649 +0,0 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, existsSync, chmodSync, mkdirSync, copyFileSync, statSync, readdirSync } from 'fs';
|
|
2
|
-
import { join, dirname } from 'path';
|
|
3
|
-
import { homedir } from 'os';
|
|
4
|
-
import { execSync } from 'child_process';
|
|
5
|
-
import * as p from '@clack/prompts';
|
|
6
|
-
import { PACKAGE_NAME } from '../shared/config.js';
|
|
7
|
-
// <Block> Prerequisites validation
|
|
8
|
-
async function validatePrerequisites() {
|
|
9
|
-
const s = p.spinner();
|
|
10
|
-
s.start('Checking prerequisites');
|
|
11
|
-
const issues = [];
|
|
12
|
-
// Check Node.js version
|
|
13
|
-
const nodeVersion = process.versions.node;
|
|
14
|
-
const [major] = nodeVersion.split('.').map(Number);
|
|
15
|
-
if (major < 18) {
|
|
16
|
-
issues.push(`Node.js version ${nodeVersion} is below minimum required (18.0.0)`);
|
|
17
|
-
}
|
|
18
|
-
// Check if Claude Code is installed
|
|
19
|
-
try {
|
|
20
|
-
execSync('which claude', { stdio: 'ignore' });
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
issues.push('Claude Code CLI is not installed or not in PATH');
|
|
24
|
-
}
|
|
25
|
-
// Check write permissions to home directory
|
|
26
|
-
const testDir = join(homedir(), '.claude-mem-test');
|
|
27
|
-
try {
|
|
28
|
-
mkdirSync(testDir, { recursive: true });
|
|
29
|
-
writeFileSync(join(testDir, 'test'), 'test');
|
|
30
|
-
execSync(`rm -rf ${testDir}`, { stdio: 'ignore' });
|
|
31
|
-
}
|
|
32
|
-
catch {
|
|
33
|
-
issues.push('No write permissions to home directory');
|
|
34
|
-
}
|
|
35
|
-
if (issues.length > 0) {
|
|
36
|
-
s.stop('Prerequisites check failed');
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
s.stop('Prerequisites check passed');
|
|
40
|
-
return true;
|
|
41
|
-
}
|
|
42
|
-
// </Block>
|
|
43
|
-
// <Block> Installation status detection
|
|
44
|
-
function detectExistingInstallation() {
|
|
45
|
-
const result = {
|
|
46
|
-
hasHooks: false,
|
|
47
|
-
hasChromaMcp: false,
|
|
48
|
-
hasSettings: false,
|
|
49
|
-
scope: undefined
|
|
50
|
-
};
|
|
51
|
-
// Check for hooks
|
|
52
|
-
const hooksDir = join(homedir(), '.claude-mem', 'hooks');
|
|
53
|
-
result.hasHooks = existsSync(hooksDir) &&
|
|
54
|
-
existsSync(join(hooksDir, 'pre-compact.js')) &&
|
|
55
|
-
existsSync(join(hooksDir, 'session-start.js'));
|
|
56
|
-
// Check for Chroma MCP server configuration
|
|
57
|
-
const userMcpPath = join(homedir(), '.claude.json');
|
|
58
|
-
const projectMcpPath = join(process.cwd(), '.mcp.json');
|
|
59
|
-
if (existsSync(userMcpPath)) {
|
|
60
|
-
try {
|
|
61
|
-
const config = JSON.parse(readFileSync(userMcpPath, 'utf8'));
|
|
62
|
-
if (config.mcpServers?.['claude-mem']) {
|
|
63
|
-
result.hasChromaMcp = true;
|
|
64
|
-
result.scope = 'user';
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
catch { }
|
|
68
|
-
}
|
|
69
|
-
if (existsSync(projectMcpPath)) {
|
|
70
|
-
try {
|
|
71
|
-
const config = JSON.parse(readFileSync(projectMcpPath, 'utf8'));
|
|
72
|
-
if (config.mcpServers?.['claude-mem']) {
|
|
73
|
-
result.hasChromaMcp = true;
|
|
74
|
-
result.scope = 'project';
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
catch { }
|
|
78
|
-
}
|
|
79
|
-
// Check for settings
|
|
80
|
-
const userSettingsPath = join(homedir(), '.claude-mem', 'settings.json');
|
|
81
|
-
result.hasSettings = existsSync(userSettingsPath);
|
|
82
|
-
return result;
|
|
83
|
-
}
|
|
84
|
-
// </Block>
|
|
85
|
-
// <Block> Interactive installation wizard
|
|
86
|
-
async function runInstallationWizard(existingInstall) {
|
|
87
|
-
const config = {};
|
|
88
|
-
// If existing installation found, ask about reinstallation
|
|
89
|
-
if (existingInstall.hasHooks || existingInstall.hasChromaMcp) {
|
|
90
|
-
const action = await p.select({
|
|
91
|
-
message: 'Existing installation detected. What would you like to do?',
|
|
92
|
-
options: [
|
|
93
|
-
{ value: 'update', label: 'Update existing installation' },
|
|
94
|
-
{ value: 'reinstall', label: 'Clean reinstall (removes existing configuration)' },
|
|
95
|
-
{ value: 'cancel', label: 'Cancel installation' }
|
|
96
|
-
]
|
|
97
|
-
});
|
|
98
|
-
if (p.isCancel(action) || action === 'cancel') {
|
|
99
|
-
p.cancel('Installation cancelled');
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
config.forceReinstall = action === 'reinstall';
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
config.forceReinstall = false;
|
|
106
|
-
}
|
|
107
|
-
// Select installation scope
|
|
108
|
-
const scope = await p.select({
|
|
109
|
-
message: 'Select installation scope',
|
|
110
|
-
options: [
|
|
111
|
-
{
|
|
112
|
-
value: 'user',
|
|
113
|
-
label: 'User (Recommended)',
|
|
114
|
-
hint: 'Install for current user (~/.claude)'
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
value: 'project',
|
|
118
|
-
label: 'Project',
|
|
119
|
-
hint: 'Install for current project only (./.mcp.json)'
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
value: 'local',
|
|
123
|
-
label: 'Local',
|
|
124
|
-
hint: 'Custom local installation'
|
|
125
|
-
}
|
|
126
|
-
],
|
|
127
|
-
initialValue: existingInstall.scope || 'user'
|
|
128
|
-
});
|
|
129
|
-
if (p.isCancel(scope)) {
|
|
130
|
-
p.cancel('Installation cancelled');
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
config.scope = scope;
|
|
134
|
-
// If local scope, ask for custom path
|
|
135
|
-
if (scope === 'local') {
|
|
136
|
-
const customPath = await p.text({
|
|
137
|
-
message: 'Enter custom installation directory',
|
|
138
|
-
placeholder: join(process.cwd(), '.claude-mem'),
|
|
139
|
-
validate: (value) => {
|
|
140
|
-
if (!value)
|
|
141
|
-
return 'Path is required';
|
|
142
|
-
if (!value.startsWith('/') && !value.startsWith('~')) {
|
|
143
|
-
return 'Please provide an absolute path';
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
if (p.isCancel(customPath)) {
|
|
148
|
-
p.cancel('Installation cancelled');
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
151
|
-
config.customPath = customPath;
|
|
152
|
-
}
|
|
153
|
-
// Use default hook timeout (3 minutes)
|
|
154
|
-
config.hookTimeout = 180000;
|
|
155
|
-
// Ask about Chroma MCP installation
|
|
156
|
-
if (!existingInstall.hasChromaMcp) {
|
|
157
|
-
const installChroma = await p.confirm({
|
|
158
|
-
message: 'Install Chroma MCP server for vector storage?',
|
|
159
|
-
initialValue: true
|
|
160
|
-
});
|
|
161
|
-
if (p.isCancel(installChroma)) {
|
|
162
|
-
p.cancel('Installation cancelled');
|
|
163
|
-
return null;
|
|
164
|
-
}
|
|
165
|
-
config.skipMcpInstall = !installChroma;
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
config.skipMcpInstall = true; // Already installed
|
|
169
|
-
}
|
|
170
|
-
return config;
|
|
171
|
-
}
|
|
172
|
-
// </Block>
|
|
173
|
-
// <Block> Backup existing configuration
|
|
174
|
-
async function backupExistingConfig() {
|
|
175
|
-
const backupDir = join(homedir(), '.claude-mem', 'backups', new Date().toISOString().replace(/[:.]/g, '-'));
|
|
176
|
-
try {
|
|
177
|
-
mkdirSync(backupDir, { recursive: true });
|
|
178
|
-
// Backup hooks if they exist
|
|
179
|
-
const hooksDir = join(homedir(), '.claude-mem', 'hooks');
|
|
180
|
-
if (existsSync(hooksDir)) {
|
|
181
|
-
copyFileRecursively(hooksDir, join(backupDir, 'hooks'));
|
|
182
|
-
}
|
|
183
|
-
// Backup settings
|
|
184
|
-
const settingsPath = join(homedir(), '.claude-mem', 'settings.json');
|
|
185
|
-
if (existsSync(settingsPath)) {
|
|
186
|
-
copyFileSync(settingsPath, join(backupDir, 'settings.json'));
|
|
187
|
-
}
|
|
188
|
-
// Backup Claude settings
|
|
189
|
-
const claudeSettingsPath = join(homedir(), '.claude', 'settings.json');
|
|
190
|
-
if (existsSync(claudeSettingsPath)) {
|
|
191
|
-
copyFileSync(claudeSettingsPath, join(backupDir, 'claude-settings.json'));
|
|
192
|
-
}
|
|
193
|
-
return backupDir;
|
|
194
|
-
}
|
|
195
|
-
catch (error) {
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
// </Block>
|
|
200
|
-
// <Block> Directory structure creation - natural setup flow
|
|
201
|
-
function ensureDirectoryStructure() {
|
|
202
|
-
const claudeMemDir = join(homedir(), '.claude-mem');
|
|
203
|
-
const hooksDir = join(claudeMemDir, 'hooks');
|
|
204
|
-
const indexDir = join(claudeMemDir, 'index');
|
|
205
|
-
const archivesDir = join(claudeMemDir, 'archives');
|
|
206
|
-
const logsDir = join(claudeMemDir, 'logs');
|
|
207
|
-
const backupsDir = join(claudeMemDir, 'backups');
|
|
208
|
-
[claudeMemDir, hooksDir, indexDir, archivesDir, logsDir, backupsDir].forEach(dir => {
|
|
209
|
-
if (!existsSync(dir)) {
|
|
210
|
-
mkdirSync(dir, { recursive: true });
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
// </Block>
|
|
215
|
-
function copyFileRecursively(src, dest) {
|
|
216
|
-
const stat = statSync(src);
|
|
217
|
-
if (stat.isDirectory()) {
|
|
218
|
-
if (!existsSync(dest)) {
|
|
219
|
-
mkdirSync(dest, { recursive: true });
|
|
220
|
-
}
|
|
221
|
-
const files = readdirSync(src);
|
|
222
|
-
files.forEach((file) => {
|
|
223
|
-
copyFileRecursively(join(src, file), join(dest, file));
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
else {
|
|
227
|
-
copyFileSync(src, dest);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
function writeHookFiles(timeout = 180000) {
|
|
231
|
-
const hooksDir = join(homedir(), '.claude-mem', 'hooks');
|
|
232
|
-
// For binary distribution, write minimal wrapper hooks that call the binary
|
|
233
|
-
const hookWrapper = (command) => `#!/usr/bin/env node
|
|
234
|
-
// Hook wrapper for claude-mem binary distribution
|
|
235
|
-
const { execSync } = require('child_process');
|
|
236
|
-
const path = require('path');
|
|
237
|
-
|
|
238
|
-
// Try to find the claude-mem binary
|
|
239
|
-
const possiblePaths = [
|
|
240
|
-
'claude-mem', // In PATH
|
|
241
|
-
path.join(__dirname, '..', '..', 'claude-mem'), // Relative to hooks dir
|
|
242
|
-
process.env.CLAUDE_MEM_BINARY // Environment variable override
|
|
243
|
-
].filter(Boolean);
|
|
244
|
-
|
|
245
|
-
let binaryPath;
|
|
246
|
-
for (const testPath of possiblePaths) {
|
|
247
|
-
try {
|
|
248
|
-
execSync(\`which \${testPath}\`, { stdio: 'ignore' });
|
|
249
|
-
binaryPath = testPath;
|
|
250
|
-
break;
|
|
251
|
-
} catch {
|
|
252
|
-
// Try next path
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (!binaryPath) {
|
|
257
|
-
console.error('Could not find claude-mem binary');
|
|
258
|
-
process.exit(1);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
try {
|
|
262
|
-
execSync(\`\${binaryPath} ${command}\`, { stdio: 'inherit' });
|
|
263
|
-
process.exit(0);
|
|
264
|
-
} catch (error) {
|
|
265
|
-
process.exit(error.status || 1);
|
|
266
|
-
}
|
|
267
|
-
`;
|
|
268
|
-
// Write hook wrappers
|
|
269
|
-
const hooks = [
|
|
270
|
-
{ name: 'pre-compact.js', command: 'hook:pre-compact' },
|
|
271
|
-
{ name: 'session-start.js', command: 'hook:session-start' },
|
|
272
|
-
{ name: 'session-end.js', command: 'hook:session-end' }
|
|
273
|
-
];
|
|
274
|
-
for (const hook of hooks) {
|
|
275
|
-
const hookPath = join(hooksDir, hook.name);
|
|
276
|
-
writeFileSync(hookPath, hookWrapper(hook.command));
|
|
277
|
-
chmodSync(hookPath, 0o755);
|
|
278
|
-
}
|
|
279
|
-
// Write configuration with custom timeout
|
|
280
|
-
const hookConfigPath = join(hooksDir, 'config.json');
|
|
281
|
-
const hookConfig = {
|
|
282
|
-
packageName: PACKAGE_NAME,
|
|
283
|
-
cliCommand: PACKAGE_NAME,
|
|
284
|
-
backend: 'chroma',
|
|
285
|
-
timeout
|
|
286
|
-
};
|
|
287
|
-
writeFileSync(hookConfigPath, JSON.stringify(hookConfig, null, 2));
|
|
288
|
-
}
|
|
289
|
-
function ensureClaudeMdInstructions() {
|
|
290
|
-
const claudeMdPath = join(homedir(), '.claude', 'CLAUDE.md');
|
|
291
|
-
const claudeMdDir = dirname(claudeMdPath);
|
|
292
|
-
// Ensure .claude directory exists
|
|
293
|
-
if (!existsSync(claudeMdDir)) {
|
|
294
|
-
mkdirSync(claudeMdDir, { recursive: true });
|
|
295
|
-
}
|
|
296
|
-
const instructions = `
|
|
297
|
-
<!-- CLAUDE-MEM INSTRUCTIONS -->
|
|
298
|
-
- You have access to a persistent memory system via the Chroma MCP server (installed as "claude-mem")
|
|
299
|
-
- The memory system automatically compresses and stores context from your sessions
|
|
300
|
-
- Available MCP tools:
|
|
301
|
-
- \`mcp__claude-mem__chroma_add_documents\`: Store new knowledge as documents in the "claude_memories" collection
|
|
302
|
-
- \`mcp__claude-mem__chroma_query_documents\`: Search for relevant memories using semantic search in the "claude_memories" collection
|
|
303
|
-
- \`mcp__claude-mem__chroma_get_documents\`: Retrieve specific documents by IDs from the "claude_memories" collection
|
|
304
|
-
- **Document Structure:**
|
|
305
|
-
- Documents are stored as natural language descriptions of knowledge
|
|
306
|
-
- Each document has metadata including: timestamp, session_id, keywords, entity_type
|
|
307
|
-
- Use descriptive content that captures the essence of what was learned or accomplished
|
|
308
|
-
- **Search Tips:**
|
|
309
|
-
- Use semantic queries: \`chroma_query_documents\` with natural language queries
|
|
310
|
-
- Search by metadata: Use \`where\` parameter to filter by entity_type, timestamp, etc.
|
|
311
|
-
- Use keywords in metadata for precise filtering
|
|
312
|
-
- **Storage Format:**
|
|
313
|
-
- Store knowledge as readable documents rather than structured entities
|
|
314
|
-
- Include context, rationale, and outcomes in document content
|
|
315
|
-
- Use metadata to categorize and filter memories
|
|
316
|
-
- **Smart Retrieval Commands:**
|
|
317
|
-
- **Find similar concepts**: \`chroma_query_documents(["your search terms"])\`
|
|
318
|
-
- Example: \`chroma_query_documents(["websocket", "connection"])\` - finds all websocket-related memories
|
|
319
|
-
- **Load specific memory**: \`chroma_get_documents(["document_id"])\`
|
|
320
|
-
- Example: \`chroma_get_documents(["project_session_1"])\` - loads exact memory by ID
|
|
321
|
-
- **Search by metadata**: Use keywords from memories to find related items
|
|
322
|
-
- The system uses semantic search, so "auth" will find "authentication", "login", etc.
|
|
323
|
-
- **Find connected memories**: Look for memories with related_to fields linking them together
|
|
324
|
-
- Collection name: "claude_memories"
|
|
325
|
-
- Compressed session archives are stored in ~/.claude-mem/archives/
|
|
326
|
-
- **Optimal Search Strategies:**
|
|
327
|
-
- **Most Effective Patterns:**
|
|
328
|
-
- Session continuity: \`where: {session_id: "xxx"}\` - precise retrieval of all session work
|
|
329
|
-
- Find specific fixes: \`where: {type: "fix"}\` - filters to only bug fixes
|
|
330
|
-
- Natural language queries: "fixing overview display in templates" beats "fix AND overview AND template"
|
|
331
|
-
- Include file/function names: "extractOverview ContextTemplates" for precise code location
|
|
332
|
-
- **Search by Intent:**
|
|
333
|
-
- Debug: Use exact error messages or symptom descriptions
|
|
334
|
-
- Find code: Include function names + file names when known
|
|
335
|
-
- Architecture: Search "migration from X to Y" or "refactored X"
|
|
336
|
-
- Agent work: Search agent names directly (e.g., "steve-krug-ux")
|
|
337
|
-
- **What Doesn't Work:**
|
|
338
|
-
- Boolean operators (AND/OR) - treated as literal text
|
|
339
|
-
- Timestamp filtering with strings - needs numeric values
|
|
340
|
-
- Generic tech terms alone - too broad, add context
|
|
341
|
-
- Complex where clauses - only basic operators supported ($eq, $ne, $in)
|
|
342
|
-
- **Best Practice:** Semantic search for discovery, metadata filtering for precision. Combine both when possible.
|
|
343
|
-
<!-- /CLAUDE-MEM INSTRUCTIONS -->`;
|
|
344
|
-
// Check if file exists and read content
|
|
345
|
-
let content = '';
|
|
346
|
-
if (existsSync(claudeMdPath)) {
|
|
347
|
-
content = readFileSync(claudeMdPath, 'utf8');
|
|
348
|
-
// Check if instructions already exist
|
|
349
|
-
if (content.includes('<!-- CLAUDE-MEM INSTRUCTIONS -->')) {
|
|
350
|
-
// Replace existing instructions
|
|
351
|
-
const startMarker = '<!-- CLAUDE-MEM INSTRUCTIONS -->';
|
|
352
|
-
const endMarker = '<!-- /CLAUDE-MEM INSTRUCTIONS -->';
|
|
353
|
-
const startIndex = content.indexOf(startMarker);
|
|
354
|
-
const endIndex = content.indexOf(endMarker) + endMarker.length;
|
|
355
|
-
if (startIndex !== -1 && endIndex !== -1) {
|
|
356
|
-
content = content.substring(0, startIndex) + instructions.trim() + content.substring(endIndex);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
else {
|
|
360
|
-
// Append instructions to the end
|
|
361
|
-
content = content.trim() + '\n' + instructions;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
else {
|
|
365
|
-
// Create new file with instructions
|
|
366
|
-
content = instructions.trim();
|
|
367
|
-
}
|
|
368
|
-
// Write the updated content
|
|
369
|
-
writeFileSync(claudeMdPath, content);
|
|
370
|
-
}
|
|
371
|
-
async function installChromaMcp() {
|
|
372
|
-
const chromaMcpCommand = `claude mcp add claude-mem -- uvx chroma-mcp --client-type persistent --data-dir ${homedir()}/.claude-mem/chroma`;
|
|
373
|
-
const s = p.spinner();
|
|
374
|
-
s.start('Installing Chroma MCP server');
|
|
375
|
-
try {
|
|
376
|
-
execSync(chromaMcpCommand, { stdio: 'pipe' });
|
|
377
|
-
s.stop('Chroma MCP server installed successfully');
|
|
378
|
-
return true;
|
|
379
|
-
}
|
|
380
|
-
catch (error) {
|
|
381
|
-
s.stop('Chroma MCP server installation failed');
|
|
382
|
-
p.log.warn('You may need to install it manually:');
|
|
383
|
-
p.log.info(chromaMcpCommand);
|
|
384
|
-
return false;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
async function configureHooks(settingsPath, config) {
|
|
388
|
-
const claudeMemHooksDir = join(homedir(), '.claude-mem', 'hooks');
|
|
389
|
-
const preCompactScript = join(claudeMemHooksDir, 'pre-compact.js');
|
|
390
|
-
const sessionStartScript = join(claudeMemHooksDir, 'session-start.js');
|
|
391
|
-
const sessionEndScript = join(claudeMemHooksDir, 'session-end.js');
|
|
392
|
-
let settings = {};
|
|
393
|
-
if (existsSync(settingsPath)) {
|
|
394
|
-
const content = readFileSync(settingsPath, 'utf8');
|
|
395
|
-
settings = JSON.parse(content);
|
|
396
|
-
}
|
|
397
|
-
// Ensure settings directory exists
|
|
398
|
-
const settingsDir = dirname(settingsPath);
|
|
399
|
-
if (!existsSync(settingsDir)) {
|
|
400
|
-
mkdirSync(settingsDir, { recursive: true });
|
|
401
|
-
}
|
|
402
|
-
// Initialize hooks structure if it doesn't exist
|
|
403
|
-
if (!settings.hooks) {
|
|
404
|
-
settings.hooks = {};
|
|
405
|
-
}
|
|
406
|
-
// Remove existing claude-mem hooks to ensure clean installation/update
|
|
407
|
-
// Non-tool hooks: filter out configs where hooks contain our commands
|
|
408
|
-
if (settings.hooks.PreCompact) {
|
|
409
|
-
settings.hooks.PreCompact = settings.hooks.PreCompact.filter((cfg) => !cfg.hooks?.some((hook) => hook.command?.includes(PACKAGE_NAME) || hook.command?.includes('pre-compact.js')));
|
|
410
|
-
if (!settings.hooks.PreCompact.length)
|
|
411
|
-
delete settings.hooks.PreCompact;
|
|
412
|
-
}
|
|
413
|
-
if (settings.hooks.SessionStart) {
|
|
414
|
-
settings.hooks.SessionStart = settings.hooks.SessionStart.filter((cfg) => !cfg.hooks?.some((hook) => hook.command?.includes(PACKAGE_NAME) || hook.command?.includes('session-start.js')));
|
|
415
|
-
if (!settings.hooks.SessionStart.length)
|
|
416
|
-
delete settings.hooks.SessionStart;
|
|
417
|
-
}
|
|
418
|
-
if (settings.hooks.SessionEnd) {
|
|
419
|
-
settings.hooks.SessionEnd = settings.hooks.SessionEnd.filter((cfg) => !cfg.hooks?.some((hook) => hook.command?.includes(PACKAGE_NAME) || hook.command?.includes('session-end.js')));
|
|
420
|
-
if (!settings.hooks.SessionEnd.length)
|
|
421
|
-
delete settings.hooks.SessionEnd;
|
|
422
|
-
}
|
|
423
|
-
/**
|
|
424
|
-
* 🔒 LOCKED by @docs-agent | Change to 🔑 to allow @docs-agent edits
|
|
425
|
-
*
|
|
426
|
-
* OFFICIAL DOCS: Claude Code Hooks Configuration v2025
|
|
427
|
-
* Last Verified: 2025-08-31
|
|
428
|
-
*
|
|
429
|
-
* Hook Configuration Structure Requirements:
|
|
430
|
-
* - Tool-related hooks (PreToolUse, PostToolUse): Use 'matcher' field for tool patterns
|
|
431
|
-
* - Non-tool hooks (PreCompact, SessionStart, SessionEnd, etc.): NO matcher/pattern field
|
|
432
|
-
*
|
|
433
|
-
* Correct Non-Tool Hook Structure:
|
|
434
|
-
* {
|
|
435
|
-
* hooks: [{
|
|
436
|
-
* type: "command",
|
|
437
|
-
* command: "/path/to/script.js"
|
|
438
|
-
* }]
|
|
439
|
-
* }
|
|
440
|
-
*
|
|
441
|
-
* @see https://docs.anthropic.com/en/docs/claude-code/hooks
|
|
442
|
-
* @see docs/claude-code/hook-configuration.md for full documentation
|
|
443
|
-
*/
|
|
444
|
-
// Add PreCompact hook - Non-tool hook (no matcher field)
|
|
445
|
-
if (!settings.hooks.PreCompact) {
|
|
446
|
-
settings.hooks.PreCompact = [];
|
|
447
|
-
}
|
|
448
|
-
// ✅ CORRECT: Non-tool hooks have no 'pattern' or 'matcher' field
|
|
449
|
-
settings.hooks.PreCompact.push({
|
|
450
|
-
hooks: [
|
|
451
|
-
{
|
|
452
|
-
type: "command",
|
|
453
|
-
command: preCompactScript,
|
|
454
|
-
timeout: config.hookTimeout
|
|
455
|
-
}
|
|
456
|
-
]
|
|
457
|
-
});
|
|
458
|
-
// Add SessionStart hook - Non-tool hook (no matcher field)
|
|
459
|
-
if (!settings.hooks.SessionStart) {
|
|
460
|
-
settings.hooks.SessionStart = [];
|
|
461
|
-
}
|
|
462
|
-
// ✅ CORRECT: Non-tool hooks have no 'pattern' or 'matcher' field
|
|
463
|
-
settings.hooks.SessionStart.push({
|
|
464
|
-
hooks: [
|
|
465
|
-
{
|
|
466
|
-
type: "command",
|
|
467
|
-
command: sessionStartScript
|
|
468
|
-
}
|
|
469
|
-
]
|
|
470
|
-
});
|
|
471
|
-
// Add SessionEnd hook (only if the file exists)
|
|
472
|
-
if (existsSync(sessionEndScript)) {
|
|
473
|
-
if (!settings.hooks.SessionEnd) {
|
|
474
|
-
settings.hooks.SessionEnd = [];
|
|
475
|
-
}
|
|
476
|
-
// ✅ CORRECT: Non-tool hooks have no 'pattern' or 'matcher' field
|
|
477
|
-
settings.hooks.SessionEnd.push({
|
|
478
|
-
hooks: [{
|
|
479
|
-
type: "command",
|
|
480
|
-
command: sessionEndScript,
|
|
481
|
-
timeout: config.hookTimeout
|
|
482
|
-
}]
|
|
483
|
-
});
|
|
484
|
-
}
|
|
485
|
-
// Write updated settings
|
|
486
|
-
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
487
|
-
}
|
|
488
|
-
async function verifyInstallation() {
|
|
489
|
-
const s = p.spinner();
|
|
490
|
-
s.start('Verifying installation');
|
|
491
|
-
const issues = [];
|
|
492
|
-
// Check hooks
|
|
493
|
-
const hooksDir = join(homedir(), '.claude-mem', 'hooks');
|
|
494
|
-
if (!existsSync(join(hooksDir, 'pre-compact.js'))) {
|
|
495
|
-
issues.push('Pre-compact hook not found');
|
|
496
|
-
}
|
|
497
|
-
if (!existsSync(join(hooksDir, 'session-start.js'))) {
|
|
498
|
-
issues.push('Session-start hook not found');
|
|
499
|
-
}
|
|
500
|
-
// Check Chroma MCP configuration
|
|
501
|
-
const userMcpPath = join(homedir(), '.claude.json');
|
|
502
|
-
const projectMcpPath = join(process.cwd(), '.mcp.json');
|
|
503
|
-
let chromaMcpConfigured = false;
|
|
504
|
-
if (existsSync(userMcpPath)) {
|
|
505
|
-
try {
|
|
506
|
-
const config = JSON.parse(readFileSync(userMcpPath, 'utf8'));
|
|
507
|
-
if (config.mcpServers?.['claude-mem']) {
|
|
508
|
-
chromaMcpConfigured = true;
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
catch { }
|
|
512
|
-
}
|
|
513
|
-
if (!chromaMcpConfigured && existsSync(projectMcpPath)) {
|
|
514
|
-
try {
|
|
515
|
-
const config = JSON.parse(readFileSync(projectMcpPath, 'utf8'));
|
|
516
|
-
if (config.mcpServers?.['claude-mem']) {
|
|
517
|
-
chromaMcpConfigured = true;
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
catch { }
|
|
521
|
-
}
|
|
522
|
-
if (!chromaMcpConfigured) {
|
|
523
|
-
issues.push('Chroma MCP server not configured');
|
|
524
|
-
}
|
|
525
|
-
if (issues.length > 0) {
|
|
526
|
-
s.stop('Installation verification completed with issues');
|
|
527
|
-
p.log.warn('The following issues were detected:');
|
|
528
|
-
issues.forEach(issue => p.log.error(` - ${issue}`));
|
|
529
|
-
p.log.info('The installation may not work correctly. Consider reinstalling with --force flag.');
|
|
530
|
-
}
|
|
531
|
-
else {
|
|
532
|
-
s.stop('Installation verified successfully');
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
export async function install(options = {}) {
|
|
536
|
-
p.intro(`🧠 Claude Memory Installation`);
|
|
537
|
-
// Check if running with flags (non-interactive mode)
|
|
538
|
-
const isNonInteractive = options.user || options.project || options.local || options.force;
|
|
539
|
-
let config;
|
|
540
|
-
if (isNonInteractive) {
|
|
541
|
-
// Non-interactive mode - use flags
|
|
542
|
-
config = {
|
|
543
|
-
scope: options.local ? 'local' : options.project ? 'project' : 'user',
|
|
544
|
-
customPath: options.path,
|
|
545
|
-
hookTimeout: options.timeout || 180000,
|
|
546
|
-
forceReinstall: !!options.force,
|
|
547
|
-
skipMcpInstall: !!options.skipMcp
|
|
548
|
-
};
|
|
549
|
-
}
|
|
550
|
-
else {
|
|
551
|
-
// Interactive mode
|
|
552
|
-
// Validate prerequisites
|
|
553
|
-
const prereqValid = await validatePrerequisites();
|
|
554
|
-
if (!prereqValid) {
|
|
555
|
-
p.outro('Please fix the prerequisites issues and try again');
|
|
556
|
-
process.exit(1);
|
|
557
|
-
}
|
|
558
|
-
// Detect existing installation
|
|
559
|
-
const existingInstall = detectExistingInstallation();
|
|
560
|
-
// Run installation wizard
|
|
561
|
-
const wizardConfig = await runInstallationWizard(existingInstall);
|
|
562
|
-
if (!wizardConfig) {
|
|
563
|
-
process.exit(0);
|
|
564
|
-
}
|
|
565
|
-
config = wizardConfig;
|
|
566
|
-
}
|
|
567
|
-
// Backup existing configuration if force reinstall
|
|
568
|
-
if (config.forceReinstall) {
|
|
569
|
-
const backupPath = await backupExistingConfig();
|
|
570
|
-
if (backupPath) {
|
|
571
|
-
p.log.info(`Backup created at: ${backupPath}`);
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
// Installation steps
|
|
575
|
-
await p.group({
|
|
576
|
-
directories: async () => {
|
|
577
|
-
const s = p.spinner();
|
|
578
|
-
s.start('Creating directory structure');
|
|
579
|
-
ensureDirectoryStructure();
|
|
580
|
-
s.stop('Directory structure created');
|
|
581
|
-
},
|
|
582
|
-
chromaMcp: async () => {
|
|
583
|
-
if (!config.skipMcpInstall) {
|
|
584
|
-
await installChromaMcp();
|
|
585
|
-
}
|
|
586
|
-
},
|
|
587
|
-
instructions: async () => {
|
|
588
|
-
const s = p.spinner();
|
|
589
|
-
s.start('Adding CLAUDE.md instructions');
|
|
590
|
-
ensureClaudeMdInstructions();
|
|
591
|
-
s.stop('CLAUDE.md instructions added');
|
|
592
|
-
},
|
|
593
|
-
hooks: async () => {
|
|
594
|
-
const s = p.spinner();
|
|
595
|
-
s.start('Installing hooks');
|
|
596
|
-
writeHookFiles(config.hookTimeout);
|
|
597
|
-
s.stop('Hooks installed');
|
|
598
|
-
},
|
|
599
|
-
settings: async () => {
|
|
600
|
-
const s = p.spinner();
|
|
601
|
-
s.start('Configuring Claude settings');
|
|
602
|
-
// Determine settings path
|
|
603
|
-
let settingsPath;
|
|
604
|
-
if (config.scope === 'local' && config.customPath) {
|
|
605
|
-
settingsPath = join(config.customPath, 'settings.local.json');
|
|
606
|
-
}
|
|
607
|
-
else if (config.scope === 'project') {
|
|
608
|
-
settingsPath = join(process.cwd(), '.claude', 'settings.json');
|
|
609
|
-
}
|
|
610
|
-
else {
|
|
611
|
-
settingsPath = join(homedir(), '.claude', 'settings.json');
|
|
612
|
-
}
|
|
613
|
-
await configureHooks(settingsPath, config);
|
|
614
|
-
// Store backend setting in user settings
|
|
615
|
-
const userSettingsDir = join(homedir(), '.claude-mem');
|
|
616
|
-
const userSettingsPath = join(userSettingsDir, 'settings.json');
|
|
617
|
-
let userSettings = {};
|
|
618
|
-
if (existsSync(userSettingsPath)) {
|
|
619
|
-
try {
|
|
620
|
-
userSettings = JSON.parse(readFileSync(userSettingsPath, 'utf8'));
|
|
621
|
-
}
|
|
622
|
-
catch { }
|
|
623
|
-
}
|
|
624
|
-
userSettings.backend = 'chroma';
|
|
625
|
-
userSettings.installed = true;
|
|
626
|
-
userSettings.embedded = true;
|
|
627
|
-
writeFileSync(userSettingsPath, JSON.stringify(userSettings, null, 2));
|
|
628
|
-
s.stop('Settings configured');
|
|
629
|
-
}
|
|
630
|
-
}, {
|
|
631
|
-
onCancel: () => {
|
|
632
|
-
p.cancel('Installation cancelled');
|
|
633
|
-
process.exit(0);
|
|
634
|
-
}
|
|
635
|
-
});
|
|
636
|
-
// Verify installation
|
|
637
|
-
await verifyInstallation();
|
|
638
|
-
p.outro(`✅ Claude Memory installed successfully!
|
|
639
|
-
|
|
640
|
-
How it works:
|
|
641
|
-
• When you start Claude Code, claude-mem will load the latest memories as a startup message
|
|
642
|
-
• To save your work, do /compact or /clear, wait for it to process (takes about 30s)
|
|
643
|
-
• You'll see an updated context message with your latest memories
|
|
644
|
-
• We added instructions to your ~/.claude/CLAUDE.md that teaches Claude Code how to use its new memory system
|
|
645
|
-
|
|
646
|
-
To search memories, just ask Claude to search claude-mem. That's it.
|
|
647
|
-
|
|
648
|
-
Restart Claude Code to begin using your new memory system.`);
|
|
649
|
-
}
|