claude-mem 3.0.2 → 3.0.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/.mcp.json +11 -0
- package/claude-mem +0 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +64 -0
- package/dist/commands/compress.d.ts +2 -0
- package/dist/commands/compress.js +59 -0
- package/dist/commands/install.d.ts +2 -0
- package/dist/commands/install.js +372 -0
- package/dist/commands/load-context.d.ts +2 -0
- package/dist/commands/load-context.js +330 -0
- package/dist/commands/logs.d.ts +2 -0
- package/dist/commands/logs.js +41 -0
- package/dist/commands/migrate.d.ts +9 -0
- package/dist/commands/migrate.js +174 -0
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +159 -0
- package/dist/commands/uninstall.d.ts +2 -0
- package/dist/commands/uninstall.js +105 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.js +33 -0
- package/dist/constants.d.ts +516 -0
- package/dist/constants.js +522 -0
- package/dist/error-handler.d.ts +17 -0
- package/dist/error-handler.js +103 -0
- package/dist/mcp-server-cli.d.ts +34 -0
- package/dist/mcp-server-cli.js +158 -0
- package/dist/mcp-server.d.ts +103 -0
- package/dist/mcp-server.js +269 -0
- package/dist/types.d.ts +148 -0
- package/dist/types.js +78 -0
- package/dist/utils/HookDetector.d.ts +64 -0
- package/dist/utils/HookDetector.js +213 -0
- package/dist/utils/PathResolver.d.ts +16 -0
- package/dist/utils/PathResolver.js +55 -0
- package/dist/utils/SettingsManager.d.ts +63 -0
- package/dist/utils/SettingsManager.js +133 -0
- package/dist/utils/TranscriptCompressor.d.ts +111 -0
- package/dist/utils/TranscriptCompressor.js +486 -0
- package/dist/utils/common.d.ts +29 -0
- package/dist/utils/common.js +14 -0
- package/dist/utils/error-utils.d.ts +93 -0
- package/dist/utils/error-utils.js +238 -0
- package/dist/utils/index.d.ts +19 -0
- package/dist/utils/index.js +26 -0
- package/dist/utils/logger.d.ts +19 -0
- package/dist/utils/logger.js +42 -0
- package/dist/utils/mcp-client-factory.d.ts +51 -0
- package/dist/utils/mcp-client-factory.js +115 -0
- package/dist/utils/mcp-client.d.ts +75 -0
- package/dist/utils/mcp-client.js +120 -0
- package/dist/utils/memory-mcp-client.d.ts +135 -0
- package/dist/utils/memory-mcp-client.js +490 -0
- package/dist/utils/weaviate-mcp-adapter.d.ts +102 -0
- package/dist/utils/weaviate-mcp-adapter.js +587 -0
- package/package.json +3 -2
- package/src/claude-mem.js +0 -859
package/.mcp.json
ADDED
package/claude-mem
CHANGED
|
Binary file
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { PACKAGE_NAME, PACKAGE_VERSION, PACKAGE_DESCRIPTION } from './config.js';
|
|
4
|
+
// Import command handlers
|
|
5
|
+
import { compress } from './commands/compress.js';
|
|
6
|
+
import { install } from './commands/install.js';
|
|
7
|
+
import { uninstall } from './commands/uninstall.js';
|
|
8
|
+
import { status } from './commands/status.js';
|
|
9
|
+
import { logs } from './commands/logs.js';
|
|
10
|
+
import { loadContext } from './commands/load-context.js';
|
|
11
|
+
const program = new Command();
|
|
12
|
+
program
|
|
13
|
+
.name(PACKAGE_NAME)
|
|
14
|
+
.description(PACKAGE_DESCRIPTION)
|
|
15
|
+
.version(PACKAGE_VERSION);
|
|
16
|
+
// Compress command
|
|
17
|
+
program
|
|
18
|
+
.command('compress [transcript]')
|
|
19
|
+
.description('Compress a Claude Code transcript into memory')
|
|
20
|
+
.option('--output <path>', 'Output directory for compressed files')
|
|
21
|
+
.option('--dry-run', 'Show what would be compressed without doing it')
|
|
22
|
+
.option('-v, --verbose', 'Show detailed output')
|
|
23
|
+
.action(compress);
|
|
24
|
+
// Install command
|
|
25
|
+
program
|
|
26
|
+
.command('install')
|
|
27
|
+
.description('Install Claude Code hooks for automatic compression')
|
|
28
|
+
.option('--global', 'Install globally (default)')
|
|
29
|
+
.option('--project', 'Install for current project only')
|
|
30
|
+
.option('--force', 'Force installation even if already installed')
|
|
31
|
+
.action(install);
|
|
32
|
+
// Uninstall command
|
|
33
|
+
program
|
|
34
|
+
.command('uninstall')
|
|
35
|
+
.description('Remove Claude Code hooks')
|
|
36
|
+
.option('--global', 'Remove from global settings')
|
|
37
|
+
.option('--project', 'Remove from project settings')
|
|
38
|
+
.option('--all', 'Remove from both global and project settings')
|
|
39
|
+
.action(uninstall);
|
|
40
|
+
// Status command
|
|
41
|
+
program
|
|
42
|
+
.command('status')
|
|
43
|
+
.description('Check installation status of Claude Memory System')
|
|
44
|
+
.action(status);
|
|
45
|
+
// Logs command
|
|
46
|
+
program
|
|
47
|
+
.command('logs')
|
|
48
|
+
.description('View claude-mem operation logs')
|
|
49
|
+
.option('--debug', 'Show debug logs only')
|
|
50
|
+
.option('--error', 'Show error logs only')
|
|
51
|
+
.option('--tail [n]', 'Show last n lines', '50')
|
|
52
|
+
.option('--follow', 'Follow log output')
|
|
53
|
+
.action(logs);
|
|
54
|
+
// Load-context command
|
|
55
|
+
program
|
|
56
|
+
.command('load-context')
|
|
57
|
+
.description('Load compressed memories for current session')
|
|
58
|
+
.option('--project <name>', 'Filter by project name')
|
|
59
|
+
.option('--count <n>', 'Number of memories to load', '10')
|
|
60
|
+
.option('--raw', 'Output raw JSON instead of formatted text')
|
|
61
|
+
.option('--format <type>', 'Output format: json, session-start, or default')
|
|
62
|
+
.action(loadContext);
|
|
63
|
+
// Parse arguments and execute
|
|
64
|
+
program.parse();
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { dirname, basename } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
5
|
+
const __dirname = dirname(__filename);
|
|
6
|
+
export async function compress(transcript, options = {}) {
|
|
7
|
+
if (!transcript) {
|
|
8
|
+
console.error('❌ Transcript file path is required');
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
if (!existsSync(transcript)) {
|
|
12
|
+
console.error(`❌ Transcript file not found: ${transcript}`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
console.log(`🗜️ Compressing transcript: ${transcript}`);
|
|
16
|
+
if (options.dryRun) {
|
|
17
|
+
console.log('📋 Dry run mode - no files will be written');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
// Import TranscriptCompressor dynamically to ensure it's available
|
|
22
|
+
const { TranscriptCompressor } = await import('../utils/TranscriptCompressor.js');
|
|
23
|
+
// Create compressor instance with debug mode based on verbose option
|
|
24
|
+
const compressor = new TranscriptCompressor({
|
|
25
|
+
debug: options.verbose || false
|
|
26
|
+
});
|
|
27
|
+
// Extract session ID from transcript filename if not provided
|
|
28
|
+
const sessionId = options.sessionId || basename(transcript, '.jsonl');
|
|
29
|
+
if (options.verbose) {
|
|
30
|
+
console.log(`📊 Starting compression with session ID: ${sessionId}`);
|
|
31
|
+
}
|
|
32
|
+
// Perform compression
|
|
33
|
+
const archivePath = await compressor.compress(transcript, sessionId);
|
|
34
|
+
console.log('✅ Compression completed successfully');
|
|
35
|
+
console.log(`📦 Archive created: ${archivePath}`);
|
|
36
|
+
if (options.output && options.output !== dirname(archivePath)) {
|
|
37
|
+
console.log(`📂 Output directory: ${options.output} (archive location may differ)`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
const err = error;
|
|
42
|
+
console.error('❌ Compression failed:', err.message);
|
|
43
|
+
if (options.verbose) {
|
|
44
|
+
console.error('Stack trace:', err.stack);
|
|
45
|
+
}
|
|
46
|
+
// Provide helpful error messages for common issues
|
|
47
|
+
if (err.message.includes('Claude Code executable not found')) {
|
|
48
|
+
console.error('💡 Hint: Make sure Claude Code is installed and accessible in your PATH');
|
|
49
|
+
console.error(' Or set CLAUDE_CODE_PATH environment variable');
|
|
50
|
+
}
|
|
51
|
+
else if (err.message.includes('JWT_SECRET')) {
|
|
52
|
+
console.error('💡 Hint: Make sure JWT_SECRET is set in your MCP configuration');
|
|
53
|
+
}
|
|
54
|
+
else if (err.message.includes('No summaries extracted')) {
|
|
55
|
+
console.error('💡 Hint: The transcript may be too short or not contain analyzable content');
|
|
56
|
+
}
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, chmodSync, mkdirSync, copyFileSync, statSync } from 'fs';
|
|
2
|
+
import { join, resolve, dirname } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { PACKAGE_NAME, MCP_SERVER_SCRIPT } from '../config.js';
|
|
6
|
+
import { CLI_MESSAGES } from '../constants.js';
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
function ensureDirectoryStructure() {
|
|
9
|
+
const claudeMemDir = join(homedir(), '.claude-mem');
|
|
10
|
+
const hooksDir = join(claudeMemDir, 'hooks');
|
|
11
|
+
const indexDir = join(claudeMemDir, 'index');
|
|
12
|
+
const archivesDir = join(claudeMemDir, 'archives');
|
|
13
|
+
const logsDir = join(claudeMemDir, 'logs');
|
|
14
|
+
[claudeMemDir, hooksDir, indexDir, archivesDir, logsDir].forEach(dir => {
|
|
15
|
+
if (!existsSync(dir)) {
|
|
16
|
+
mkdirSync(dir, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function writeHookFiles() {
|
|
21
|
+
const hooksDir = join(homedir(), '.claude-mem', 'hooks');
|
|
22
|
+
const preCompactDest = join(hooksDir, 'pre-compact.js');
|
|
23
|
+
const sessionStartDest = join(hooksDir, 'session-start.js');
|
|
24
|
+
const sessionEndDest = join(hooksDir, 'session-end.js');
|
|
25
|
+
const mcpServerDest = join(homedir(), '.claude-mem', MCP_SERVER_SCRIPT);
|
|
26
|
+
// Find hooks directory relative to the package location
|
|
27
|
+
const possibleHooksPaths = [
|
|
28
|
+
resolve(join(__dirname, '..', '..', 'hooks')), // Development mode
|
|
29
|
+
resolve(join(dirname(process.execPath), 'hooks')), // Standalone executable with hooks alongside
|
|
30
|
+
resolve(join(dirname(process.execPath), '..', 'lib', 'node_modules', 'claude-mem', 'hooks')), // npm global install
|
|
31
|
+
resolve(join(dirname(process.execPath), '..', '..', 'lib', 'node_modules', 'claude-mem', 'hooks')), // npm global install (alternative path)
|
|
32
|
+
];
|
|
33
|
+
// Find MCP server script in dist directory
|
|
34
|
+
const possibleMcpServerPaths = [
|
|
35
|
+
resolve(join(__dirname, '..', '..', 'dist', MCP_SERVER_SCRIPT)), // Development mode - look in project dist/
|
|
36
|
+
resolve(join(__dirname, '..', MCP_SERVER_SCRIPT)), // Development mode - compiled to same dist/
|
|
37
|
+
resolve(join(dirname(process.execPath), '..', 'lib', 'node_modules', 'claude-mem', 'dist', MCP_SERVER_SCRIPT)), // npm global install
|
|
38
|
+
resolve(join(dirname(process.execPath), '..', '..', 'lib', 'node_modules', 'claude-mem', 'dist', MCP_SERVER_SCRIPT)), // npm global install (alternative path)
|
|
39
|
+
resolve(join(dirname(process.execPath), MCP_SERVER_SCRIPT)), // Standalone executable with script alongside
|
|
40
|
+
];
|
|
41
|
+
let preCompactSource = null;
|
|
42
|
+
let sessionStartSource = null;
|
|
43
|
+
let sessionEndSource = null;
|
|
44
|
+
let mcpServerSource = null;
|
|
45
|
+
for (const hooksPath of possibleHooksPaths) {
|
|
46
|
+
const preCompactCandidate = join(hooksPath, 'pre-compact.js');
|
|
47
|
+
const sessionStartCandidate = join(hooksPath, 'session-start.js');
|
|
48
|
+
const sessionEndCandidate = join(hooksPath, 'session-end.js');
|
|
49
|
+
if (existsSync(preCompactCandidate) && existsSync(sessionStartCandidate)) {
|
|
50
|
+
preCompactSource = preCompactCandidate;
|
|
51
|
+
sessionStartSource = sessionStartCandidate;
|
|
52
|
+
// Check for session-end.js, but don't require it for backward compatibility
|
|
53
|
+
if (existsSync(sessionEndCandidate)) {
|
|
54
|
+
sessionEndSource = sessionEndCandidate;
|
|
55
|
+
}
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Find MCP server script
|
|
60
|
+
for (const mcpServerPath of possibleMcpServerPaths) {
|
|
61
|
+
if (existsSync(mcpServerPath)) {
|
|
62
|
+
mcpServerSource = mcpServerPath;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (preCompactSource && sessionStartSource) {
|
|
67
|
+
copyFileSync(preCompactSource, preCompactDest);
|
|
68
|
+
copyFileSync(sessionStartSource, sessionStartDest);
|
|
69
|
+
// Copy session-end.js if it exists
|
|
70
|
+
if (sessionEndSource) {
|
|
71
|
+
copyFileSync(sessionEndSource, sessionEndDest);
|
|
72
|
+
chmodSync(sessionEndDest, 0o755);
|
|
73
|
+
}
|
|
74
|
+
chmodSync(preCompactDest, 0o755);
|
|
75
|
+
chmodSync(sessionStartDest, 0o755);
|
|
76
|
+
// Copy MCP server script
|
|
77
|
+
if (mcpServerSource) {
|
|
78
|
+
copyFileSync(mcpServerSource, mcpServerDest);
|
|
79
|
+
chmodSync(mcpServerDest, 0o755);
|
|
80
|
+
console.log('✅ MCP server script installed');
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
console.error('❌ MCP server script not found');
|
|
84
|
+
console.error('Searched in:');
|
|
85
|
+
possibleMcpServerPaths.forEach(path => console.error(` - ${path}`));
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
// Write a configuration file for hooks to know the package name
|
|
89
|
+
const hookConfigPath = join(hooksDir, 'config.json');
|
|
90
|
+
const hookConfig = {
|
|
91
|
+
packageName: PACKAGE_NAME,
|
|
92
|
+
cliCommand: PACKAGE_NAME, // The command to call (claude-mem)
|
|
93
|
+
backend: 'weaviate' // Always use embedded Weaviate
|
|
94
|
+
};
|
|
95
|
+
writeFileSync(hookConfigPath, JSON.stringify(hookConfig, null, 2));
|
|
96
|
+
console.log(CLI_MESSAGES.INSTALLATION.HOOKS_INSTALLED);
|
|
97
|
+
// Validate MCP server script installation
|
|
98
|
+
if (existsSync(mcpServerDest)) {
|
|
99
|
+
try {
|
|
100
|
+
// Test script has execute permissions by checking file stats
|
|
101
|
+
const stats = statSync(mcpServerDest);
|
|
102
|
+
if (stats.mode & parseInt('100', 8)) {
|
|
103
|
+
console.log('✅ MCP server script validated');
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
console.warn('⚠️ MCP server script may not be executable');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
console.warn(`⚠️ MCP server script validation failed: ${error.message}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
console.error('❌ MCP server script validation failed - file not found');
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
console.error(CLI_MESSAGES.ERRORS.HOOKS_NOT_FOUND);
|
|
120
|
+
console.error('Searched in:');
|
|
121
|
+
possibleHooksPaths.forEach(path => console.error(` - ${path}`));
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* 🔒 LOCKED by @docs-agent | Change to 🔑 to allow @docs-agent edits
|
|
127
|
+
*
|
|
128
|
+
* OFFICIAL DOCS: Claude Code MCP Configuration v2025
|
|
129
|
+
* Last Verified: 2025-08-31
|
|
130
|
+
* @see https://docs.anthropic.com/en/docs/claude-code/mcp
|
|
131
|
+
*
|
|
132
|
+
* MCP Config File Locations (per official docs):
|
|
133
|
+
* - User scope: ~/.claude.json
|
|
134
|
+
* - Project scope: ./.mcp.json (checked into version control)
|
|
135
|
+
* - Local scope: Not officially documented (implementation decision)
|
|
136
|
+
*
|
|
137
|
+
* @see docs/claude-code/mcp-configuration.md
|
|
138
|
+
*/
|
|
139
|
+
function configureMcpServer(settingsDir, scope) {
|
|
140
|
+
// Determine the correct MCP config file based on scope
|
|
141
|
+
let mcpConfigPath;
|
|
142
|
+
if (scope === 'user') {
|
|
143
|
+
// User scope: ~/.claude.json
|
|
144
|
+
mcpConfigPath = join(homedir(), '.claude.json');
|
|
145
|
+
}
|
|
146
|
+
else if (scope === 'project') {
|
|
147
|
+
// Project scope: .mcp.json in current directory
|
|
148
|
+
mcpConfigPath = join(process.cwd(), '.mcp.json');
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
// Local scope: use .claude.json with project-specific section
|
|
152
|
+
mcpConfigPath = join(homedir(), '.claude.json');
|
|
153
|
+
}
|
|
154
|
+
let config = {};
|
|
155
|
+
if (existsSync(mcpConfigPath)) {
|
|
156
|
+
try {
|
|
157
|
+
config = JSON.parse(readFileSync(mcpConfigPath, 'utf8'));
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
console.log(CLI_MESSAGES.ERRORS.MCP_CONFIG_PARSE_FAILED(error.message));
|
|
161
|
+
return; // Don't overwrite a file we can't parse
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (!config.mcpServers) {
|
|
165
|
+
config.mcpServers = {};
|
|
166
|
+
}
|
|
167
|
+
// Only add/update the server for this package, preserve everything else
|
|
168
|
+
// Use local MCP server script with embedded Weaviate instead of npx
|
|
169
|
+
config.mcpServers[PACKAGE_NAME] = {
|
|
170
|
+
command: "node",
|
|
171
|
+
args: [join(homedir(), '.claude-mem', MCP_SERVER_SCRIPT)]
|
|
172
|
+
};
|
|
173
|
+
try {
|
|
174
|
+
writeFileSync(mcpConfigPath, JSON.stringify(config, null, 2));
|
|
175
|
+
console.log(CLI_MESSAGES.INSTALLATION.MCP_CONFIGURED(mcpConfigPath));
|
|
176
|
+
console.log('✅ Configured embedded Weaviate MCP server');
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
console.log(CLI_MESSAGES.ERRORS.MCP_CONFIG_WRITE_FAILED(error.message));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
export async function install(options = {}) {
|
|
183
|
+
console.log(CLI_MESSAGES.INSTALLATION.STARTING);
|
|
184
|
+
console.log('🧠 Installing embedded Weaviate MCP server for persistent semantic memory');
|
|
185
|
+
// Ensure claude-mem directory structure exists
|
|
186
|
+
ensureDirectoryStructure();
|
|
187
|
+
// Determine which settings file to use
|
|
188
|
+
let settingsDir;
|
|
189
|
+
let settingsFile;
|
|
190
|
+
let settingsType;
|
|
191
|
+
if (options.local) {
|
|
192
|
+
settingsDir = join(process.cwd(), '.claude');
|
|
193
|
+
settingsFile = 'settings.local.json';
|
|
194
|
+
settingsType = 'Local';
|
|
195
|
+
}
|
|
196
|
+
else if (options.project) {
|
|
197
|
+
settingsDir = join(process.cwd(), '.claude');
|
|
198
|
+
settingsFile = 'settings.json';
|
|
199
|
+
settingsType = 'Project';
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
// Default to user settings (--user or --global or no flag)
|
|
203
|
+
settingsDir = join(homedir(), '.claude');
|
|
204
|
+
settingsFile = 'settings.json';
|
|
205
|
+
settingsType = 'User';
|
|
206
|
+
}
|
|
207
|
+
const settingsPath = join(settingsDir, settingsFile);
|
|
208
|
+
// Determine scope for MCP configuration
|
|
209
|
+
const scope = options.local ? 'local' :
|
|
210
|
+
options.project ? 'project' :
|
|
211
|
+
'user';
|
|
212
|
+
// Configure MCP server with the appropriate scope
|
|
213
|
+
configureMcpServer(settingsDir, scope);
|
|
214
|
+
// Write hook files to ~/.claude-mem/hooks/
|
|
215
|
+
writeHookFiles();
|
|
216
|
+
// Point settings to the installed hooks
|
|
217
|
+
const claudeMemHooksDir = join(homedir(), '.claude-mem', 'hooks');
|
|
218
|
+
const preCompactScript = join(claudeMemHooksDir, 'pre-compact.js');
|
|
219
|
+
const sessionStartScript = join(claudeMemHooksDir, 'session-start.js');
|
|
220
|
+
const sessionEndScript = join(claudeMemHooksDir, 'session-end.js');
|
|
221
|
+
let settings = {};
|
|
222
|
+
if (existsSync(settingsPath)) {
|
|
223
|
+
try {
|
|
224
|
+
const content = readFileSync(settingsPath, 'utf8');
|
|
225
|
+
settings = JSON.parse(content);
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
console.log(`⚠️ Creating new settings file (could not parse existing): ${error.message}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// Ensure settings directory exists
|
|
232
|
+
if (!existsSync(settingsDir)) {
|
|
233
|
+
mkdirSync(settingsDir, { recursive: true });
|
|
234
|
+
}
|
|
235
|
+
// Initialize hooks structure if it doesn't exist
|
|
236
|
+
if (!settings.hooks) {
|
|
237
|
+
settings.hooks = {};
|
|
238
|
+
}
|
|
239
|
+
// Check for existing hooks from this package
|
|
240
|
+
// Non-tool hooks structure: array of { hooks: [...] } (no matcher field)
|
|
241
|
+
const hasExistingPreCompact = settings.hooks.PreCompact?.some((config) => config.hooks?.some((hook) => hook.command?.includes(PACKAGE_NAME) || hook.command?.includes('pre-compact.js')));
|
|
242
|
+
const hasExistingSessionStart = settings.hooks.SessionStart?.some((config) => config.hooks?.some((hook) => hook.command?.includes(PACKAGE_NAME) || hook.command?.includes('session-start.js')));
|
|
243
|
+
const hasExistingSessionEnd = settings.hooks.SessionEnd?.some((config) => config.hooks?.some((hook) => hook.command?.includes(PACKAGE_NAME) || hook.command?.includes('session-end.js')));
|
|
244
|
+
if ((hasExistingPreCompact || hasExistingSessionStart || hasExistingSessionEnd) && !options.force) {
|
|
245
|
+
console.log(CLI_MESSAGES.INSTALLATION.ALREADY_INSTALLED);
|
|
246
|
+
console.log(CLI_MESSAGES.INSTALLATION.USE_FORCE);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
// Remove existing claude-mem hooks if forcing
|
|
250
|
+
if (options.force) {
|
|
251
|
+
// Non-tool hooks: filter out configs where hooks contain our commands
|
|
252
|
+
if (settings.hooks.PreCompact) {
|
|
253
|
+
settings.hooks.PreCompact = settings.hooks.PreCompact.filter((config) => !config.hooks?.some((hook) => hook.command?.includes(PACKAGE_NAME) || hook.command?.includes('pre-compact.js')));
|
|
254
|
+
if (settings.hooks.PreCompact.length === 0) {
|
|
255
|
+
delete settings.hooks.PreCompact;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (settings.hooks.SessionStart) {
|
|
259
|
+
settings.hooks.SessionStart = settings.hooks.SessionStart.filter((config) => !config.hooks?.some((hook) => hook.command?.includes(PACKAGE_NAME) || hook.command?.includes('session-start.js')));
|
|
260
|
+
if (settings.hooks.SessionStart.length === 0) {
|
|
261
|
+
delete settings.hooks.SessionStart;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (settings.hooks.SessionEnd) {
|
|
265
|
+
settings.hooks.SessionEnd = settings.hooks.SessionEnd.filter((config) => !config.hooks?.some((hook) => hook.command?.includes(PACKAGE_NAME) || hook.command?.includes('session-end.js')));
|
|
266
|
+
if (settings.hooks.SessionEnd.length === 0) {
|
|
267
|
+
delete settings.hooks.SessionEnd;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* 🔒 LOCKED by @docs-agent | Change to 🔑 to allow @docs-agent edits
|
|
273
|
+
*
|
|
274
|
+
* OFFICIAL DOCS: Claude Code Hooks Configuration v2025
|
|
275
|
+
* Last Verified: 2025-08-31
|
|
276
|
+
*
|
|
277
|
+
* Hook Configuration Structure Requirements:
|
|
278
|
+
* - Tool-related hooks (PreToolUse, PostToolUse): Use 'matcher' field for tool patterns
|
|
279
|
+
* - Non-tool hooks (PreCompact, SessionStart, SessionEnd, etc.): NO matcher/pattern field
|
|
280
|
+
*
|
|
281
|
+
* Correct Non-Tool Hook Structure:
|
|
282
|
+
* {
|
|
283
|
+
* hooks: [{
|
|
284
|
+
* type: "command",
|
|
285
|
+
* command: "/path/to/script.js"
|
|
286
|
+
* }]
|
|
287
|
+
* }
|
|
288
|
+
*
|
|
289
|
+
* @see https://docs.anthropic.com/en/docs/claude-code/hooks
|
|
290
|
+
* @see docs/claude-code/hook-configuration.md for full documentation
|
|
291
|
+
*/
|
|
292
|
+
// Add PreCompact hook - Non-tool hook (no matcher field)
|
|
293
|
+
if (!settings.hooks.PreCompact) {
|
|
294
|
+
settings.hooks.PreCompact = [];
|
|
295
|
+
}
|
|
296
|
+
// ✅ CORRECT: Non-tool hooks have no 'pattern' or 'matcher' field
|
|
297
|
+
settings.hooks.PreCompact.push({
|
|
298
|
+
hooks: [
|
|
299
|
+
{
|
|
300
|
+
type: "command",
|
|
301
|
+
command: preCompactScript,
|
|
302
|
+
timeout: 180000
|
|
303
|
+
}
|
|
304
|
+
]
|
|
305
|
+
});
|
|
306
|
+
// Add SessionStart hook - Non-tool hook (no matcher field)
|
|
307
|
+
if (!settings.hooks.SessionStart) {
|
|
308
|
+
settings.hooks.SessionStart = [];
|
|
309
|
+
}
|
|
310
|
+
// ✅ CORRECT: Non-tool hooks have no 'pattern' or 'matcher' field
|
|
311
|
+
settings.hooks.SessionStart.push({
|
|
312
|
+
hooks: [
|
|
313
|
+
{
|
|
314
|
+
type: "command",
|
|
315
|
+
command: sessionStartScript
|
|
316
|
+
}
|
|
317
|
+
]
|
|
318
|
+
});
|
|
319
|
+
// Add SessionEnd hook (only if the file exists)
|
|
320
|
+
if (existsSync(sessionEndScript)) {
|
|
321
|
+
if (!settings.hooks.SessionEnd) {
|
|
322
|
+
settings.hooks.SessionEnd = [];
|
|
323
|
+
}
|
|
324
|
+
// ✅ CORRECT: Non-tool hooks have no 'pattern' or 'matcher' field
|
|
325
|
+
settings.hooks.SessionEnd.push({
|
|
326
|
+
hooks: [{
|
|
327
|
+
type: "command",
|
|
328
|
+
command: sessionEndScript,
|
|
329
|
+
timeout: 180000
|
|
330
|
+
}]
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
// Write updated settings
|
|
334
|
+
try {
|
|
335
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
336
|
+
console.log(CLI_MESSAGES.INSTALLATION.SETTINGS_WRITTEN(settingsType, settingsPath));
|
|
337
|
+
console.log('');
|
|
338
|
+
console.log(CLI_MESSAGES.INSTALLATION.SUCCESS);
|
|
339
|
+
// Store backend setting in user settings
|
|
340
|
+
const userSettingsDir = join(homedir(), '.claude-mem');
|
|
341
|
+
const userSettingsPath = join(userSettingsDir, 'settings.json');
|
|
342
|
+
let userSettings = {};
|
|
343
|
+
if (existsSync(userSettingsPath)) {
|
|
344
|
+
try {
|
|
345
|
+
userSettings = JSON.parse(readFileSync(userSettingsPath, 'utf8'));
|
|
346
|
+
}
|
|
347
|
+
catch (error) {
|
|
348
|
+
console.log(`⚠️ Creating new user settings file (could not parse existing): ${error.message}`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
userSettings.backend = 'weaviate';
|
|
352
|
+
userSettings.installed = true;
|
|
353
|
+
// Always use embedded Weaviate - no external config needed
|
|
354
|
+
userSettings.embedded = true;
|
|
355
|
+
try {
|
|
356
|
+
writeFileSync(userSettingsPath, JSON.stringify(userSettings, null, 2));
|
|
357
|
+
console.log(`✅ Backend configuration saved: embedded Weaviate`);
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
console.log(`⚠️ Could not save backend settings: ${error.message}`);
|
|
361
|
+
}
|
|
362
|
+
console.log('');
|
|
363
|
+
console.log('Next steps:');
|
|
364
|
+
CLI_MESSAGES.NEXT_STEPS.forEach((step) => {
|
|
365
|
+
console.log(step);
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
catch (error) {
|
|
369
|
+
console.error(CLI_MESSAGES.ERRORS.SETTINGS_WRITE_FAILED(settingsPath, error.message));
|
|
370
|
+
process.exit(1);
|
|
371
|
+
}
|
|
372
|
+
}
|