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,151 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { PathResolver } from '../shared/paths.js';
|
|
5
|
+
import { createCompletionMessage, createContextualError, createUserFriendlyError, formatTimeAgo, outputSessionStartContent } from '../prompts/templates/context/ContextTemplates.js';
|
|
6
|
+
function formatSize(bytes) {
|
|
7
|
+
if (bytes === 0)
|
|
8
|
+
return '0 B';
|
|
9
|
+
const k = 1024;
|
|
10
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
11
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
12
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
13
|
+
}
|
|
14
|
+
function getTrashStatus() {
|
|
15
|
+
const trashDir = join(homedir(), '.claude-mem', 'trash');
|
|
16
|
+
if (!fs.existsSync(trashDir)) {
|
|
17
|
+
return { folderCount: 0, fileCount: 0, totalSize: 0, isEmpty: true };
|
|
18
|
+
}
|
|
19
|
+
const items = fs.readdirSync(trashDir);
|
|
20
|
+
if (items.length === 0) {
|
|
21
|
+
return { folderCount: 0, fileCount: 0, totalSize: 0, isEmpty: true };
|
|
22
|
+
}
|
|
23
|
+
let folderCount = 0;
|
|
24
|
+
let fileCount = 0;
|
|
25
|
+
let totalSize = 0;
|
|
26
|
+
for (const item of items) {
|
|
27
|
+
const itemPath = join(trashDir, item);
|
|
28
|
+
const stats = fs.statSync(itemPath);
|
|
29
|
+
if (stats.isDirectory()) {
|
|
30
|
+
folderCount++;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
fileCount++;
|
|
34
|
+
}
|
|
35
|
+
totalSize += stats.size;
|
|
36
|
+
}
|
|
37
|
+
return { folderCount, fileCount, totalSize, isEmpty: false };
|
|
38
|
+
}
|
|
39
|
+
export async function loadContext(options = {}) {
|
|
40
|
+
const pathResolver = new PathResolver();
|
|
41
|
+
const indexPath = pathResolver.getIndexPath();
|
|
42
|
+
try {
|
|
43
|
+
// Check if index file exists
|
|
44
|
+
if (!fs.existsSync(indexPath)) {
|
|
45
|
+
if (options.format === 'session-start') {
|
|
46
|
+
console.log(createContextualError('NO_MEMORIES', options.project || 'this project'));
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const content = fs.readFileSync(indexPath, 'utf-8');
|
|
51
|
+
const lines = content.trim().split('\n').filter(line => line.trim());
|
|
52
|
+
if (lines.length === 0) {
|
|
53
|
+
if (options.format === 'session-start') {
|
|
54
|
+
console.log(createContextualError('NO_MEMORIES', options.project || 'this project'));
|
|
55
|
+
}
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
// Parse JSONL format - each line is a JSON object
|
|
59
|
+
const jsonObjects = [];
|
|
60
|
+
for (const line of lines) {
|
|
61
|
+
try {
|
|
62
|
+
// Skip lines that don't look like JSON (could be legacy format)
|
|
63
|
+
if (!line.trim().startsWith('{')) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const obj = JSON.parse(line);
|
|
67
|
+
jsonObjects.push(obj);
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
// Skip malformed JSON lines
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (jsonObjects.length === 0) {
|
|
75
|
+
if (options.format === 'session-start') {
|
|
76
|
+
console.log(createContextualError('NO_MEMORIES', options.project || 'this project'));
|
|
77
|
+
}
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// Filter by project if specified
|
|
81
|
+
let filteredObjects = jsonObjects;
|
|
82
|
+
if (options.project) {
|
|
83
|
+
// Sanitize project name to match how it's stored (replace hyphens with underscores)
|
|
84
|
+
const sanitizedProject = options.project.replace(/-/g, '_');
|
|
85
|
+
// Filter objects by project field
|
|
86
|
+
filteredObjects = jsonObjects.filter(obj => obj.project === sanitizedProject || obj.project === options.project);
|
|
87
|
+
}
|
|
88
|
+
// Take more entries for session-start to get multiple overviews
|
|
89
|
+
const count = options.format === 'session-start' ? 30 : 10;
|
|
90
|
+
const recentObjects = filteredObjects.slice(-count);
|
|
91
|
+
if (options.format === 'session-start') {
|
|
92
|
+
// Find most recent timestamp for last session info
|
|
93
|
+
let lastSessionTime = 'recently';
|
|
94
|
+
const timestamps = recentObjects
|
|
95
|
+
.map(obj => {
|
|
96
|
+
// Get timestamp from JSON object
|
|
97
|
+
return obj.timestamp ? new Date(obj.timestamp) : null;
|
|
98
|
+
})
|
|
99
|
+
.filter(date => date !== null)
|
|
100
|
+
.sort((a, b) => b.getTime() - a.getTime());
|
|
101
|
+
if (timestamps.length > 0) {
|
|
102
|
+
lastSessionTime = formatTimeAgo(timestamps[0]);
|
|
103
|
+
}
|
|
104
|
+
// Use dual-stream output for session start formatting
|
|
105
|
+
outputSessionStartContent({
|
|
106
|
+
projectName: options.project || 'your project',
|
|
107
|
+
memoryCount: recentObjects.length,
|
|
108
|
+
lastSessionTime,
|
|
109
|
+
recentObjects
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
else if (options.format === 'json') {
|
|
113
|
+
console.log(JSON.stringify(recentObjects));
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
// Default format - show completion message and entries
|
|
117
|
+
console.log(createCompletionMessage('Context loading', recentObjects.length, 'recent memories found'));
|
|
118
|
+
recentObjects.forEach((obj) => {
|
|
119
|
+
if (obj.type === 'memory') {
|
|
120
|
+
console.log(`${obj.text} | ${obj.document_id} | ${obj.keywords}`);
|
|
121
|
+
}
|
|
122
|
+
else if (obj.type === 'overview') {
|
|
123
|
+
console.log(`**Overview:** ${obj.content}`);
|
|
124
|
+
}
|
|
125
|
+
else if (obj.type === 'session') {
|
|
126
|
+
console.log(`# Session: ${obj.session_id} [${obj.timestamp}]`);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
console.log(JSON.stringify(obj));
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
// Display trash status if not empty (except for JSON format to avoid breaking JSON parsing)
|
|
134
|
+
if (options.format !== 'json') {
|
|
135
|
+
const trashStatus = getTrashStatus();
|
|
136
|
+
if (!trashStatus.isEmpty) {
|
|
137
|
+
const formattedSize = formatSize(trashStatus.totalSize);
|
|
138
|
+
console.log(`šļø Trash ā ${trashStatus.folderCount} folders | ${trashStatus.fileCount} files | ${formattedSize} ā use \`$ claude-mem restore\` for more options.`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
144
|
+
if (options.format === 'session-start') {
|
|
145
|
+
console.log(createContextualError('CONNECTION_FAILED', errorMessage));
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
console.log(createUserFriendlyError('Context loading', errorMessage, 'Check file permissions and try again'));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, statSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
// <Block> 1.1 ====================================
|
|
5
|
+
async function showLog(logPath, logType, tail) {
|
|
6
|
+
// <Block> 1.2 ====================================
|
|
7
|
+
try {
|
|
8
|
+
const content = readFileSync(logPath, 'utf8');
|
|
9
|
+
const lines = content.split('\n').filter(line => line.trim());
|
|
10
|
+
const displayLines = lines.slice(-tail);
|
|
11
|
+
console.log(`š ${logType} Logs (last ${tail} lines):`);
|
|
12
|
+
console.log(` File: ${logPath}`);
|
|
13
|
+
console.log('');
|
|
14
|
+
// <Block> 1.3 ====================================
|
|
15
|
+
if (displayLines.length === 0) {
|
|
16
|
+
console.log(' No log entries found');
|
|
17
|
+
// </Block> =======================================
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
displayLines.forEach(line => {
|
|
21
|
+
console.log(` ${line}`);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
// </Block> =======================================
|
|
25
|
+
console.log('');
|
|
26
|
+
// </Block> =======================================
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
// <Block> 1.4 ====================================
|
|
30
|
+
console.log(`ā Could not read ${logType.toLowerCase()} log: ${logPath}`);
|
|
31
|
+
// </Block> =======================================
|
|
32
|
+
}
|
|
33
|
+
// </Block> =======================================
|
|
34
|
+
}
|
|
35
|
+
// <Block> 2.1 ====================================
|
|
36
|
+
export async function logs(options = {}) {
|
|
37
|
+
// <Block> 2.2 ====================================
|
|
38
|
+
const logsDir = join(homedir(), '.claude-mem', 'logs');
|
|
39
|
+
const tail = parseInt(options.tail) || 20;
|
|
40
|
+
// </Block> =======================================
|
|
41
|
+
// Find most recent log file
|
|
42
|
+
try {
|
|
43
|
+
const files = readdirSync(logsDir);
|
|
44
|
+
const logFiles = files
|
|
45
|
+
.filter(f => f.startsWith('claude-mem-') && f.endsWith('.log'))
|
|
46
|
+
.map(f => ({
|
|
47
|
+
name: f,
|
|
48
|
+
path: join(logsDir, f),
|
|
49
|
+
mtime: statSync(join(logsDir, f)).mtime
|
|
50
|
+
}))
|
|
51
|
+
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
52
|
+
if (logFiles.length === 0) {
|
|
53
|
+
console.log('ā No log files found in ~/.claude-mem/logs/');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
// Show most recent log
|
|
57
|
+
await showLog(logFiles[0].path, 'Most Recent', tail);
|
|
58
|
+
if (options.all && logFiles.length > 1) {
|
|
59
|
+
console.log(`š Found ${logFiles.length} total log files`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
console.log('ā Could not read logs directory: ~/.claude-mem/logs/');
|
|
64
|
+
console.log(' Run a compression first to generate logs');
|
|
65
|
+
}
|
|
66
|
+
// <Block> 2.5 ====================================
|
|
67
|
+
if (options.follow) {
|
|
68
|
+
console.log('Following logs... (Press Ctrl+C to stop)');
|
|
69
|
+
// Basic follow implementation - would need more sophisticated watching in real usage
|
|
70
|
+
setInterval(() => {
|
|
71
|
+
// This would need proper file watching implementation
|
|
72
|
+
}, 1000);
|
|
73
|
+
}
|
|
74
|
+
// </Block> =======================================
|
|
75
|
+
// </Block> =======================================
|
|
76
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* One-time migration script to convert claude-mem-index.md to claude-mem-index.jsonl
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { PathResolver } from '../shared/paths.js';
|
|
8
|
+
export function migrateToJSONL() {
|
|
9
|
+
const pathResolver = new PathResolver();
|
|
10
|
+
const oldIndexPath = path.join(pathResolver.getConfigDir(), 'claude-mem-index.md');
|
|
11
|
+
const newIndexPath = path.join(pathResolver.getConfigDir(), 'claude-mem-index.jsonl');
|
|
12
|
+
// Check if old index exists
|
|
13
|
+
if (!fs.existsSync(oldIndexPath)) {
|
|
14
|
+
console.log('No markdown index found to migrate');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
// Check if new index already exists
|
|
18
|
+
if (fs.existsSync(newIndexPath)) {
|
|
19
|
+
console.log('JSONL index already exists, skipping migration');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
console.log('Starting migration from MD to JSONL...');
|
|
23
|
+
const content = fs.readFileSync(oldIndexPath, 'utf-8');
|
|
24
|
+
const lines = content.split('\n').filter(line => line.trim());
|
|
25
|
+
const jsonlLines = [];
|
|
26
|
+
let currentSessionId = '';
|
|
27
|
+
let currentSessionTimestamp = '';
|
|
28
|
+
for (const line of lines) {
|
|
29
|
+
// Parse session headers: # Session: <id> [<timestamp>]
|
|
30
|
+
const sessionMatch = line.match(/^# Session:\s*([^\[]+)(?:\s*\[([^\]]+)\])?/);
|
|
31
|
+
if (sessionMatch) {
|
|
32
|
+
currentSessionId = sessionMatch[1].trim();
|
|
33
|
+
currentSessionTimestamp = sessionMatch[2]?.trim() || new Date().toISOString();
|
|
34
|
+
// Extract project from session ID (assuming format like <project>_<uuid>)
|
|
35
|
+
const projectMatch = currentSessionId.match(/^([^_]+)_/);
|
|
36
|
+
const project = projectMatch ? projectMatch[1] : 'unknown';
|
|
37
|
+
jsonlLines.push(JSON.stringify({
|
|
38
|
+
type: 'session',
|
|
39
|
+
session_id: currentSessionId,
|
|
40
|
+
timestamp: currentSessionTimestamp,
|
|
41
|
+
project
|
|
42
|
+
}));
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
// Parse overviews: **Overview:** <text>
|
|
46
|
+
const overviewMatch = line.match(/^\*\*Overview:\*\*\s*(.+)/);
|
|
47
|
+
if (overviewMatch) {
|
|
48
|
+
const overviewText = overviewMatch[1].trim();
|
|
49
|
+
// Extract project from current session ID
|
|
50
|
+
const projectMatch = currentSessionId.match(/^([^_]+)_/);
|
|
51
|
+
const project = projectMatch ? projectMatch[1] : 'unknown';
|
|
52
|
+
jsonlLines.push(JSON.stringify({
|
|
53
|
+
type: 'overview',
|
|
54
|
+
content: overviewText,
|
|
55
|
+
session_id: currentSessionId,
|
|
56
|
+
project,
|
|
57
|
+
timestamp: currentSessionTimestamp
|
|
58
|
+
}));
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
// Skip certain lines
|
|
62
|
+
if (line.startsWith('# NO SUMMARIES EXTRACTED')) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
// Parse memory entries (pipe-separated)
|
|
66
|
+
if (line.includes(' | ')) {
|
|
67
|
+
const parts = line.split(' | ').map(p => p.trim());
|
|
68
|
+
if (parts.length >= 3) {
|
|
69
|
+
const [text, document_id, keywords, timestamp, archive] = parts;
|
|
70
|
+
// Extract project from document_id (format: <project>_<session>_<number>)
|
|
71
|
+
const projectMatch = document_id?.match(/^([^_]+)_/);
|
|
72
|
+
const project = projectMatch ? projectMatch[1] : 'unknown';
|
|
73
|
+
jsonlLines.push(JSON.stringify({
|
|
74
|
+
type: 'memory',
|
|
75
|
+
text,
|
|
76
|
+
document_id: document_id || `${currentSessionId}_${Date.now()}`,
|
|
77
|
+
keywords: keywords || '',
|
|
78
|
+
session_id: currentSessionId,
|
|
79
|
+
project,
|
|
80
|
+
timestamp: timestamp || currentSessionTimestamp,
|
|
81
|
+
archive: archive || `${currentSessionId}.jsonl.archive`
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Write JSONL file
|
|
87
|
+
fs.writeFileSync(newIndexPath, jsonlLines.join('\n') + '\n');
|
|
88
|
+
// Backup old index
|
|
89
|
+
const backupPath = oldIndexPath + '.backup';
|
|
90
|
+
fs.renameSync(oldIndexPath, backupPath);
|
|
91
|
+
console.log(`ā
Migration complete!`);
|
|
92
|
+
console.log(` - Converted ${jsonlLines.length} entries`);
|
|
93
|
+
console.log(` - New index: ${newIndexPath}`);
|
|
94
|
+
console.log(` - Backup: ${backupPath}`);
|
|
95
|
+
}
|
|
96
|
+
// Run if called directly
|
|
97
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
98
|
+
migrateToJSONL();
|
|
99
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function restore(): Promise<void>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { readdirSync, renameSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import * as p from '@clack/prompts';
|
|
5
|
+
export async function restore() {
|
|
6
|
+
const trashDir = join(homedir(), '.claude-mem', 'trash');
|
|
7
|
+
const files = readdirSync(trashDir);
|
|
8
|
+
if (files.length === 0) {
|
|
9
|
+
console.log('Trash is empty');
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const file = await p.select({
|
|
13
|
+
message: 'Select file to restore:',
|
|
14
|
+
options: files.map(f => ({ value: f, label: f }))
|
|
15
|
+
});
|
|
16
|
+
if (p.isCancel(file))
|
|
17
|
+
return;
|
|
18
|
+
renameSync(join(trashDir, file), join(process.cwd(), file));
|
|
19
|
+
console.log(`Restored ${file}`);
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function status(): Promise<void>;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { readFileSync, existsSync, readdirSync, statSync } 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
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
export async function status() {
|
|
8
|
+
console.log('š Claude Memory System Status Check');
|
|
9
|
+
console.log('=====================================\n');
|
|
10
|
+
console.log('š Installed Hook Scripts:');
|
|
11
|
+
const claudeMemHooksDir = join(homedir(), '.claude-mem', 'hooks');
|
|
12
|
+
const preCompactScript = join(claudeMemHooksDir, 'pre-compact.js');
|
|
13
|
+
const sessionStartScript = join(claudeMemHooksDir, 'session-start.js');
|
|
14
|
+
const sessionEndScript = join(claudeMemHooksDir, 'session-end.js');
|
|
15
|
+
const checkScript = (path, name) => {
|
|
16
|
+
if (existsSync(path)) {
|
|
17
|
+
console.log(` ā
${name}: Found at ${path}`);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
console.log(` ā ${name}: Not found at ${path}`);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
checkScript(preCompactScript, 'pre-compact.js');
|
|
24
|
+
checkScript(sessionStartScript, 'session-start.js');
|
|
25
|
+
checkScript(sessionEndScript, 'session-end.js');
|
|
26
|
+
console.log('');
|
|
27
|
+
console.log('āļø Settings Configuration:');
|
|
28
|
+
const checkSettings = (name, path) => {
|
|
29
|
+
if (!existsSync(path)) {
|
|
30
|
+
console.log(` āļø ${name}: No settings file`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
console.log(` š ${name}: ${path}`);
|
|
34
|
+
try {
|
|
35
|
+
const settings = JSON.parse(readFileSync(path, 'utf8'));
|
|
36
|
+
const hasPreCompact = settings.hooks?.PreCompact?.some((matcher) => matcher.hooks?.some((hook) => hook.command?.includes('pre-compact.js') || hook.command?.includes('claude-mem')));
|
|
37
|
+
const hasSessionStart = settings.hooks?.SessionStart?.some((matcher) => matcher.hooks?.some((hook) => hook.command?.includes('session-start.js') || hook.command?.includes('claude-mem')));
|
|
38
|
+
const hasSessionEnd = settings.hooks?.SessionEnd?.some((matcher) => matcher.hooks?.some((hook) => hook.command?.includes('session-end.js') || hook.command?.includes('claude-mem')));
|
|
39
|
+
console.log(` PreCompact: ${hasPreCompact ? 'ā
' : 'ā'}`);
|
|
40
|
+
console.log(` SessionStart: ${hasSessionStart ? 'ā
' : 'ā'}`);
|
|
41
|
+
console.log(` SessionEnd: ${hasSessionEnd ? 'ā
' : 'ā'}`);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
console.log(` ā ļø Could not parse settings`);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
checkSettings('Global', join(homedir(), '.claude', 'settings.json'));
|
|
48
|
+
checkSettings('Project', join(process.cwd(), '.claude', 'settings.json'));
|
|
49
|
+
console.log('');
|
|
50
|
+
console.log('š¦ Compressed Transcripts:');
|
|
51
|
+
const claudeProjectsDir = join(homedir(), '.claude', 'projects');
|
|
52
|
+
if (existsSync(claudeProjectsDir)) {
|
|
53
|
+
try {
|
|
54
|
+
let compressedCount = 0;
|
|
55
|
+
let archiveCount = 0;
|
|
56
|
+
const searchDir = (dir, depth = 0) => {
|
|
57
|
+
if (depth > 3)
|
|
58
|
+
return;
|
|
59
|
+
const files = readdirSync(dir);
|
|
60
|
+
for (const file of files) {
|
|
61
|
+
const fullPath = join(dir, file);
|
|
62
|
+
const stats = statSync(fullPath);
|
|
63
|
+
if (stats.isDirectory() && !file.startsWith('.')) {
|
|
64
|
+
searchDir(fullPath, depth + 1);
|
|
65
|
+
}
|
|
66
|
+
else if (file.endsWith('.jsonl.compressed')) {
|
|
67
|
+
compressedCount++;
|
|
68
|
+
}
|
|
69
|
+
else if (file.endsWith('.jsonl.archive')) {
|
|
70
|
+
archiveCount++;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
searchDir(claudeProjectsDir);
|
|
75
|
+
console.log(` Compressed files: ${compressedCount}`);
|
|
76
|
+
console.log(` Archive files: ${archiveCount}`);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
console.log(` ā ļø Could not scan projects directory`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
console.log(` ā¹ļø No Claude projects directory found`);
|
|
84
|
+
}
|
|
85
|
+
console.log('');
|
|
86
|
+
console.log('š§ Runtime Environment:');
|
|
87
|
+
const checkCommand = (cmd, name) => {
|
|
88
|
+
try {
|
|
89
|
+
const version = execSync(`${cmd} --version`, { encoding: 'utf8' }).trim();
|
|
90
|
+
console.log(` ā
${name}: ${version}`);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
console.log(` ā ${name}: Not found`);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
checkCommand('node', 'Node.js');
|
|
97
|
+
checkCommand('bun', 'Bun');
|
|
98
|
+
console.log('');
|
|
99
|
+
console.log('š§ Chroma Storage Status:');
|
|
100
|
+
console.log(' ā
Storage backend: Chroma MCP');
|
|
101
|
+
console.log(' š Data location: ~/.claude-mem/chroma');
|
|
102
|
+
console.log(' š Features: Vector search, semantic similarity, document storage');
|
|
103
|
+
console.log('');
|
|
104
|
+
console.log('š Summary:');
|
|
105
|
+
const globalPath = join(homedir(), '.claude', 'settings.json');
|
|
106
|
+
const projectPath = join(process.cwd(), '.claude', 'settings.json');
|
|
107
|
+
let isInstalled = false;
|
|
108
|
+
let installLocation = 'Not installed';
|
|
109
|
+
try {
|
|
110
|
+
if (existsSync(globalPath)) {
|
|
111
|
+
const settings = JSON.parse(readFileSync(globalPath, 'utf8'));
|
|
112
|
+
if (settings.hooks?.PreCompact || settings.hooks?.SessionStart || settings.hooks?.SessionEnd) {
|
|
113
|
+
isInstalled = true;
|
|
114
|
+
installLocation = 'Global';
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (existsSync(projectPath)) {
|
|
118
|
+
const settings = JSON.parse(readFileSync(projectPath, 'utf8'));
|
|
119
|
+
if (settings.hooks?.PreCompact || settings.hooks?.SessionStart || settings.hooks?.SessionEnd) {
|
|
120
|
+
isInstalled = true;
|
|
121
|
+
installLocation = installLocation === 'Global' ? 'Global + Project' : 'Project';
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch { }
|
|
126
|
+
if (isInstalled) {
|
|
127
|
+
console.log(` ā
Claude Memory System is installed (${installLocation})`);
|
|
128
|
+
console.log('');
|
|
129
|
+
console.log('š” To test: Use /compact in Claude Code');
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
console.log(` ā Claude Memory System is not installed`);
|
|
133
|
+
console.log('');
|
|
134
|
+
console.log('š” To install: claude-mem install');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { rmSync, readdirSync, existsSync, statSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import * as p from '@clack/prompts';
|
|
5
|
+
export async function emptyTrash(options = {}) {
|
|
6
|
+
const trashDir = join(homedir(), '.claude-mem', 'trash');
|
|
7
|
+
// Check if trash directory exists
|
|
8
|
+
if (!existsSync(trashDir)) {
|
|
9
|
+
p.log.info('šļø Trash is already empty');
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
const files = readdirSync(trashDir);
|
|
14
|
+
if (files.length === 0) {
|
|
15
|
+
p.log.info('šļø Trash is already empty');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
// Count items
|
|
19
|
+
let folderCount = 0;
|
|
20
|
+
let fileCount = 0;
|
|
21
|
+
for (const file of files) {
|
|
22
|
+
const filePath = join(trashDir, file);
|
|
23
|
+
const stats = statSync(filePath);
|
|
24
|
+
if (stats.isDirectory()) {
|
|
25
|
+
folderCount++;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
fileCount++;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Confirm deletion unless --force flag is used
|
|
32
|
+
if (!options.force) {
|
|
33
|
+
const confirm = await p.confirm({
|
|
34
|
+
message: `Permanently delete ${folderCount} folders and ${fileCount} files from trash?`,
|
|
35
|
+
initialValue: false
|
|
36
|
+
});
|
|
37
|
+
if (p.isCancel(confirm) || !confirm) {
|
|
38
|
+
p.log.info('Cancelled - trash not emptied');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Delete all files in trash
|
|
43
|
+
const s = p.spinner();
|
|
44
|
+
s.start('Emptying trash...');
|
|
45
|
+
for (const file of files) {
|
|
46
|
+
const filePath = join(trashDir, file);
|
|
47
|
+
rmSync(filePath, { recursive: true, force: true });
|
|
48
|
+
}
|
|
49
|
+
s.stop(`šļø Trash emptied - permanently deleted ${folderCount} folders and ${fileCount} files`);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
p.log.error('Failed to empty trash');
|
|
53
|
+
console.error(error);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function viewTrash(): Promise<void>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { readdirSync, statSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import * as p from '@clack/prompts';
|
|
5
|
+
function parseTrashName(filename) {
|
|
6
|
+
const lastDotIndex = filename.lastIndexOf('.');
|
|
7
|
+
if (lastDotIndex === -1)
|
|
8
|
+
return { name: filename, timestamp: 0 };
|
|
9
|
+
const timestamp = parseInt(filename.substring(lastDotIndex + 1));
|
|
10
|
+
if (isNaN(timestamp))
|
|
11
|
+
return { name: filename, timestamp: 0 };
|
|
12
|
+
return {
|
|
13
|
+
name: filename.substring(0, lastDotIndex),
|
|
14
|
+
timestamp
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function formatSize(bytes) {
|
|
18
|
+
if (bytes === 0)
|
|
19
|
+
return '0 B';
|
|
20
|
+
const k = 1024;
|
|
21
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
22
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
23
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
24
|
+
}
|
|
25
|
+
function getDirectorySize(dirPath) {
|
|
26
|
+
let size = 0;
|
|
27
|
+
const files = readdirSync(dirPath);
|
|
28
|
+
for (const file of files) {
|
|
29
|
+
const filePath = join(dirPath, file);
|
|
30
|
+
const stats = statSync(filePath);
|
|
31
|
+
if (stats.isDirectory()) {
|
|
32
|
+
size += getDirectorySize(filePath);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
size += stats.size;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return size;
|
|
39
|
+
}
|
|
40
|
+
export async function viewTrash() {
|
|
41
|
+
const trashDir = join(homedir(), '.claude-mem', 'trash');
|
|
42
|
+
try {
|
|
43
|
+
const files = readdirSync(trashDir);
|
|
44
|
+
if (files.length === 0) {
|
|
45
|
+
p.log.info('šļø Trash is empty');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const items = files.map(file => {
|
|
49
|
+
const filePath = join(trashDir, file);
|
|
50
|
+
const stats = statSync(filePath);
|
|
51
|
+
const { name, timestamp } = parseTrashName(file);
|
|
52
|
+
const size = stats.isDirectory() ? getDirectorySize(filePath) : stats.size;
|
|
53
|
+
return {
|
|
54
|
+
originalName: name,
|
|
55
|
+
trashedName: file,
|
|
56
|
+
size,
|
|
57
|
+
trashedAt: new Date(timestamp),
|
|
58
|
+
isDirectory: stats.isDirectory()
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
// Sort by date, newest first
|
|
62
|
+
items.sort((a, b) => b.trashedAt.getTime() - a.trashedAt.getTime());
|
|
63
|
+
// Display header
|
|
64
|
+
console.log('\nšļø Trash Contents\n');
|
|
65
|
+
console.log('ā'.repeat(80));
|
|
66
|
+
// Display items
|
|
67
|
+
let totalSize = 0;
|
|
68
|
+
let folderCount = 0;
|
|
69
|
+
let fileCount = 0;
|
|
70
|
+
for (const item of items) {
|
|
71
|
+
totalSize += item.size;
|
|
72
|
+
if (item.isDirectory) {
|
|
73
|
+
folderCount++;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
fileCount++;
|
|
77
|
+
}
|
|
78
|
+
const type = item.isDirectory ? 'š' : 'š';
|
|
79
|
+
const date = item.trashedAt.toLocaleString();
|
|
80
|
+
const size = formatSize(item.size);
|
|
81
|
+
console.log(`${type} ${item.originalName}`);
|
|
82
|
+
console.log(` Size: ${size} | Trashed: ${date}`);
|
|
83
|
+
console.log(` ID: ${item.trashedName}`);
|
|
84
|
+
console.log();
|
|
85
|
+
}
|
|
86
|
+
// Display summary
|
|
87
|
+
console.log('ā'.repeat(80));
|
|
88
|
+
console.log(`Total: ${folderCount} folders, ${fileCount} files (${formatSize(totalSize)})`);
|
|
89
|
+
console.log('\nTo restore files: claude-mem restore');
|
|
90
|
+
console.log('To empty trash: claude-mem trash empty');
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
if (error.code === 'ENOENT') {
|
|
94
|
+
p.log.info('šļø Trash is empty');
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
p.log.error('Failed to read trash directory');
|
|
98
|
+
console.error(error);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|