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.
Files changed (96) hide show
  1. package/README.md +183 -46
  2. package/dist/bin/cli.d.ts +2 -0
  3. package/dist/bin/cli.js +179 -0
  4. package/dist/commands/compress.d.ts +2 -0
  5. package/dist/commands/compress.js +27 -0
  6. package/dist/commands/hooks.d.ts +19 -0
  7. package/dist/commands/hooks.js +131 -0
  8. package/dist/commands/install.d.ts +2 -0
  9. package/dist/commands/install.js +836 -0
  10. package/dist/commands/load-context.d.ts +2 -0
  11. package/dist/commands/load-context.js +151 -0
  12. package/dist/commands/logs.d.ts +2 -0
  13. package/dist/commands/logs.js +76 -0
  14. package/dist/commands/migrate-to-jsonl.d.ts +5 -0
  15. package/dist/commands/migrate-to-jsonl.js +99 -0
  16. package/dist/commands/restore.d.ts +1 -0
  17. package/dist/commands/restore.js +20 -0
  18. package/dist/commands/status.d.ts +1 -0
  19. package/dist/commands/status.js +136 -0
  20. package/dist/commands/trash-empty.d.ts +3 -0
  21. package/dist/commands/trash-empty.js +56 -0
  22. package/dist/commands/trash-view.d.ts +1 -0
  23. package/dist/commands/trash-view.js +101 -0
  24. package/dist/commands/trash.d.ts +6 -0
  25. package/dist/commands/trash.js +49 -0
  26. package/dist/commands/uninstall.d.ts +2 -0
  27. package/dist/commands/uninstall.js +107 -0
  28. package/dist/constants.d.ts +271 -0
  29. package/dist/constants.js +199 -0
  30. package/dist/core/compression/TranscriptCompressor.d.ts +79 -0
  31. package/dist/core/compression/TranscriptCompressor.js +585 -0
  32. package/dist/core/orchestration/PromptOrchestrator.d.ts +165 -0
  33. package/dist/core/orchestration/PromptOrchestrator.js +182 -0
  34. package/dist/lib/time-utils.d.ts +5 -0
  35. package/dist/lib/time-utils.js +70 -0
  36. package/dist/prompts/constants.d.ts +126 -0
  37. package/dist/prompts/constants.js +161 -0
  38. package/dist/prompts/index.d.ts +10 -0
  39. package/dist/prompts/index.js +11 -0
  40. package/dist/prompts/templates/analysis/AnalysisTemplates.d.ts +13 -0
  41. package/dist/prompts/templates/analysis/AnalysisTemplates.js +94 -0
  42. package/dist/prompts/templates/context/ContextTemplates.d.ts +119 -0
  43. package/dist/prompts/templates/context/ContextTemplates.js +399 -0
  44. package/dist/prompts/templates/hooks/HookTemplates.d.ts +175 -0
  45. package/dist/prompts/templates/hooks/HookTemplates.js +394 -0
  46. package/dist/prompts/templates/hooks/HookTemplates.test.d.ts +7 -0
  47. package/dist/prompts/templates/hooks/HookTemplates.test.js +127 -0
  48. package/dist/shared/config.d.ts +4 -0
  49. package/dist/shared/config.js +41 -0
  50. package/dist/shared/error-handler.d.ts +22 -0
  51. package/dist/shared/error-handler.js +142 -0
  52. package/dist/shared/logger.d.ts +19 -0
  53. package/dist/shared/logger.js +51 -0
  54. package/dist/shared/paths.d.ts +28 -0
  55. package/dist/shared/paths.js +100 -0
  56. package/dist/shared/settings.d.ts +41 -0
  57. package/dist/shared/settings.js +81 -0
  58. package/dist/shared/types.d.ts +145 -0
  59. package/dist/shared/types.js +78 -0
  60. package/docs/STATUS.md +155 -0
  61. package/docs/chroma-backend-migration.md +161 -0
  62. package/docs/landing-page-outline.md +287 -0
  63. package/docs/multi-platform-builds.md +96 -0
  64. package/docs/plans/cloud-service-plan.md +274 -0
  65. package/docs/plans/fix-response-format-issue.md +61 -0
  66. package/docs/plans/restructure-session-hook-output.md +102 -0
  67. package/docs/plans/session-start-hook-investigation.md +45 -0
  68. package/docs/plans/src-reorganization-plan.md +181 -0
  69. package/docs/plans/terminal-effects-decision.md +22 -0
  70. package/docs/plans/terminal-effects-integration.md +82 -0
  71. package/docs/plans/trash-bin-feature-plan.md +240 -0
  72. package/docs/plans/trash-bin-minimal-plan.md +102 -0
  73. package/docs/reference/bun-single-executable.md +584 -0
  74. package/docs/reference/cc-output-styles.md +99 -0
  75. package/docs/reference/chroma-mcp-project-memory-example.md +80 -0
  76. package/docs/reference/chroma-mcp-team-example.md +92 -0
  77. package/docs/reference/claude-code/cc-hooks.md +787 -0
  78. package/docs/reference/claude-code/cc-status-line.md +202 -0
  79. package/docs/reference/claude-code/hook-configuration.md +173 -0
  80. package/docs/reference/claude-code/hook-responses.md +127 -0
  81. package/docs/reference/claude-code/hooks.md +175 -0
  82. package/docs/reference/claude-code/mcp-configuration.md +133 -0
  83. package/docs/reference/claude-code/session-start-hook.md +82 -0
  84. package/docs/reference/load-context-format-example.md +33 -0
  85. package/docs/reference/mcp-sdk/mcp-typescript-sdk-readme.md +1323 -0
  86. package/docs/reference/mcp-sdk/server-implementation.md +286 -0
  87. package/docs/reference/mcp-sdk/stdio-transport.md +345 -0
  88. package/docs/todos/fix-response-format-tasks.md +43 -0
  89. package/docs/todos/implementation-todos.md +280 -0
  90. package/docs/todos/restructure-hook-output-tasks.md +103 -0
  91. package/docs/todos/session-start-hook-fix.md +26 -0
  92. package/docs/todos/terminal-effects-tasks.md +42 -0
  93. package/docs/todos/trash-bin-implementation-todos.md +348 -0
  94. package/docs/todos/trash-bin-minimal-todos.md +27 -0
  95. package/package.json +56 -6
  96. 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
+ }