claude-mem 3.3.7 → 3.3.9
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/README.md +183 -46
- package/dist/bin/cli.d.ts +2 -0
- package/dist/bin/cli.js +179 -0
- package/dist/commands/compress.d.ts +2 -0
- package/dist/commands/compress.js +27 -0
- package/dist/commands/hooks.d.ts +19 -0
- package/dist/commands/hooks.js +131 -0
- package/dist/commands/install.d.ts +2 -0
- package/dist/commands/install.js +836 -0
- package/dist/commands/load-context.d.ts +2 -0
- package/dist/commands/load-context.js +151 -0
- package/dist/commands/logs.d.ts +2 -0
- package/dist/commands/logs.js +76 -0
- package/dist/commands/migrate-to-jsonl.d.ts +5 -0
- package/dist/commands/migrate-to-jsonl.js +99 -0
- package/dist/commands/restore.d.ts +1 -0
- package/dist/commands/restore.js +20 -0
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +136 -0
- package/dist/commands/trash-empty.d.ts +3 -0
- package/dist/commands/trash-empty.js +56 -0
- package/dist/commands/trash-view.d.ts +1 -0
- package/dist/commands/trash-view.js +101 -0
- package/dist/commands/trash.d.ts +6 -0
- package/dist/commands/trash.js +49 -0
- package/dist/commands/uninstall.d.ts +2 -0
- package/dist/commands/uninstall.js +107 -0
- package/dist/constants.d.ts +271 -0
- package/dist/constants.js +199 -0
- package/dist/core/compression/TranscriptCompressor.d.ts +79 -0
- package/dist/core/compression/TranscriptCompressor.js +585 -0
- package/dist/core/orchestration/PromptOrchestrator.d.ts +165 -0
- package/dist/core/orchestration/PromptOrchestrator.js +182 -0
- package/dist/lib/time-utils.d.ts +5 -0
- package/dist/lib/time-utils.js +70 -0
- package/dist/prompts/constants.d.ts +126 -0
- package/dist/prompts/constants.js +161 -0
- package/dist/prompts/index.d.ts +10 -0
- package/dist/prompts/index.js +11 -0
- package/dist/prompts/templates/analysis/AnalysisTemplates.d.ts +13 -0
- package/dist/prompts/templates/analysis/AnalysisTemplates.js +94 -0
- package/dist/prompts/templates/context/ContextTemplates.d.ts +119 -0
- package/dist/prompts/templates/context/ContextTemplates.js +399 -0
- package/dist/prompts/templates/hooks/HookTemplates.d.ts +175 -0
- package/dist/prompts/templates/hooks/HookTemplates.js +394 -0
- package/dist/prompts/templates/hooks/HookTemplates.test.d.ts +7 -0
- package/dist/prompts/templates/hooks/HookTemplates.test.js +127 -0
- package/dist/shared/config.d.ts +4 -0
- package/dist/shared/config.js +41 -0
- package/dist/shared/error-handler.d.ts +22 -0
- package/dist/shared/error-handler.js +142 -0
- package/dist/shared/logger.d.ts +19 -0
- package/dist/shared/logger.js +51 -0
- package/dist/shared/paths.d.ts +28 -0
- package/dist/shared/paths.js +100 -0
- package/dist/shared/settings.d.ts +41 -0
- package/dist/shared/settings.js +81 -0
- package/dist/shared/types.d.ts +145 -0
- package/dist/shared/types.js +78 -0
- package/docs/STATUS.md +155 -0
- package/docs/chroma-backend-migration.md +161 -0
- package/docs/landing-page-outline.md +287 -0
- package/docs/multi-platform-builds.md +96 -0
- package/docs/plans/cloud-service-plan.md +274 -0
- package/docs/plans/fix-response-format-issue.md +61 -0
- package/docs/plans/restructure-session-hook-output.md +102 -0
- package/docs/plans/session-start-hook-investigation.md +45 -0
- package/docs/plans/src-reorganization-plan.md +181 -0
- package/docs/plans/terminal-effects-decision.md +22 -0
- package/docs/plans/terminal-effects-integration.md +82 -0
- package/docs/plans/trash-bin-feature-plan.md +240 -0
- package/docs/plans/trash-bin-minimal-plan.md +102 -0
- package/docs/reference/bun-single-executable.md +584 -0
- package/docs/reference/cc-output-styles.md +99 -0
- package/docs/reference/chroma-mcp-project-memory-example.md +80 -0
- package/docs/reference/chroma-mcp-team-example.md +92 -0
- package/docs/reference/claude-code/cc-hooks.md +787 -0
- package/docs/reference/claude-code/cc-status-line.md +202 -0
- package/docs/reference/claude-code/hook-configuration.md +173 -0
- package/docs/reference/claude-code/hook-responses.md +127 -0
- package/docs/reference/claude-code/hooks.md +175 -0
- package/docs/reference/claude-code/mcp-configuration.md +133 -0
- package/docs/reference/claude-code/session-start-hook.md +82 -0
- package/docs/reference/load-context-format-example.md +33 -0
- package/docs/reference/mcp-sdk/mcp-typescript-sdk-readme.md +1323 -0
- package/docs/reference/mcp-sdk/server-implementation.md +286 -0
- package/docs/reference/mcp-sdk/stdio-transport.md +345 -0
- package/docs/todos/fix-response-format-tasks.md +43 -0
- package/docs/todos/implementation-todos.md +280 -0
- package/docs/todos/restructure-hook-output-tasks.md +103 -0
- package/docs/todos/session-start-hook-fix.md +26 -0
- package/docs/todos/terminal-effects-tasks.md +42 -0
- package/docs/todos/trash-bin-implementation-todos.md +348 -0
- package/docs/todos/trash-bin-minimal-todos.md +27 -0
- package/package.json +56 -6
- package/claude-mem +0 -0
|
@@ -0,0 +1,836 @@
|
|
|
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 { fileURLToPath } from 'url';
|
|
6
|
+
import * as p from '@clack/prompts';
|
|
7
|
+
import figlet from 'figlet';
|
|
8
|
+
import gradient from 'gradient-string';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import boxen from 'boxen';
|
|
11
|
+
import { PACKAGE_NAME } from '../shared/config.js';
|
|
12
|
+
// Enhanced animation utilities
|
|
13
|
+
function createLoadingAnimation(message) {
|
|
14
|
+
let interval;
|
|
15
|
+
let frame = 0;
|
|
16
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
17
|
+
return {
|
|
18
|
+
start() {
|
|
19
|
+
interval = setInterval(() => {
|
|
20
|
+
process.stdout.write(`\r${chalk.cyan(frames[frame % frames.length])} ${message}`);
|
|
21
|
+
frame++;
|
|
22
|
+
}, 50); // Faster spinner animation (was 80ms)
|
|
23
|
+
},
|
|
24
|
+
stop(result, success = true) {
|
|
25
|
+
clearInterval(interval);
|
|
26
|
+
const icon = success ? chalk.green('✓') : chalk.red('✗');
|
|
27
|
+
process.stdout.write(`\r${icon} ${result}\n`);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
// Create animated rainbow text with adjustable speed
|
|
32
|
+
function animatedRainbow(text, speed = 100) {
|
|
33
|
+
return new Promise((resolve) => {
|
|
34
|
+
let offset = 0;
|
|
35
|
+
const maxFrames = 10;
|
|
36
|
+
const interval = setInterval(() => {
|
|
37
|
+
// Create a shifted gradient by rotating through different presets
|
|
38
|
+
const gradients = [fastRainbow, vibrantRainbow, gradient.rainbow, gradient.pastel];
|
|
39
|
+
const shifted = gradients[offset % gradients.length](text);
|
|
40
|
+
process.stdout.write('\r' + shifted);
|
|
41
|
+
offset++;
|
|
42
|
+
if (offset >= maxFrames) {
|
|
43
|
+
clearInterval(interval);
|
|
44
|
+
resolve();
|
|
45
|
+
}
|
|
46
|
+
}, speed);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
// Sleep utility for smooth animations
|
|
50
|
+
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
51
|
+
// Fast rainbow gradient preset with tighter color transitions
|
|
52
|
+
const fastRainbow = gradient(['#ff0000', '#ff4500', '#ffa500', '#ffff00', '#00ff00', '#00ffff', '#0000ff', '#8b00ff']);
|
|
53
|
+
const vibrantRainbow = gradient(['#ff006e', '#fb5607', '#ffbe0b', '#8338ec', '#3a86ff']);
|
|
54
|
+
// <Block> Silent Prerequisites validation - no visual output unless error
|
|
55
|
+
async function validatePrerequisites() {
|
|
56
|
+
// No announcement, just run checks silently
|
|
57
|
+
const checks = [
|
|
58
|
+
{
|
|
59
|
+
name: 'Node.js version',
|
|
60
|
+
check: async () => {
|
|
61
|
+
const nodeVersion = process.versions.node;
|
|
62
|
+
const [major] = nodeVersion.split('.').map(Number);
|
|
63
|
+
return {
|
|
64
|
+
success: major >= 18,
|
|
65
|
+
message: major >= 18 ? '' : `Node.js ${nodeVersion} is below required version 18.0.0`
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: 'Claude Code CLI',
|
|
71
|
+
check: async () => {
|
|
72
|
+
try {
|
|
73
|
+
execSync('which claude', { stdio: 'ignore' });
|
|
74
|
+
return { success: true, message: '' };
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return { success: false, message: 'Claude Code CLI not found. Please install: https://docs.anthropic.com/claude/docs/claude-code' };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: 'Write permissions',
|
|
83
|
+
check: async () => {
|
|
84
|
+
const testDir = join(homedir(), '.claude-mem-test');
|
|
85
|
+
try {
|
|
86
|
+
mkdirSync(testDir, { recursive: true });
|
|
87
|
+
writeFileSync(join(testDir, 'test'), 'test');
|
|
88
|
+
execSync(`rm -rf ${testDir}`, { stdio: 'ignore' });
|
|
89
|
+
return { success: true, message: '' };
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return { success: false, message: 'No write permissions to home directory' };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
];
|
|
97
|
+
// Run all checks silently
|
|
98
|
+
for (const { name, check } of checks) {
|
|
99
|
+
const result = await check();
|
|
100
|
+
if (!result.success) {
|
|
101
|
+
// Only show output if there's an error
|
|
102
|
+
console.log(boxen(chalk.red(`❌ ${name} check failed!\n\n${result.message}`), {
|
|
103
|
+
padding: 1,
|
|
104
|
+
margin: 1,
|
|
105
|
+
borderStyle: 'double',
|
|
106
|
+
borderColor: 'red'
|
|
107
|
+
}));
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Success - no output, just return true
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
// </Block>
|
|
115
|
+
// <Block> Claude binary path detection
|
|
116
|
+
function detectClaudePath() {
|
|
117
|
+
try {
|
|
118
|
+
const path = execSync('which claude', {
|
|
119
|
+
encoding: 'utf8',
|
|
120
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
121
|
+
}).trim();
|
|
122
|
+
return path || null;
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// </Block>
|
|
129
|
+
// <Block> Installation status detection
|
|
130
|
+
function detectExistingInstallation() {
|
|
131
|
+
const result = {
|
|
132
|
+
hasHooks: false,
|
|
133
|
+
hasChromaMcp: false,
|
|
134
|
+
hasSettings: false,
|
|
135
|
+
scope: undefined
|
|
136
|
+
};
|
|
137
|
+
// Check for hooks
|
|
138
|
+
const hooksDir = join(homedir(), '.claude-mem', 'hooks');
|
|
139
|
+
result.hasHooks = existsSync(hooksDir) &&
|
|
140
|
+
existsSync(join(hooksDir, 'pre-compact.js')) &&
|
|
141
|
+
existsSync(join(hooksDir, 'session-start.js'));
|
|
142
|
+
// Check for Chroma MCP server configuration
|
|
143
|
+
const userMcpPath = join(homedir(), '.claude.json');
|
|
144
|
+
const projectMcpPath = join(process.cwd(), '.mcp.json');
|
|
145
|
+
if (existsSync(userMcpPath)) {
|
|
146
|
+
try {
|
|
147
|
+
const config = JSON.parse(readFileSync(userMcpPath, 'utf8'));
|
|
148
|
+
if (config.mcpServers?.['claude-mem']) {
|
|
149
|
+
result.hasChromaMcp = true;
|
|
150
|
+
result.scope = 'user';
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch { }
|
|
154
|
+
}
|
|
155
|
+
if (existsSync(projectMcpPath)) {
|
|
156
|
+
try {
|
|
157
|
+
const config = JSON.parse(readFileSync(projectMcpPath, 'utf8'));
|
|
158
|
+
if (config.mcpServers?.['claude-mem']) {
|
|
159
|
+
result.hasChromaMcp = true;
|
|
160
|
+
result.scope = 'project';
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch { }
|
|
164
|
+
}
|
|
165
|
+
// Check for settings
|
|
166
|
+
const userSettingsPath = join(homedir(), '.claude-mem', 'settings.json');
|
|
167
|
+
result.hasSettings = existsSync(userSettingsPath);
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
// </Block>
|
|
171
|
+
// <Block> Interactive installation wizard
|
|
172
|
+
async function runInstallationWizard(existingInstall) {
|
|
173
|
+
const config = {};
|
|
174
|
+
// If existing installation found, ask about reinstallation
|
|
175
|
+
if (existingInstall.hasHooks || existingInstall.hasChromaMcp) {
|
|
176
|
+
const shouldReinstall = await p.confirm({
|
|
177
|
+
message: '🧠 Existing claude-mem installation detected. Your memories and data are safe!\n\nReinstall to update hooks and configuration?',
|
|
178
|
+
initialValue: true
|
|
179
|
+
});
|
|
180
|
+
if (p.isCancel(shouldReinstall)) {
|
|
181
|
+
p.cancel('Installation cancelled');
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
if (!shouldReinstall) {
|
|
185
|
+
p.cancel('Installation cancelled');
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
config.forceReinstall = true;
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
config.forceReinstall = false;
|
|
192
|
+
}
|
|
193
|
+
// Select installation scope
|
|
194
|
+
const scope = await p.select({
|
|
195
|
+
message: 'Select installation scope',
|
|
196
|
+
options: [
|
|
197
|
+
{
|
|
198
|
+
value: 'user',
|
|
199
|
+
label: 'User (Recommended)',
|
|
200
|
+
hint: 'Install for current user (~/.claude)'
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
value: 'project',
|
|
204
|
+
label: 'Project',
|
|
205
|
+
hint: 'Install for current project only (./.mcp.json)'
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
value: 'local',
|
|
209
|
+
label: 'Local',
|
|
210
|
+
hint: 'Custom local installation'
|
|
211
|
+
}
|
|
212
|
+
],
|
|
213
|
+
initialValue: existingInstall.scope || 'user'
|
|
214
|
+
});
|
|
215
|
+
if (p.isCancel(scope)) {
|
|
216
|
+
p.cancel('Installation cancelled');
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
config.scope = scope;
|
|
220
|
+
// If local scope, ask for custom path
|
|
221
|
+
if (scope === 'local') {
|
|
222
|
+
const customPath = await p.text({
|
|
223
|
+
message: 'Enter custom installation directory',
|
|
224
|
+
placeholder: join(process.cwd(), '.claude-mem'),
|
|
225
|
+
validate: (value) => {
|
|
226
|
+
if (!value)
|
|
227
|
+
return 'Path is required';
|
|
228
|
+
if (!value.startsWith('/') && !value.startsWith('~')) {
|
|
229
|
+
return 'Please provide an absolute path';
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
if (p.isCancel(customPath)) {
|
|
234
|
+
p.cancel('Installation cancelled');
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
config.customPath = customPath;
|
|
238
|
+
}
|
|
239
|
+
// Use default hook timeout (3 minutes)
|
|
240
|
+
config.hookTimeout = 180000;
|
|
241
|
+
// Always install/reinstall Chroma MCP - it's required for claude-mem to work
|
|
242
|
+
// Ask about smart trash alias
|
|
243
|
+
const enableSmartTrash = await p.confirm({
|
|
244
|
+
message: 'Enable Smart Trash? This creates an alias for "rm" that moves files to ~/.claude-mem/trash instead of permanently deleting them. You can restore files anytime by typing "claude-mem restore".',
|
|
245
|
+
initialValue: true
|
|
246
|
+
});
|
|
247
|
+
if (p.isCancel(enableSmartTrash)) {
|
|
248
|
+
p.cancel('Installation cancelled');
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
config.enableSmartTrash = enableSmartTrash;
|
|
252
|
+
// Ask about save-on-clear
|
|
253
|
+
const saveMemoriesOnClear = await p.confirm({
|
|
254
|
+
message: 'claude-mem is designed to save "memories" when you type /compact. The official compact summary + claude-mem produces the best ongoing results, but sometimes you may want to completely clear the context and still retain the "memories" from your last conversation.\n\nWould you like to save memories when you type "/clear" in Claude Code? When running /clear with this on, it takes about a minute to save memories before your new session starts.',
|
|
255
|
+
initialValue: false
|
|
256
|
+
});
|
|
257
|
+
if (p.isCancel(saveMemoriesOnClear)) {
|
|
258
|
+
p.cancel('Installation cancelled');
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
config.saveMemoriesOnClear = saveMemoriesOnClear;
|
|
262
|
+
return config;
|
|
263
|
+
}
|
|
264
|
+
// </Block>
|
|
265
|
+
// <Block> Backup existing configuration
|
|
266
|
+
async function backupExistingConfig() {
|
|
267
|
+
const backupDir = join(homedir(), '.claude-mem', 'backups', new Date().toISOString().replace(/[:.]/g, '-'));
|
|
268
|
+
try {
|
|
269
|
+
mkdirSync(backupDir, { recursive: true });
|
|
270
|
+
// Backup hooks if they exist
|
|
271
|
+
const hooksDir = join(homedir(), '.claude-mem', 'hooks');
|
|
272
|
+
if (existsSync(hooksDir)) {
|
|
273
|
+
copyFileRecursively(hooksDir, join(backupDir, 'hooks'));
|
|
274
|
+
}
|
|
275
|
+
// Backup settings
|
|
276
|
+
const settingsPath = join(homedir(), '.claude-mem', 'settings.json');
|
|
277
|
+
if (existsSync(settingsPath)) {
|
|
278
|
+
copyFileSync(settingsPath, join(backupDir, 'settings.json'));
|
|
279
|
+
}
|
|
280
|
+
// Backup Claude settings
|
|
281
|
+
const claudeSettingsPath = join(homedir(), '.claude', 'settings.json');
|
|
282
|
+
if (existsSync(claudeSettingsPath)) {
|
|
283
|
+
copyFileSync(claudeSettingsPath, join(backupDir, 'claude-settings.json'));
|
|
284
|
+
}
|
|
285
|
+
return backupDir;
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// </Block>
|
|
292
|
+
// <Block> Directory structure creation - natural setup flow
|
|
293
|
+
function ensureDirectoryStructure() {
|
|
294
|
+
const claudeMemDir = join(homedir(), '.claude-mem');
|
|
295
|
+
const hooksDir = join(claudeMemDir, 'hooks');
|
|
296
|
+
const indexDir = join(claudeMemDir, 'index');
|
|
297
|
+
const archivesDir = join(claudeMemDir, 'archives');
|
|
298
|
+
const logsDir = join(claudeMemDir, 'logs');
|
|
299
|
+
const backupsDir = join(claudeMemDir, 'backups');
|
|
300
|
+
[claudeMemDir, hooksDir, indexDir, archivesDir, logsDir, backupsDir].forEach(dir => {
|
|
301
|
+
if (!existsSync(dir)) {
|
|
302
|
+
mkdirSync(dir, { recursive: true });
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
// </Block>
|
|
307
|
+
function copyFileRecursively(src, dest) {
|
|
308
|
+
const stat = statSync(src);
|
|
309
|
+
if (stat.isDirectory()) {
|
|
310
|
+
if (!existsSync(dest)) {
|
|
311
|
+
mkdirSync(dest, { recursive: true });
|
|
312
|
+
}
|
|
313
|
+
const files = readdirSync(src);
|
|
314
|
+
files.forEach((file) => {
|
|
315
|
+
copyFileRecursively(join(src, file), join(dest, file));
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
copyFileSync(src, dest);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function writeHookFiles(timeout = 180000) {
|
|
323
|
+
const hooksDir = join(homedir(), '.claude-mem', 'hooks');
|
|
324
|
+
// Find the installed package hooks directory
|
|
325
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
326
|
+
const __dirname = dirname(__filename);
|
|
327
|
+
const packageHooksDir = join(__dirname, '..', '..', 'hooks');
|
|
328
|
+
// Copy hook files from the package instead of creating wrappers
|
|
329
|
+
const hooks = ['pre-compact.js', 'session-start.js', 'session-end.js'];
|
|
330
|
+
for (const hookName of hooks) {
|
|
331
|
+
const sourcePath = join(packageHooksDir, hookName);
|
|
332
|
+
const destPath = join(hooksDir, hookName);
|
|
333
|
+
if (existsSync(sourcePath)) {
|
|
334
|
+
copyFileSync(sourcePath, destPath);
|
|
335
|
+
chmodSync(destPath, 0o755);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
// Copy shared directory if it exists
|
|
339
|
+
const sourceSharedDir = join(packageHooksDir, 'shared');
|
|
340
|
+
const destSharedDir = join(hooksDir, 'shared');
|
|
341
|
+
if (existsSync(sourceSharedDir)) {
|
|
342
|
+
copyFileRecursively(sourceSharedDir, destSharedDir);
|
|
343
|
+
}
|
|
344
|
+
// Write configuration with custom timeout
|
|
345
|
+
const hookConfigPath = join(hooksDir, 'config.json');
|
|
346
|
+
const hookConfig = {
|
|
347
|
+
packageName: PACKAGE_NAME,
|
|
348
|
+
cliCommand: PACKAGE_NAME,
|
|
349
|
+
backend: 'chroma',
|
|
350
|
+
timeout
|
|
351
|
+
};
|
|
352
|
+
writeFileSync(hookConfigPath, JSON.stringify(hookConfig, null, 2));
|
|
353
|
+
}
|
|
354
|
+
function ensureClaudeMdInstructions() {
|
|
355
|
+
const claudeMdPath = join(homedir(), '.claude', 'CLAUDE.md');
|
|
356
|
+
const claudeMdDir = dirname(claudeMdPath);
|
|
357
|
+
// Ensure .claude directory exists
|
|
358
|
+
if (!existsSync(claudeMdDir)) {
|
|
359
|
+
mkdirSync(claudeMdDir, { recursive: true });
|
|
360
|
+
}
|
|
361
|
+
const instructions = `
|
|
362
|
+
<!-- CLAUDE-MEM INSTRUCTIONS -->
|
|
363
|
+
- You have access to a persistent memory system via the Chroma MCP server (installed as "claude-mem")
|
|
364
|
+
- The memory system automatically compresses and stores context from your sessions
|
|
365
|
+
- Available MCP tools:
|
|
366
|
+
- \`mcp__claude-mem__chroma_add_documents\`: Store new knowledge as documents in the "claude_memories" collection
|
|
367
|
+
- \`mcp__claude-mem__chroma_query_documents\`: Search for relevant memories using semantic search in the "claude_memories" collection
|
|
368
|
+
- \`mcp__claude-mem__chroma_get_documents\`: Retrieve specific documents by IDs from the "claude_memories" collection
|
|
369
|
+
- **Document Structure:**
|
|
370
|
+
- Documents are stored as natural language descriptions of knowledge
|
|
371
|
+
- Each document has metadata including: timestamp, session_id, keywords, entity_type
|
|
372
|
+
- Use descriptive content that captures the essence of what was learned or accomplished
|
|
373
|
+
- **Search Tips:**
|
|
374
|
+
- Use semantic queries: \`chroma_query_documents\` with natural language queries
|
|
375
|
+
- Search by metadata: Use \`where\` parameter to filter by entity_type, timestamp, etc.
|
|
376
|
+
- Use keywords in metadata for precise filtering
|
|
377
|
+
- **Storage Format:**
|
|
378
|
+
- Store knowledge as readable documents rather than structured entities
|
|
379
|
+
- Include context, rationale, and outcomes in document content
|
|
380
|
+
- Use metadata to categorize and filter memories
|
|
381
|
+
- **Smart Retrieval Commands:**
|
|
382
|
+
- **Find similar concepts**: \`chroma_query_documents(["your search terms"])\`
|
|
383
|
+
- Example: \`chroma_query_documents(["websocket", "connection"])\` - finds all websocket-related memories
|
|
384
|
+
- **Load specific memory**: \`chroma_get_documents(["document_id"])\`
|
|
385
|
+
- Example: \`chroma_get_documents(["project_session_1"])\` - loads exact memory by ID
|
|
386
|
+
- **Search by metadata**: Use keywords from memories to find related items
|
|
387
|
+
- The system uses semantic search, so "auth" will find "authentication", "login", etc.
|
|
388
|
+
- **Find connected memories**: Look for memories with related_to fields linking them together
|
|
389
|
+
- Collection name: "claude_memories"
|
|
390
|
+
- Compressed session archives are stored in ~/.claude-mem/archives/
|
|
391
|
+
- **Optimal Search Strategies:**
|
|
392
|
+
- **Most Effective Patterns:**
|
|
393
|
+
- Session continuity: \`where: {session_id: "xxx"}\` - precise retrieval of all session work
|
|
394
|
+
- Find specific fixes: \`where: {type: "fix"}\` - filters to only bug fixes
|
|
395
|
+
- Natural language queries: "fixing overview display in templates" beats "fix AND overview AND template"
|
|
396
|
+
- Include file/function names: "extractOverview ContextTemplates" for precise code location
|
|
397
|
+
- **Search by Intent:**
|
|
398
|
+
- Debug: Use exact error messages or symptom descriptions
|
|
399
|
+
- Find code: Include function names + file names when known
|
|
400
|
+
- Architecture: Search "migration from X to Y" or "refactored X"
|
|
401
|
+
- Agent work: Search agent names directly (e.g., "steve-krug-ux")
|
|
402
|
+
- **What Doesn't Work:**
|
|
403
|
+
- Boolean operators (AND/OR) - treated as literal text
|
|
404
|
+
- Timestamp filtering with strings - needs numeric values
|
|
405
|
+
- Generic tech terms alone - too broad, add context
|
|
406
|
+
- Complex where clauses - only basic operators supported ($eq, $ne, $in)
|
|
407
|
+
- **Best Practice:** Semantic search for discovery, metadata filtering for precision. Combine both when possible.
|
|
408
|
+
<!-- /CLAUDE-MEM INSTRUCTIONS -->`;
|
|
409
|
+
// Check if file exists and read content
|
|
410
|
+
let content = '';
|
|
411
|
+
if (existsSync(claudeMdPath)) {
|
|
412
|
+
content = readFileSync(claudeMdPath, 'utf8');
|
|
413
|
+
// Check if instructions already exist
|
|
414
|
+
if (content.includes('<!-- CLAUDE-MEM INSTRUCTIONS -->')) {
|
|
415
|
+
// Replace existing instructions
|
|
416
|
+
const startMarker = '<!-- CLAUDE-MEM INSTRUCTIONS -->';
|
|
417
|
+
const endMarker = '<!-- /CLAUDE-MEM INSTRUCTIONS -->';
|
|
418
|
+
const startIndex = content.indexOf(startMarker);
|
|
419
|
+
const endIndex = content.indexOf(endMarker) + endMarker.length;
|
|
420
|
+
if (startIndex !== -1 && endIndex !== -1) {
|
|
421
|
+
content = content.substring(0, startIndex) + instructions.trim() + content.substring(endIndex);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
// Append instructions to the end
|
|
426
|
+
content = content.trim() + '\n' + instructions;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
// Create new file with instructions
|
|
431
|
+
content = instructions.trim();
|
|
432
|
+
}
|
|
433
|
+
// Write the updated content
|
|
434
|
+
writeFileSync(claudeMdPath, content);
|
|
435
|
+
}
|
|
436
|
+
async function installChromaMcp() {
|
|
437
|
+
const loader = createLoadingAnimation('Installing Chroma MCP server...');
|
|
438
|
+
loader.start();
|
|
439
|
+
try {
|
|
440
|
+
await sleep(400); // Realistic timing
|
|
441
|
+
// Remove existing claude-mem MCP server if it exists (silently ignore errors)
|
|
442
|
+
try {
|
|
443
|
+
execSync('claude mcp remove claude-mem', { stdio: 'pipe' });
|
|
444
|
+
await sleep(200);
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
// Ignore errors - server may not exist
|
|
448
|
+
}
|
|
449
|
+
// Install fresh Chroma MCP server
|
|
450
|
+
const chromaMcpCommand = `claude mcp add claude-mem -- uvx chroma-mcp --client-type persistent --data-dir ${homedir()}/.claude-mem/chroma`;
|
|
451
|
+
execSync(chromaMcpCommand, { stdio: 'pipe' });
|
|
452
|
+
await sleep(300);
|
|
453
|
+
loader.stop(vibrantRainbow('Chroma MCP server installed successfully! 🚀'), true);
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
catch (error) {
|
|
457
|
+
loader.stop('Chroma MCP server installation failed', false);
|
|
458
|
+
console.log(boxen(chalk.yellow(`⚠️ Manual installation required:\n\n${chalk.cyan(`claude mcp add claude-mem -- uvx chroma-mcp --client-type persistent --data-dir ${homedir()}/.claude-mem/chroma`)}`), {
|
|
459
|
+
padding: 1,
|
|
460
|
+
margin: 1,
|
|
461
|
+
borderStyle: 'round',
|
|
462
|
+
borderColor: 'yellow'
|
|
463
|
+
}));
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
async function configureHooks(settingsPath, config) {
|
|
468
|
+
const claudeMemHooksDir = join(homedir(), '.claude-mem', 'hooks');
|
|
469
|
+
const preCompactScript = join(claudeMemHooksDir, 'pre-compact.js');
|
|
470
|
+
const sessionStartScript = join(claudeMemHooksDir, 'session-start.js');
|
|
471
|
+
const sessionEndScript = join(claudeMemHooksDir, 'session-end.js');
|
|
472
|
+
let settings = {};
|
|
473
|
+
if (existsSync(settingsPath)) {
|
|
474
|
+
const content = readFileSync(settingsPath, 'utf8');
|
|
475
|
+
settings = JSON.parse(content);
|
|
476
|
+
}
|
|
477
|
+
// Ensure settings directory exists
|
|
478
|
+
const settingsDir = dirname(settingsPath);
|
|
479
|
+
if (!existsSync(settingsDir)) {
|
|
480
|
+
mkdirSync(settingsDir, { recursive: true });
|
|
481
|
+
}
|
|
482
|
+
// Initialize hooks structure if it doesn't exist
|
|
483
|
+
if (!settings.hooks) {
|
|
484
|
+
settings.hooks = {};
|
|
485
|
+
}
|
|
486
|
+
// Remove existing claude-mem hooks to ensure clean installation/update
|
|
487
|
+
// Non-tool hooks: filter out configs where hooks contain our commands
|
|
488
|
+
if (settings.hooks.PreCompact) {
|
|
489
|
+
settings.hooks.PreCompact = settings.hooks.PreCompact.filter((cfg) => !cfg.hooks?.some((hook) => hook.command?.includes(PACKAGE_NAME) || hook.command?.includes('pre-compact.js')));
|
|
490
|
+
if (!settings.hooks.PreCompact.length)
|
|
491
|
+
delete settings.hooks.PreCompact;
|
|
492
|
+
}
|
|
493
|
+
if (settings.hooks.SessionStart) {
|
|
494
|
+
settings.hooks.SessionStart = settings.hooks.SessionStart.filter((cfg) => !cfg.hooks?.some((hook) => hook.command?.includes(PACKAGE_NAME) || hook.command?.includes('session-start.js')));
|
|
495
|
+
if (!settings.hooks.SessionStart.length)
|
|
496
|
+
delete settings.hooks.SessionStart;
|
|
497
|
+
}
|
|
498
|
+
if (settings.hooks.SessionEnd) {
|
|
499
|
+
settings.hooks.SessionEnd = settings.hooks.SessionEnd.filter((cfg) => !cfg.hooks?.some((hook) => hook.command?.includes(PACKAGE_NAME) || hook.command?.includes('session-end.js')));
|
|
500
|
+
if (!settings.hooks.SessionEnd.length)
|
|
501
|
+
delete settings.hooks.SessionEnd;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* 🔒 LOCKED by @docs-agent | Change to 🔑 to allow @docs-agent edits
|
|
505
|
+
*
|
|
506
|
+
* OFFICIAL DOCS: Claude Code Hooks Configuration v2025
|
|
507
|
+
* Last Verified: 2025-08-31
|
|
508
|
+
*
|
|
509
|
+
* Hook Configuration Structure Requirements:
|
|
510
|
+
* - Tool-related hooks (PreToolUse, PostToolUse): Use 'matcher' field for tool patterns
|
|
511
|
+
* - Non-tool hooks (PreCompact, SessionStart, SessionEnd, etc.): NO matcher/pattern field
|
|
512
|
+
*
|
|
513
|
+
* Correct Non-Tool Hook Structure:
|
|
514
|
+
* {
|
|
515
|
+
* hooks: [{
|
|
516
|
+
* type: "command",
|
|
517
|
+
* command: "/path/to/script.js"
|
|
518
|
+
* }]
|
|
519
|
+
* }
|
|
520
|
+
*
|
|
521
|
+
* @see https://docs.anthropic.com/en/docs/claude-code/hooks
|
|
522
|
+
* @see docs/claude-code/hook-configuration.md for full documentation
|
|
523
|
+
*/
|
|
524
|
+
// Add PreCompact hook - Non-tool hook (no matcher field)
|
|
525
|
+
if (!settings.hooks.PreCompact) {
|
|
526
|
+
settings.hooks.PreCompact = [];
|
|
527
|
+
}
|
|
528
|
+
// ✅ CORRECT: Non-tool hooks have no 'pattern' or 'matcher' field
|
|
529
|
+
settings.hooks.PreCompact.push({
|
|
530
|
+
hooks: [
|
|
531
|
+
{
|
|
532
|
+
type: "command",
|
|
533
|
+
command: preCompactScript,
|
|
534
|
+
timeout: config.hookTimeout
|
|
535
|
+
}
|
|
536
|
+
]
|
|
537
|
+
});
|
|
538
|
+
// Add SessionStart hook - Non-tool hook (no matcher field)
|
|
539
|
+
if (!settings.hooks.SessionStart) {
|
|
540
|
+
settings.hooks.SessionStart = [];
|
|
541
|
+
}
|
|
542
|
+
// ✅ CORRECT: Non-tool hooks have no 'pattern' or 'matcher' field
|
|
543
|
+
settings.hooks.SessionStart.push({
|
|
544
|
+
hooks: [
|
|
545
|
+
{
|
|
546
|
+
type: "command",
|
|
547
|
+
command: sessionStartScript
|
|
548
|
+
}
|
|
549
|
+
]
|
|
550
|
+
});
|
|
551
|
+
// Add SessionEnd hook (only if the file exists)
|
|
552
|
+
if (existsSync(sessionEndScript)) {
|
|
553
|
+
if (!settings.hooks.SessionEnd) {
|
|
554
|
+
settings.hooks.SessionEnd = [];
|
|
555
|
+
}
|
|
556
|
+
// ✅ CORRECT: Non-tool hooks have no 'pattern' or 'matcher' field
|
|
557
|
+
settings.hooks.SessionEnd.push({
|
|
558
|
+
hooks: [{
|
|
559
|
+
type: "command",
|
|
560
|
+
command: sessionEndScript,
|
|
561
|
+
timeout: config.hookTimeout
|
|
562
|
+
}]
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
// Write updated settings
|
|
566
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
567
|
+
}
|
|
568
|
+
async function configureSmartTrashAlias() {
|
|
569
|
+
const homeDir = homedir();
|
|
570
|
+
const shellConfigs = [
|
|
571
|
+
join(homeDir, '.bashrc'),
|
|
572
|
+
join(homeDir, '.zshrc'),
|
|
573
|
+
join(homeDir, '.bash_profile')
|
|
574
|
+
];
|
|
575
|
+
const aliasLine = 'alias rm="claude-mem trash"';
|
|
576
|
+
const commentLine = '# claude-mem smart trash alias';
|
|
577
|
+
for (const configPath of shellConfigs) {
|
|
578
|
+
if (!existsSync(configPath))
|
|
579
|
+
continue;
|
|
580
|
+
try {
|
|
581
|
+
let content = readFileSync(configPath, 'utf8');
|
|
582
|
+
// Check if alias already exists
|
|
583
|
+
if (content.includes(aliasLine)) {
|
|
584
|
+
continue; // Already configured
|
|
585
|
+
}
|
|
586
|
+
// Add the alias
|
|
587
|
+
const aliasBlock = `\n${commentLine}\n${aliasLine}\n`;
|
|
588
|
+
content += aliasBlock;
|
|
589
|
+
writeFileSync(configPath, content);
|
|
590
|
+
}
|
|
591
|
+
catch (error) {
|
|
592
|
+
// Silent fail - not critical
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
async function verifyInstallation() {
|
|
597
|
+
const s = p.spinner();
|
|
598
|
+
s.start('Verifying installation');
|
|
599
|
+
const issues = [];
|
|
600
|
+
// Check hooks
|
|
601
|
+
const hooksDir = join(homedir(), '.claude-mem', 'hooks');
|
|
602
|
+
if (!existsSync(join(hooksDir, 'pre-compact.js'))) {
|
|
603
|
+
issues.push('Pre-compact hook not found');
|
|
604
|
+
}
|
|
605
|
+
if (!existsSync(join(hooksDir, 'session-start.js'))) {
|
|
606
|
+
issues.push('Session-start hook not found');
|
|
607
|
+
}
|
|
608
|
+
if (issues.length > 0) {
|
|
609
|
+
s.stop('Installation verification completed with issues');
|
|
610
|
+
p.log.warn('The following issues were detected:');
|
|
611
|
+
issues.forEach(issue => p.log.error(` - ${issue}`));
|
|
612
|
+
p.log.info('The installation may not work correctly. Consider reinstalling with --force flag.');
|
|
613
|
+
}
|
|
614
|
+
else {
|
|
615
|
+
s.stop('Installation verified successfully');
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
export async function install(options = {}) {
|
|
619
|
+
// Create stunning ASCII banner
|
|
620
|
+
try {
|
|
621
|
+
const banner = await new Promise((resolve, reject) => {
|
|
622
|
+
figlet('claude-mem', (err, data) => {
|
|
623
|
+
if (err)
|
|
624
|
+
reject(err);
|
|
625
|
+
else
|
|
626
|
+
resolve(data || 'claude-mem');
|
|
627
|
+
});
|
|
628
|
+
});
|
|
629
|
+
console.log(fastRainbow(banner));
|
|
630
|
+
}
|
|
631
|
+
catch {
|
|
632
|
+
// Fallback banner if figlet fails
|
|
633
|
+
console.log(fastRainbow('\n██████╗ ██╗ ██████╗ ██╗ ██╗██████╗ ███████╗ ███████╗ ███████╗███╗ ███╗'));
|
|
634
|
+
console.log(fastRainbow('██╔═════╝██║ ██╔══██╗██║ ██║██╔══██╗██╔════╝ ██╔════╝ ██╔════╝████╗ ████║'));
|
|
635
|
+
console.log(fastRainbow('██║ ██║ ███████║██║ ██║██║ ██║█████╗ █████╗ █████╗ ██╔████╔██║'));
|
|
636
|
+
console.log(fastRainbow('██║ ██║ ██╔══██║██║ ██║██║ ██║██╔══╝ ██╔══╝ ██╔══╝ ██║╚██╔╝██║'));
|
|
637
|
+
console.log(fastRainbow('╚██████╗███████╗██║ ██║╚██████╔╝██████╔╝███████╗ ███████╗███████╗██║ ╚═╝ ██║'));
|
|
638
|
+
console.log(fastRainbow(' ╚═════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚══════╝╚══════╝╚═╝ ╚═╝'));
|
|
639
|
+
}
|
|
640
|
+
console.log(boxen(vibrantRainbow('🧠 Persistent Memory System for Claude Code\n\n✨ Transform your Claude experience with seamless context preservation\n🚀 Never lose your conversation history again'), {
|
|
641
|
+
padding: 2,
|
|
642
|
+
margin: 1,
|
|
643
|
+
borderStyle: 'double',
|
|
644
|
+
borderColor: 'magenta',
|
|
645
|
+
textAlignment: 'center'
|
|
646
|
+
}));
|
|
647
|
+
await sleep(500); // Let the banner shine
|
|
648
|
+
// Check if running with flags (non-interactive mode)
|
|
649
|
+
const isNonInteractive = options.user || options.project || options.local || options.force;
|
|
650
|
+
let config;
|
|
651
|
+
if (isNonInteractive) {
|
|
652
|
+
// Non-interactive mode - use flags
|
|
653
|
+
config = {
|
|
654
|
+
scope: options.local ? 'local' : options.project ? 'project' : 'user',
|
|
655
|
+
customPath: options.path,
|
|
656
|
+
hookTimeout: options.timeout || 180000,
|
|
657
|
+
forceReinstall: !!options.force,
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
else {
|
|
661
|
+
// Interactive mode
|
|
662
|
+
// Validate prerequisites
|
|
663
|
+
const prereqValid = await validatePrerequisites();
|
|
664
|
+
if (!prereqValid) {
|
|
665
|
+
p.outro('Please fix the prerequisites issues and try again');
|
|
666
|
+
process.exit(1);
|
|
667
|
+
}
|
|
668
|
+
// Detect existing installation
|
|
669
|
+
const existingInstall = detectExistingInstallation();
|
|
670
|
+
// Run installation wizard
|
|
671
|
+
const wizardConfig = await runInstallationWizard(existingInstall);
|
|
672
|
+
if (!wizardConfig) {
|
|
673
|
+
process.exit(0);
|
|
674
|
+
}
|
|
675
|
+
config = wizardConfig;
|
|
676
|
+
}
|
|
677
|
+
// Backup existing configuration if force reinstall
|
|
678
|
+
if (config.forceReinstall) {
|
|
679
|
+
const backupPath = await backupExistingConfig();
|
|
680
|
+
if (backupPath) {
|
|
681
|
+
p.log.info(`Backup created at: ${backupPath}`);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
// Enhanced installation steps with beautiful progress
|
|
685
|
+
console.log(vibrantRainbow('\n🚀 Beginning Installation Process\n'));
|
|
686
|
+
const installationSteps = [
|
|
687
|
+
{
|
|
688
|
+
name: 'Creating directory structure',
|
|
689
|
+
action: async () => {
|
|
690
|
+
await sleep(200);
|
|
691
|
+
ensureDirectoryStructure();
|
|
692
|
+
await sleep(100);
|
|
693
|
+
}
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
name: 'Installing Chroma MCP server',
|
|
697
|
+
action: async () => {
|
|
698
|
+
const success = await installChromaMcp();
|
|
699
|
+
if (!success)
|
|
700
|
+
throw new Error('MCP installation failed');
|
|
701
|
+
}
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
name: 'Adding CLAUDE.md instructions',
|
|
705
|
+
action: async () => {
|
|
706
|
+
await sleep(300);
|
|
707
|
+
ensureClaudeMdInstructions();
|
|
708
|
+
await sleep(200);
|
|
709
|
+
}
|
|
710
|
+
},
|
|
711
|
+
{
|
|
712
|
+
name: 'Installing memory hooks',
|
|
713
|
+
action: async () => {
|
|
714
|
+
await sleep(400);
|
|
715
|
+
writeHookFiles(config.hookTimeout);
|
|
716
|
+
await sleep(200);
|
|
717
|
+
}
|
|
718
|
+
},
|
|
719
|
+
{
|
|
720
|
+
name: 'Configuring Claude settings',
|
|
721
|
+
action: async () => {
|
|
722
|
+
await sleep(300);
|
|
723
|
+
// Determine settings path
|
|
724
|
+
let settingsPath;
|
|
725
|
+
if (config.scope === 'local' && config.customPath) {
|
|
726
|
+
settingsPath = join(config.customPath, 'settings.local.json');
|
|
727
|
+
}
|
|
728
|
+
else if (config.scope === 'project') {
|
|
729
|
+
settingsPath = join(process.cwd(), '.claude', 'settings.json');
|
|
730
|
+
}
|
|
731
|
+
else {
|
|
732
|
+
settingsPath = join(homedir(), '.claude', 'settings.json');
|
|
733
|
+
}
|
|
734
|
+
await configureHooks(settingsPath, config);
|
|
735
|
+
// Store backend setting in user settings
|
|
736
|
+
const userSettingsDir = join(homedir(), '.claude-mem');
|
|
737
|
+
const userSettingsPath = join(userSettingsDir, 'settings.json');
|
|
738
|
+
let userSettings = {};
|
|
739
|
+
if (existsSync(userSettingsPath)) {
|
|
740
|
+
try {
|
|
741
|
+
userSettings = JSON.parse(readFileSync(userSettingsPath, 'utf8'));
|
|
742
|
+
}
|
|
743
|
+
catch { }
|
|
744
|
+
}
|
|
745
|
+
userSettings.backend = 'chroma';
|
|
746
|
+
userSettings.installed = true;
|
|
747
|
+
userSettings.embedded = true;
|
|
748
|
+
userSettings.saveMemoriesOnClear = config.saveMemoriesOnClear || false;
|
|
749
|
+
// Detect and store Claude binary path
|
|
750
|
+
const claudePath = detectClaudePath();
|
|
751
|
+
if (claudePath) {
|
|
752
|
+
userSettings.claudePath = claudePath;
|
|
753
|
+
}
|
|
754
|
+
else {
|
|
755
|
+
delete userSettings.claudePath;
|
|
756
|
+
}
|
|
757
|
+
writeFileSync(userSettingsPath, JSON.stringify(userSettings, null, 2));
|
|
758
|
+
await sleep(200);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
];
|
|
762
|
+
// Add Smart Trash step if enabled
|
|
763
|
+
if (config.enableSmartTrash) {
|
|
764
|
+
installationSteps.push({
|
|
765
|
+
name: 'Configuring Smart Trash alias',
|
|
766
|
+
action: async () => {
|
|
767
|
+
await sleep(200);
|
|
768
|
+
await configureSmartTrashAlias();
|
|
769
|
+
await sleep(100);
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
// Execute all steps with enhanced progress display
|
|
774
|
+
for (let i = 0; i < installationSteps.length; i++) {
|
|
775
|
+
const step = installationSteps[i];
|
|
776
|
+
const progress = `[${i + 1}/${installationSteps.length}]`;
|
|
777
|
+
const loader = createLoadingAnimation(`${chalk.gray(progress)} ${step.name}...`);
|
|
778
|
+
loader.start();
|
|
779
|
+
try {
|
|
780
|
+
await step.action();
|
|
781
|
+
loader.stop(`${chalk.gray(progress)} ${step.name} ${vibrantRainbow('completed! ✨')}`);
|
|
782
|
+
}
|
|
783
|
+
catch (error) {
|
|
784
|
+
loader.stop(`${chalk.gray(progress)} ${step.name} ${chalk.red('failed')}`, false);
|
|
785
|
+
console.log(boxen(chalk.red(`❌ Installation failed at: ${step.name}\n\nError: ${error}`), {
|
|
786
|
+
padding: 1,
|
|
787
|
+
margin: 1,
|
|
788
|
+
borderStyle: 'double',
|
|
789
|
+
borderColor: 'red'
|
|
790
|
+
}));
|
|
791
|
+
process.exit(1);
|
|
792
|
+
}
|
|
793
|
+
await sleep(150); // Smooth progression
|
|
794
|
+
}
|
|
795
|
+
// Verification with style
|
|
796
|
+
console.log(chalk.gray('\n🔍 Verifying Installation\n'));
|
|
797
|
+
await verifyInstallation();
|
|
798
|
+
// Beautiful success message
|
|
799
|
+
const successTitle = fastRainbow('🎉 INSTALLATION COMPLETE! 🎉');
|
|
800
|
+
const successMessage = `
|
|
801
|
+
${chalk.bold('How your new memory system works:')}
|
|
802
|
+
|
|
803
|
+
${chalk.green('•')} When you start Claude Code, claude-mem loads your latest memories automatically
|
|
804
|
+
${chalk.green('•')} Save your work by typing ${chalk.cyan('/compact')} or ${chalk.cyan('/clear')} (takes ~30s to process)
|
|
805
|
+
${chalk.green('•')} Ask Claude to search your memories anytime with natural language
|
|
806
|
+
${chalk.green('•')} Instructions added to ${chalk.cyan('~/.claude/CLAUDE.md')} teach Claude how to use the system
|
|
807
|
+
|
|
808
|
+
${chalk.bold('Quick Start:')}
|
|
809
|
+
${chalk.yellow('1.')} Restart Claude Code to activate your memory system
|
|
810
|
+
${chalk.yellow('2.')} Start using Claude normally - memories save automatically
|
|
811
|
+
${chalk.yellow('3.')} Search memories by asking: ${chalk.italic('"Search my memories for X"')}`;
|
|
812
|
+
// Check Claude path detection result and inform user
|
|
813
|
+
const finalClaudePath = detectClaudePath();
|
|
814
|
+
const finalClaudePathNote = finalClaudePath
|
|
815
|
+
? `\n\n${chalk.green('✓')} Claude binary detected at: ${chalk.cyan(finalClaudePath)}`
|
|
816
|
+
: `\n\n${chalk.yellow('⚠️')} Claude binary not found in PATH. Features will still work with manual configuration.`;
|
|
817
|
+
const finalSmartTrashNote = config.enableSmartTrash ?
|
|
818
|
+
`\n\n${chalk.blue('🗑️ Smart Trash Enabled:')}
|
|
819
|
+
${chalk.gray(' • rm commands now move files to ~/.claude-mem/trash')}
|
|
820
|
+
${chalk.gray(' • View trash:')} ${chalk.cyan('claude-mem trash view')}
|
|
821
|
+
${chalk.gray(' • Restore files:')} ${chalk.cyan('claude-mem restore')}
|
|
822
|
+
${chalk.gray(' • Empty trash:')} ${chalk.cyan('claude-mem trash empty')}
|
|
823
|
+
${chalk.yellow(' • Restart terminal for alias to activate')}` : '';
|
|
824
|
+
const finalClearHookNote = config.saveMemoriesOnClear ?
|
|
825
|
+
`\n\n${chalk.magenta('💾 Save-on-clear enabled:')}
|
|
826
|
+
${chalk.gray(' • /clear now saves memories automatically (takes ~1 minute)')}` : '';
|
|
827
|
+
console.log(boxen(successTitle + successMessage + finalClaudePathNote + finalSmartTrashNote + finalClearHookNote, {
|
|
828
|
+
padding: 2,
|
|
829
|
+
margin: 1,
|
|
830
|
+
borderStyle: 'double',
|
|
831
|
+
borderColor: 'green',
|
|
832
|
+
backgroundColor: '#001122'
|
|
833
|
+
}));
|
|
834
|
+
// Final flourish
|
|
835
|
+
console.log(fastRainbow('\n✨ Welcome to the future of persistent AI conversations! ✨\n'));
|
|
836
|
+
}
|