feat: remove install, logs, restore, status, trash, and uninstall commands

- Deleted the install.ts command file, removing the installation logic for the Claude Memory System.
- Removed logs.ts command file, eliminating the log viewing functionality.
- Deleted restore.ts command file, which handled restoring files from trash.
- Removed status.ts command file, which provided system status checks.
- Deleted trash-empty.ts and trash-view.ts command files, removing trash management features.
- Removed trash.ts command file, which handled moving files to trash.
- Deleted uninstall.ts command file, eliminating the uninstallation process for the memory system.
- Updated new.ts hook to enforce plugin mode for Claude Code integration.
- Cleaned up config.ts by removing unused export for CLI_NAME.
This commit is contained in:
Alex Newman
2025-10-16 19:57:54 -04:00
parent 18d5e0d3bb
commit 8e460a8c2a
16 changed files with 15 additions and 2242 deletions
-593
View File
File diff suppressed because one or more lines are too long
+3 -14
View File
@@ -4,13 +4,14 @@
"description": "Memory compression system for Claude Code - persist context across sessions",
"keywords": [
"claude",
"claude-code",
"claude-agent-sdk",
"mcp",
"plugin",
"memory",
"compression",
"knowledge-graph",
"transcript",
"cli",
"typescript",
"bun"
],
@@ -32,15 +33,10 @@
"engines": {
"bun": ">=1.0.0"
},
"bin": {
"claude-mem": "./dist/claude-mem.min.js"
},
"scripts": {
"build": "node scripts/build.js && node scripts/build-hooks.js",
"build:cli": "node scripts/build.js",
"build": "node scripts/build-hooks.js",
"build:hooks": "node scripts/build-hooks.js",
"publish:npm": "node scripts/publish.js",
"dev": "bun run src/bin/cli.ts",
"prepublishOnly": "npm run build",
"test": "bun test tests/",
"test:context": "echo '{\"session_id\":\"test-'$(date +%s)'\",\"cwd\":\"'$(pwd)'\",\"source\":\"startup\"}' | bun scripts/hooks/context-hook.js 2>/dev/null",
@@ -48,17 +44,10 @@
},
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.1.0",
"@clack/prompts": "^0.11.0",
"boxen": "^8.0.1",
"chalk": "^5.6.0",
"commander": "^14.0.0",
"glob": "^11.0.3",
"gradient-string": "^3.0.0",
"handlebars": "^4.7.8"
},
"files": [
"dist",
"commands",
"hooks",
"scripts",
".claude-plugin",
-91
View File
@@ -1,91 +0,0 @@
#!/usr/bin/env node
/**
* Build script for claude-mem
* Bundles TypeScript source into a single minified executable
*/
import { exec } from 'child_process';
import { promisify } from 'util';
import fs from 'fs';
import path from 'path';
const execAsync = promisify(exec);
async function build() {
console.log('🔨 Building claude-mem...\n');
try {
// Read version from package.json
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
const version = packageJson.version;
console.log(`📌 Version: ${version}`);
// Check if bun is installed
try {
await execAsync('bun --version');
console.log('✓ Bun detected');
} catch {
console.error('❌ Bun is not installed. Please install it from https://bun.sh');
process.exit(1);
}
// Clean dist directory
console.log('\n📦 Cleaning dist directory...');
if (fs.existsSync('dist')) {
fs.rmSync('dist', { recursive: true });
}
fs.mkdirSync('dist', { recursive: true });
console.log('✓ Cleaned dist directory');
// Build with bun
console.log('\n🔧 Bundling with Bun...');
const buildCommand = [
'bun build',
'src/bin/cli.ts',
'--target=bun',
'--outfile=dist/claude-mem.min.js',
'--minify',
'--external @anthropic-ai/claude-agent-sdk',
'--external bun:sqlite',
`--define __DEFAULT_PACKAGE_VERSION__='"${version}"'`
].join(' ');
const { stdout, stderr } = await execAsync(buildCommand);
if (stdout) console.log(stdout);
if (stderr && !stderr.includes('warn')) console.error(stderr);
console.log('✓ Bundle created');
// Add shebang to output
console.log('\n📝 Adding shebang...');
const distFile = 'dist/claude-mem.min.js';
let content = fs.readFileSync(distFile, 'utf-8');
// Remove any existing shebangs
content = content.replace(/^#!.*\n/gm, '');
// Add the bun shebang
fs.writeFileSync(distFile, `#!/usr/bin/env bun\n${content}`);
console.log('✓ Shebang added');
// Make executable
console.log('\n🔐 Setting executable permissions...');
fs.chmodSync(distFile, 0o755);
console.log('✓ Made executable');
// Check file size
const stats = fs.statSync(distFile);
const sizeInKB = (stats.size / 1024).toFixed(2);
console.log(`\n✅ Build complete! (${sizeInKB} KB)`);
console.log(` Output: ${distFile}`);
} catch (error) {
console.error('\n❌ Build failed:', error.message);
if (error.stderr) {
console.error('\nError details:', error.stderr);
}
process.exit(1);
}
}
build();
+3 -3
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bun
// @bun
import{Database as E}from"bun:sqlite";import{join as X,dirname as g,basename as C}from"path";import{homedir as F}from"os";import{existsSync as w,mkdirSync as H}from"fs";var Z=process.env.CLAUDE_MEM_DATA_DIR||X(F(),".claude-mem"),v=process.env.CLAUDE_CONFIG_DIR||X(F(),".claude"),y=X(Z,"archives"),l=X(Z,"logs"),h=X(Z,"trash"),j=X(Z,"backups"),A=X(Z,"chroma"),R=X(Z,"settings.json"),G=X(Z,"claude-mem.db"),_=X(v,"settings.json"),I=X(v,"commands"),c=X(v,"CLAUDE.md");function x(z){H(z,{recursive:!0})}class J{db;constructor(){x(Z),this.db=new E(G,{create:!0,readwrite:!0}),this.db.run("PRAGMA journal_mode = WAL"),this.db.run("PRAGMA synchronous = NORMAL"),this.db.run("PRAGMA foreign_keys = ON")}getRecentSummaries(z,Q=10){return this.db.query(`
import{Database as f}from"bun:sqlite";import{join as X,dirname as g,basename as C}from"path";import{homedir as q}from"os";import{existsSync as S,mkdirSync as U}from"fs";var Z=process.env.CLAUDE_MEM_DATA_DIR||X(q(),".claude-mem"),B=process.env.CLAUDE_CONFIG_DIR||X(q(),".claude"),l=X(Z,"archives"),y=X(Z,"logs"),h=X(Z,"trash"),j=X(Z,"backups"),A=X(Z,"chroma"),R=X(Z,"settings.json"),F=X(Z,"claude-mem.db"),_=X(B,"settings.json"),I=X(B,"commands"),c=X(B,"CLAUDE.md");function G(z){U(z,{recursive:!0})}class V{db;constructor(){G(Z),this.db=new f(F,{create:!0,readwrite:!0}),this.db.run("PRAGMA journal_mode = WAL"),this.db.run("PRAGMA synchronous = NORMAL"),this.db.run("PRAGMA foreign_keys = ON")}getRecentSummaries(z,Q=10){return this.db.query(`
SELECT
request, investigated, learned, completed, next_steps,
files_read, files_edited, notes, created_at
@@ -38,5 +38,5 @@ import{Database as E}from"bun:sqlite";import{join as X,dirname as g,basename as
UPDATE sdk_sessions
SET status = 'failed', completed_at = ?, completed_at_epoch = ?
WHERE id = ?
`).run(Q.toISOString(),W,z)}close(){this.db.close()}}import L from"path";import{spawn as O}from"child_process";function N(z){try{if(!z)console.log("No input provided - this script is designed to run as a Claude Code UserPromptSubmit hook"),console.log(`
Expected input format:`),console.log(JSON.stringify({session_id:"string",cwd:"string",prompt:"string"},null,2)),process.exit(0);let{session_id:Q,cwd:W,prompt:Y}=z,$=L.basename(W),K=new J;if(K.findActiveSDKSession(Q))K.close(),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0);let B=K.createSDKSession(Q,$,Y);K.close();let q=process.env.CLAUDE_PLUGIN_ROOT,V;if(q){let f=L.join(q,"scripts","hooks","worker.js");V=O("bun",[f,B.toString()],{detached:!0,stdio:"ignore"})}else V=O("claude-mem",["worker",B.toString()],{detached:!0,stdio:"ignore"});V.unref(),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}catch(Q){console.error(`[claude-mem new error: ${Q.message}]`),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}}var U=await Bun.stdin.text();try{let z=U.trim()?JSON.parse(U):void 0;N(z)}catch(z){console.error(`[claude-mem new-hook error: ${z.message}]`),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}
`).run(Q.toISOString(),W,z)}close(){this.db.close()}}import x from"path";import{spawn as H}from"child_process";function L(z){try{if(!z)console.log("No input provided - this script is designed to run as a Claude Code UserPromptSubmit hook"),console.log(`
Expected input format:`),console.log(JSON.stringify({session_id:"string",cwd:"string",prompt:"string"},null,2)),process.exit(0);let{session_id:Q,cwd:W,prompt:Y}=z,$=x.basename(W),K=new V;if(K.findActiveSDKSession(Q))K.close(),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0);let J=K.createSDKSession(Q,$,Y);K.close();let M=process.env.CLAUDE_PLUGIN_ROOT;if(!M)throw new Error("CLAUDE_PLUGIN_ROOT not set - claude-mem must be installed as a Claude Code plugin");let N=x.join(M,"scripts","hooks","worker.js");H("bun",[N,J.toString()],{detached:!0,stdio:"ignore"}).unref(),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}catch(Q){console.error(`[claude-mem new error: ${Q.message}]`),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}}var O=await Bun.stdin.text();try{let z=O.trim()?JSON.parse(O):void 0;L(z)}catch(z){console.error(`[claude-mem new-hook error: ${z.message}]`),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}
-248
View File
@@ -1,248 +0,0 @@
#!/usr/bin/env node
// <Block> 1.1 ====================================
// CLI Dependencies and Imports Setup
// Natural pattern: Import what you need before using it
import { Command } from 'commander';
import { PACKAGE_NAME, PACKAGE_VERSION, PACKAGE_DESCRIPTION } from '../shared/config.js';
// Import command handlers
import { install } from '../commands/install.js';
import { uninstall } from '../commands/uninstall.js';
import { logs } from '../commands/logs.js';
import { trash } from '../commands/trash.js';
import { viewTrash } from '../commands/trash-view.js';
import { emptyTrash } from '../commands/trash-empty.js';
import { restore } from '../commands/restore.js';
import { doctor } from '../commands/doctor.js';
import { status } from '../commands/status.js';
const program = new Command();
// </Block> =======================================
// <Block> 1.2 ====================================
// Program Configuration
// Natural pattern: Configure program metadata first
program
.name(PACKAGE_NAME)
.description(PACKAGE_DESCRIPTION)
.version(PACKAGE_VERSION);
// </Block> =======================================
// <Block> 1.3 ====================================
// Install Command Definition
// Natural pattern: Define command with its options and handler
// Install command
program
.command('install')
.description('Install Claude Code hooks for automatic compression')
.option('--user', 'Install for current user (default)')
.option('--project', 'Install for current project only')
.option('--local', 'Install to custom local directory')
.option('--path <path>', 'Custom installation path (with --local)')
.option('--timeout <ms>', 'Hook execution timeout in milliseconds', '180000')
.option('--skip-mcp', 'Skip Chroma MCP server installation')
.option('--force', 'Force installation even if already installed')
.action(install);
// </Block> =======================================
// <Block> 1.5 ====================================
// Uninstall Command Definition
// Natural pattern: Define command with its options and handler
// Uninstall command
program
.command('uninstall')
.description('Remove Claude Code hooks')
.option('--user', 'Remove from user settings (default)')
.option('--project', 'Remove from project settings')
.option('--all', 'Remove from both user and project settings')
.action(uninstall);
// </Block> =======================================
// <Block> 1.6 ====================================
// Logs Command Definition
// Natural pattern: Define command with its options and handler
// Logs command
program
.command('logs')
.description('View claude-mem operation logs')
.option('--debug', 'Show debug logs only')
.option('--error', 'Show error logs only')
.option('--tail [n]', 'Show last n lines', '50')
.option('--follow', 'Follow log output')
.action(logs);
// </Block> =======================================
// <Block> 1.8 ====================================
// Trash and Restore Commands Definition
// Natural pattern: Define commands for safe file operations
// Trash command with subcommands
const trashCmd = program
.command('trash')
.description('Manage trash bin for safe file deletion')
.argument('[files...]', 'Files to move to trash')
.option('-r, --recursive', 'Remove directories recursively')
.option('-R', 'Remove directories recursively (same as -r)')
.option('-f, --force', 'Suppress errors for nonexistent files')
.action(async (files: string[] | undefined, options: any) => {
// If no files provided, show help
if (!files || files.length === 0) {
trashCmd.outputHelp();
return;
}
// Map -R to recursive
if (options.R) options.recursive = true;
await trash(files, {
force: options.force,
recursive: options.recursive
});
});
// Trash view subcommand
trashCmd
.command('view')
.description('View contents of trash bin')
.action(viewTrash);
// Trash empty subcommand
trashCmd
.command('empty')
.description('Permanently delete all files in trash')
.option('-f, --force', 'Skip confirmation prompt')
.action(emptyTrash);
// Restore command
program
.command('restore')
.description('Restore files from trash interactively')
.action(restore);
// Doctor command
program
.command('doctor')
.description('Run health checks on claude-mem installation')
.option('--json', 'Output results as JSON')
.action(doctor);
// Status command
program
.command('status')
.description('Show claude-mem system status')
.action(status);
// </Block> =======================================
// <Block> 1.9 ====================================
// Hook Commands Definition
// Natural pattern: Define hook commands for Claude Code integration
// Hook commands (for Claude Code hook integration)
program
.command('context')
.description('SessionStart hook - show recent session context')
.action(async () => {
try {
const { contextHook } = await import('../hooks/index.js');
const input = await readStdin();
const data = input.trim() ? JSON.parse(input) : undefined;
contextHook(data);
} catch (error: any) {
console.error(`[claude-mem context] Error: ${error.message}`);
process.exit(0); // Exit gracefully to avoid blocking Claude Code
}
});
program
.command('new')
.description('UserPromptSubmit hook - initialize SDK session')
.action(async () => {
try {
const { newHook } = await import('../hooks/index.js');
const input = await readStdin();
const data = input.trim() ? JSON.parse(input) : undefined;
newHook(data);
} catch (error: any) {
console.error(`[claude-mem new] Error: ${error.message}`);
process.exit(0); // Exit gracefully to avoid blocking Claude Code
}
});
program
.command('save')
.description('PostToolUse hook - queue observation')
.action(async () => {
try {
const { saveHook } = await import('../hooks/index.js');
const input = await readStdin();
const data = input.trim() ? JSON.parse(input) : undefined;
saveHook(data);
} catch (error: any) {
console.error(`[claude-mem save] Error: ${error.message}`);
process.exit(0); // Exit gracefully to avoid blocking Claude Code
}
});
program
.command('summary')
.description('Stop hook - finalize session')
.action(async () => {
try {
const { summaryHook } = await import('../hooks/index.js');
const input = await readStdin();
const data = input.trim() ? JSON.parse(input) : undefined;
summaryHook(data);
} catch (error: any) {
console.error(`[claude-mem summary] Error: ${error.message}`);
process.exit(0); // Exit gracefully to avoid blocking Claude Code
}
});
program
.command('worker <sessionId>')
.description('Run SDK worker process (internal use)')
.action(async (sessionId: string) => {
try {
// Import and run the worker main function
const { main } = await import('../sdk/worker.js');
// Set process.argv so worker can parse sessionId
process.argv[2] = sessionId;
await main();
} catch (error: any) {
console.error(`[SDK Worker] Fatal error: ${error.message}`);
process.exit(1);
}
});
// Helper function to read stdin (Bun-compatible)
async function readStdin(): Promise<string> {
// Use Bun's native stdin.text() if available, otherwise use Node.js streams
if (typeof Bun !== 'undefined' && Bun.stdin) {
return await Bun.stdin.text();
}
return new Promise((resolve) => {
let data = '';
process.stdin.on('data', chunk => {
data += chunk;
});
process.stdin.on('end', () => {
resolve(data);
});
});
}
// </Block> =======================================
// <Block> 1.11 ===================================
// CLI Execution
// Natural pattern: After defining all commands, parse and execute
// Parse arguments and execute
program.parse();
// </Block> =======================================
// <Block> 1.11 ===================================
// Module Exports for Programmatic Use
// Export database and utility classes for hooks and external consumers
export { DatabaseManager, migrations, initializeDatabase, getDatabase } from '../services/sqlite/index.js';
// </Block> =======================================
-90
View File
@@ -1,90 +0,0 @@
import { OptionValues } from 'commander';
import fs from 'fs';
import path from 'path';
import * as paths from '../shared/paths.js';
import { HooksDatabase } from '../services/sqlite/index.js';
type CheckStatus = 'pass' | 'fail' | 'warn';
interface CheckResult {
name: string;
status: CheckStatus;
details?: string;
}
function printCheck(result: CheckResult): void {
const icon =
result.status === 'pass' ? '✅' : result.status === 'warn' ? '⚠️ ' : '❌';
const message = result.details ? `${result.name}: ${result.details}` : result.name;
console.log(`${icon} ${message}`);
}
export async function doctor(options: OptionValues = {}): Promise<void> {
const checks: CheckResult[] = [];
// Data directory
try {
if (!fs.existsSync(paths.DATA_DIR)) {
fs.mkdirSync(paths.DATA_DIR, { recursive: true });
checks.push({ name: `Data directory created at ${paths.DATA_DIR}`, status: 'warn' });
} else {
const stats = fs.statSync(paths.DATA_DIR);
let writable = false;
try {
fs.accessSync(paths.DATA_DIR, fs.constants.W_OK);
writable = true;
} catch {}
checks.push({
name: `Data directory ${paths.DATA_DIR}`,
status: stats.isDirectory() && writable ? 'pass' : 'fail',
details: stats.isDirectory() && writable ? 'accessible' : 'not writable'
});
}
} catch (error: any) {
checks.push({
name: 'Data directory',
status: 'fail',
details: error?.message || String(error)
});
}
// SQLite connectivity
try {
const db = new HooksDatabase();
checks.push({
name: 'SQLite database',
status: 'pass',
details: 'connected'
});
} catch (error: any) {
checks.push({
name: 'SQLite database',
status: 'fail',
details: error?.message || String(error)
});
}
// Chroma connectivity
try {
const chromaExists = fs.existsSync(paths.CHROMA_DIR);
checks.push({
name: 'Chroma vector store',
status: chromaExists ? 'pass' : 'warn',
details: chromaExists ? `data dir ${path.resolve(paths.CHROMA_DIR)}` : 'Not yet initialized'
});
} catch (error: any) {
checks.push({
name: 'Chroma vector store',
status: 'warn',
details: error?.message || 'Unable to check Chroma directory'
});
}
if (options.json) {
console.log(JSON.stringify({ checks }, null, 2));
} else {
console.log('claude-mem doctor');
console.log('=================');
checks.forEach(printCheck);
}
}
-519
View File
@@ -1,519 +0,0 @@
import { OptionValues } from 'commander';
import { readFileSync, writeFileSync, existsSync, mkdirSync, copyFileSync, statSync, readdirSync } from 'fs';
import { join, resolve, dirname } from 'path';
import { homedir } from 'os';
import { execSync } from 'child_process';
import { fileURLToPath } from 'url';
import * as p from '@clack/prompts';
import gradient from 'gradient-string';
import chalk from 'chalk';
import boxen from 'boxen';
import { PACKAGE_NAME } from '../shared/config.js';
import type { Settings } from '../shared/types.js';
import { Platform } from '../utils/platform.js';
import * as paths from '../shared/paths.js';
// Enhanced animation utilities
function createLoadingAnimation(message: string) {
let interval: NodeJS.Timeout;
let frame = 0;
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
return {
start() {
interval = setInterval(() => {
process.stdout.write(`\r${chalk.cyan(frames[frame % frames.length])} ${message}`);
frame++;
}, 50); // Faster spinner animation (was 80ms)
},
stop(result: string, success: boolean = true) {
clearInterval(interval);
const icon = success ? chalk.green('✓') : chalk.red('✗');
process.stdout.write(`\r${icon} ${result}\n`);
}
};
}
// Fast rainbow gradient preset with tighter color transitions
const fastRainbow = gradient(['#ff0000', '#ff4500', '#ffa500', '#ffff00', '#00ff00', '#00ffff', '#0000ff', '#8b00ff']);
const vibrantRainbow = gradient(['#ff006e', '#fb5607', '#ffbe0b', '#8338ec', '#3a86ff']);
// Installation scope type
type InstallScope = 'user' | 'project' | 'local';
// Installation configuration from wizard
interface InstallConfig {
scope: InstallScope;
customPath?: string;
hookTimeout: number;
forceReinstall: boolean;
enableSmartTrash?: boolean;
saveMemoriesOnClear?: boolean;
}
function installUv(): void {
Platform.installUv();
process.env.PATH = `${homedir()}/.cargo/bin:${process.env.PATH}`;
}
function hasExistingInstallation(): boolean {
const settingsPath = paths.CLAUDE_SETTINGS_PATH;
if (!existsSync(settingsPath)) return false;
try {
const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
return !!(settings.hooks?.SessionStart || settings.hooks?.Stop || settings.hooks?.PostToolUse);
} catch {
return false;
}
}
async function runInstallationWizard(existingInstall: boolean): Promise<InstallConfig | null> {
const config: Partial<InstallConfig> = {};
if (existingInstall) {
const shouldReinstall = await p.confirm({
message: '🧠 Existing claude-mem installation detected. Your memories and data are safe!\n\nReinstall to update hooks and configuration?',
initialValue: true
});
if (p.isCancel(shouldReinstall) || !shouldReinstall) {
p.cancel('Installation cancelled');
return null;
}
config.forceReinstall = true;
} else {
config.forceReinstall = false;
}
// Select installation scope
const scope = await p.select({
message: 'Select installation scope',
options: [
{
value: 'user',
label: 'User (Recommended)',
hint: 'Install for current user (~/.claude)'
},
{
value: 'project',
label: 'Project',
hint: 'Install for current project only (./.mcp.json)'
},
{
value: 'local',
label: 'Local',
hint: 'Custom local installation'
}
],
initialValue: 'user'
});
if (p.isCancel(scope)) {
p.cancel('Installation cancelled');
return null;
}
config.scope = scope as InstallScope;
// If local scope, ask for custom path
if (scope === 'local') {
const customPath = await p.text({
message: 'Enter custom installation directory',
placeholder: join(process.cwd(), '.claude-mem'),
validate: (value) => {
if (!value) return 'Path is required';
if (!value.startsWith('/') && !value.startsWith('~')) {
return 'Please provide an absolute path';
}
}
});
if (p.isCancel(customPath)) {
p.cancel('Installation cancelled');
return null;
}
config.customPath = customPath as string;
}
// Use default hook timeout (3 minutes)
config.hookTimeout = 180000;
// Always install/reinstall Chroma MCP - it's required for claude-mem to work
// Ask about smart trash alias
const enableSmartTrash = await p.confirm({
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".',
initialValue: true
});
if (p.isCancel(enableSmartTrash)) {
p.cancel('Installation cancelled');
return null;
}
config.enableSmartTrash = enableSmartTrash;
// Ask about save-on-clear
const saveMemoriesOnClear = await p.confirm({
message: 'Would 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.',
initialValue: false
});
if (p.isCancel(saveMemoriesOnClear)) {
p.cancel('Installation cancelled');
return null;
}
config.saveMemoriesOnClear = saveMemoriesOnClear;
return config as InstallConfig;
}
// </Block>
// <Block> Directory structure creation - natural setup flow
function ensureDirectoryStructure(): void {
// Create all data directories
paths.ensureAllDataDirs();
// Create all Claude integration directories
paths.ensureAllClaudeDirs();
// Create package.json in .claude-mem to fix ESM module issues
const packageJsonPath = join(paths.DATA_DIR, 'package.json');
if (!existsSync(packageJsonPath)) {
const packageJson = {
name: "claude-mem-data",
type: "module"
};
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
}
}
// </Block>
function copyFileRecursively(src: string, dest: string): void {
const stat = statSync(src);
if (stat.isDirectory()) {
mkdirSync(dest, { recursive: true });
const files = readdirSync(src);
files.forEach((file: string) => {
copyFileRecursively(join(src, file), join(dest, file));
});
} else {
copyFileSync(src, dest);
}
}
function ensureClaudeMdInstructions(): void {
const claudeMdPath = paths.CLAUDE_MD_PATH;
const claudeMdDir = dirname(claudeMdPath);
// Ensure .claude directory exists
mkdirSync(claudeMdDir, { recursive: true });
const instructions = `
<!-- CLAUDE-MEM QUICK REFERENCE -->
## 🧠 Memory System Quick Reference
### Search Your Memories (SIMPLE & POWERFUL)
- **Semantic search is king**: \`mcp__claude-mem__chroma_query_documents(["search terms"])\`
- **🔒 ALWAYS include project name in query**: \`["claude-mem feature authentication"]\` not just \`["feature authentication"]\`
- **Include dates for temporal search**: \`["project-name 2025-09-09 bug fix"]\` finds memories from that date
- **Get specific memory**: \`mcp__claude-mem__chroma_get_documents(ids: ["document_id"])\`
### Search Tips That Actually Work
- **Project isolation**: Always prefix queries with project name to avoid cross-contamination
- **Temporal search**: Include dates (YYYY-MM-DD) in query text to find memories from specific times
- **Intent-based**: "implementing oauth" > "oauth implementation code function"
- **Multiple queries**: Search with different phrasings for better coverage
- **Session-specific**: Include session ID in query when you know it
### What Doesn't Work (Don't Do This!)
- ❌ Complex where filters with $and/$or - they cause errors
- ❌ Timestamp comparisons ($gte/$lt) - Chroma stores timestamps as strings
- ❌ Mixing project filters in where clause - causes "Error finding id"
### Storage
- Collection: "claude_memories"
- Archives: ~/.claude-mem/archives/
<!-- /CLAUDE-MEM QUICK REFERENCE -->`;
// Check if file exists and read content
let content = '';
if (existsSync(claudeMdPath)) {
content = readFileSync(claudeMdPath, 'utf8');
// Check if instructions already exist (handle both old and new format)
const hasOldInstructions = content.includes('<!-- CLAUDE-MEM INSTRUCTIONS -->');
const hasNewInstructions = content.includes('<!-- CLAUDE-MEM QUICK REFERENCE -->');
if (hasOldInstructions || hasNewInstructions) {
// Replace existing instructions (handle both old and new markers)
let startMarker, endMarker;
if (hasOldInstructions) {
startMarker = '<!-- CLAUDE-MEM INSTRUCTIONS -->';
endMarker = '<!-- /CLAUDE-MEM INSTRUCTIONS -->';
} else {
startMarker = '<!-- CLAUDE-MEM QUICK REFERENCE -->';
endMarker = '<!-- /CLAUDE-MEM QUICK REFERENCE -->';
}
const startIndex = content.indexOf(startMarker);
const endIndex = content.indexOf(endMarker) + endMarker.length;
if (startIndex !== -1 && endIndex !== -1) {
content = content.substring(0, startIndex) + instructions.trim() + content.substring(endIndex);
}
} else {
// Append instructions to the end
content = content.trim() + '\n' + instructions;
}
} else {
// Create new file with instructions
content = instructions.trim();
}
// Write the updated content
writeFileSync(claudeMdPath, content);
}
function installChromaMcp(forceReinstall: boolean = false): void {
const uvPath = `${homedir()}/.cargo/bin`;
if (existsSync(uvPath) && !process.env.PATH?.includes(uvPath)) {
process.env.PATH = `${uvPath}:${process.env.PATH}`;
}
if (forceReinstall) {
try {
execSync('claude mcp remove claude-mem', { stdio: 'pipe' });
} catch (error) {
// Ignore errors if claude-mem doesn't exist
}
}
const chromaMcpCommand = `claude mcp add claude-mem -- uvx chroma-mcp --client-type persistent --data-dir ${paths.CHROMA_DIR}`;
execSync(chromaMcpCommand, { stdio: 'inherit' });
}
function createHookConfig(command: string, timeout: number, matcher?: string) {
const config: any = {
hooks: [{ type: "command", command, timeout }]
};
if (matcher) config.matcher = matcher;
return config;
}
function configureHooks(settingsPath: string): void {
let settings: any = existsSync(settingsPath)
? JSON.parse(readFileSync(settingsPath, 'utf8'))
: { hooks: {} };
mkdirSync(dirname(settingsPath), { recursive: true });
if (!settings.hooks) settings.hooks = {};
// Remove any existing claude-mem hooks
const hookTypes = ['SessionStart', 'Stop', 'UserPromptSubmit', 'PostToolUse'];
hookTypes.forEach(type => {
if (settings.hooks[type]) {
settings.hooks[type] = settings.hooks[type].filter(
(cfg: any) => !cfg.hooks?.some((h: any) => h.command?.includes(PACKAGE_NAME))
);
}
});
// Configure hooks to use CLI commands directly
// claude-mem should be in PATH after npm installation
settings.hooks.SessionStart = [createHookConfig(`${PACKAGE_NAME} context`, 180)];
settings.hooks.Stop = [createHookConfig(`${PACKAGE_NAME} summary`, 60)];
settings.hooks.UserPromptSubmit = [createHookConfig(`${PACKAGE_NAME} new`, 60)];
settings.hooks.PostToolUse = [createHookConfig(`${PACKAGE_NAME} save`, 180, "*")];
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
}
function getSettingsPath(config: InstallConfig): string {
if (config.scope === 'local' && config.customPath) {
return join(config.customPath, 'settings.local.json');
} else if (config.scope === 'project') {
return join(process.cwd(), '.claude', 'settings.json');
} else {
return paths.CLAUDE_SETTINGS_PATH;
}
}
function configureUserSettings(config: InstallConfig): void {
const userSettingsPath = paths.USER_SETTINGS_PATH;
let userSettings: Settings = existsSync(userSettingsPath)
? JSON.parse(readFileSync(userSettingsPath, 'utf8'))
: {};
userSettings.backend = 'chroma';
userSettings.installed = true;
userSettings.embedded = true;
userSettings.saveMemoriesOnClear = config.saveMemoriesOnClear || false;
writeFileSync(userSettingsPath, JSON.stringify(userSettings, null, 2));
}
function configureSmartTrashAlias(): void {
const shellConfigs = Platform.getShellConfigPaths();
const aliasDefinition = Platform.getAliasDefinition('rm', 'claude-mem trash');
const commentLine = '# claude-mem smart trash alias';
for (const configPath of shellConfigs) {
if (!existsSync(configPath)) {
// Create the file if it doesn't exist (especially for PowerShell profiles)
const dir = dirname(configPath);
mkdirSync(dir, { recursive: true });
writeFileSync(configPath, '');
}
let content = readFileSync(configPath, 'utf8');
if (content.includes(aliasDefinition)) continue;
const aliasBlock = `\n${commentLine}\n${aliasDefinition}\n`;
content += aliasBlock;
writeFileSync(configPath, content);
}
}
function installClaudeCommands(): void {
const claudeCommandsDir = paths.CLAUDE_COMMANDS_DIR;
const packageCommandsDir = paths.getPackageCommandsDir();
mkdirSync(claudeCommandsDir, { recursive: true });
const commandFiles = ['save.md', 'remember.md', 'claude-mem.md'];
for (const fileName of commandFiles) {
const sourcePath = join(packageCommandsDir, fileName);
const destPath = join(claudeCommandsDir, fileName);
if (existsSync(sourcePath)) {
copyFileSync(sourcePath, destPath);
}
}
}
export async function install(options: OptionValues = {}): Promise<void> {
console.log(fastRainbow('\n═══════════════════════════════════════'));
console.log(fastRainbow(' CLAUDE-MEM INSTALLER '));
console.log(fastRainbow('═══════════════════════════════════════'));
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'), {
padding: 2,
margin: 1,
borderStyle: 'double',
borderColor: 'magenta',
textAlignment: 'center'
}));
installUv();
const isNonInteractive = options.user || options.project || options.local || options.force;
let config: InstallConfig;
if (isNonInteractive) {
config = {
scope: options.local ? 'local' : options.project ? 'project' : 'user',
customPath: options.path,
hookTimeout: options.timeout ? parseInt(options.timeout) : 180,
forceReinstall: !!options.force,
enableSmartTrash: false,
saveMemoriesOnClear: false
};
} else {
const existingInstall = hasExistingInstallation();
const wizardConfig = await runInstallationWizard(existingInstall);
if (!wizardConfig) {
process.exit(0);
}
config = wizardConfig;
}
console.log(vibrantRainbow('\n🚀 Beginning Installation Process\n'));
const steps = [
{ name: 'Creating directory structure', fn: () => ensureDirectoryStructure() },
{ name: 'Installing Chroma MCP server', fn: () => installChromaMcp(config.forceReinstall) },
{ name: 'Adding CLAUDE.md instructions', fn: () => ensureClaudeMdInstructions() },
{ name: 'Installing Claude commands', fn: () => installClaudeCommands() },
{ name: 'Configuring Claude settings', fn: () => configureHooks(getSettingsPath(config)) },
{ name: 'Configuring user settings', fn: () => configureUserSettings(config) }
];
if (config.enableSmartTrash) {
steps.push({ name: 'Configuring Smart Trash alias', fn: () => configureSmartTrashAlias() });
}
for (let i = 0; i < steps.length; i++) {
const step = steps[i];
const progress = `[${i + 1}/${steps.length}]`;
const loader = createLoadingAnimation(`${chalk.gray(progress)} ${step.name}...`);
loader.start();
step.fn();
loader.stop(`${chalk.gray(progress)} ${step.name} ${vibrantRainbow('completed! ✨')}`);
}
// Beautiful success message
const successTitle = fastRainbow('🎉 INSTALLATION COMPLETE! 🎉');
const successMessage = `
${chalk.bold('How your new memory system works:')}
${chalk.green('•')} When you start Claude Code, claude-mem loads your latest memories automatically
${chalk.green('•')} Memories are saved automatically as you work
${chalk.green('•')} Ask Claude to search your memories anytime with natural language
${chalk.green('•')} Instructions added to ${chalk.cyan('~/.claude/CLAUDE.md')} teach Claude how to use the system
${chalk.bold('Slash Commands Available:')}
${chalk.cyan('/claude-mem help')} - Show all memory commands and features
${chalk.cyan('/save')} - Quick save of current conversation overview
${chalk.cyan('/remember')} - Search your saved memories
${chalk.bold('Quick Start:')}
${chalk.yellow('1.')} Restart Claude Code to activate your memory system
${chalk.yellow('2.')} Start using Claude normally - memories save automatically
${chalk.yellow('3.')} Search memories by asking: ${chalk.italic('"Search my memories for X"')}`;
const finalSmartTrashNote = config.enableSmartTrash ?
`\n\n${chalk.blue('🗑️ Smart Trash Enabled:')}
${chalk.gray(' • rm commands now move files to ~/.claude-mem/trash')}
${chalk.gray(' • View trash:')} ${chalk.cyan('claude-mem trash view')}
${chalk.gray(' • Restore files:')} ${chalk.cyan('claude-mem restore')}
${chalk.gray(' • Empty trash:')} ${chalk.cyan('claude-mem trash empty')}
${chalk.yellow(' • Restart terminal for alias to activate')}` : '';
const finalClearHookNote = config.saveMemoriesOnClear ?
`\n\n${chalk.magenta('💾 Save-on-clear enabled:')}
${chalk.gray(' • /clear now saves memories automatically (takes ~1 minute)')}` : '';
console.log(boxen(successTitle + successMessage + finalSmartTrashNote + finalClearHookNote, {
padding: 2,
margin: 1,
borderStyle: 'double',
borderColor: 'green',
backgroundColor: '#001122'
}));
// Final flourish
console.log(fastRainbow('\n✨ Welcome to the future of persistent AI conversations! ✨\n'));
}
-73
View File
@@ -1,73 +0,0 @@
import { OptionValues } from 'commander';
import { readFileSync, readdirSync, statSync } from 'fs';
import { join } from 'path';
import * as paths from '../shared/paths.js';
// <Block> 1.1 ====================================
async function showLog(logPath: string, logType: string, tail: number): Promise<void> {
// <Block> 1.2 ====================================
try {
const content = readFileSync(logPath, 'utf8');
const lines = content.split('\n').filter(line => line.trim());
const displayLines = lines.slice(-tail);
console.log(`📋 ${logType} Logs (last ${tail} lines):`);
console.log(` File: ${logPath}`);
console.log('');
// <Block> 1.3 ====================================
if (displayLines.length === 0) {
console.log(' No log entries found');
// </Block> =======================================
} else {
displayLines.forEach(line => {
console.log(` ${line}`);
});
}
// </Block> =======================================
console.log('');
// </Block> =======================================
} catch (error) {
// <Block> 1.4 ====================================
console.log(`❌ Could not read ${logType.toLowerCase()} log: ${logPath}`);
// </Block> =======================================
}
// </Block> =======================================
}
// <Block> 2.1 ====================================
export async function logs(options: OptionValues = {}): Promise<void> {
// <Block> 2.2 ====================================
const logsDir = paths.LOGS_DIR;
const tail = parseInt(options.tail) || 20;
// </Block> =======================================
// Find most recent log file
try {
const files = readdirSync(logsDir);
const logFiles = files
.filter(f => f.startsWith('claude-mem-') && f.endsWith('.log'))
.map(f => ({
name: f,
path: join(logsDir, f),
mtime: statSync(join(logsDir, f)).mtime
}))
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
if (logFiles.length === 0) {
console.log('❌ No log files found in ~/.claude-mem/logs/');
return;
}
// Show most recent log
await showLog(logFiles[0].path, 'Most Recent', tail);
if (options.all && logFiles.length > 1) {
console.log(`📚 Found ${logFiles.length} total log files`);
}
} catch (error) {
console.log('❌ Could not read logs directory: ~/.claude-mem/logs/');
console.log(' Run a compression first to generate logs');
}
}
-24
View File
@@ -1,24 +0,0 @@
import { readdirSync, renameSync } from 'fs';
import { join } from 'path';
import * as p from '@clack/prompts';
import * as paths from '../shared/paths.js';
export async function restore(): Promise<void> {
const trashDir = paths.TRASH_DIR;
const files = readdirSync(trashDir);
if (files.length === 0) {
console.log('Trash is empty');
return;
}
const file = await p.select({
message: 'Select file to restore:',
options: files.map(f => ({ value: f, label: f }))
});
if (p.isCancel(file)) return;
renameSync(join(trashDir, file), join(process.cwd(), file));
console.log(`Restored ${file}`);
}
-158
View File
@@ -1,158 +0,0 @@
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
import { join, resolve, dirname } from 'path';
import { execSync } from 'child_process';
import { fileURLToPath } from 'url';
import * as paths from '../shared/paths.js';
import { HooksDatabase } from '../services/sqlite/index.js';
import chalk from 'chalk';
const __dirname = dirname(fileURLToPath(import.meta.url));
export async function status(): Promise<void> {
console.log('🔍 Claude Memory System Status Check');
console.log('=====================================\n');
// paths imported
console.log('⚙️ Settings Configuration:');
const checkSettings = (name: string, path: string) => {
if (!existsSync(path)) {
console.log(` ⏭️ ${name}: No settings file`);
return;
}
console.log(` 📋 ${name}: ${path}`);
try {
const settings = JSON.parse(readFileSync(path, 'utf8'));
const hasSessionStart = settings.hooks?.SessionStart?.some((matcher: any) =>
matcher.hooks?.some((hook: any) => hook.command?.includes('claude-mem'))
);
const hasStop = settings.hooks?.Stop?.some((matcher: any) =>
matcher.hooks?.some((hook: any) => hook.command?.includes('claude-mem'))
);
const hasUserPrompt = settings.hooks?.UserPromptSubmit?.some((matcher: any) =>
matcher.hooks?.some((hook: any) => hook.command?.includes('claude-mem'))
);
const hasPostTool = settings.hooks?.PostToolUse?.some((matcher: any) =>
matcher.hooks?.some((hook: any) => hook.command?.includes('claude-mem'))
);
console.log(` SessionStart (claude-mem context): ${hasSessionStart ? '✅' : '❌'}`);
console.log(` Stop (claude-mem summary): ${hasStop ? '✅' : '❌'}`);
console.log(` UserPromptSubmit (claude-mem new): ${hasUserPrompt ? '✅' : '❌'}`);
console.log(` PostToolUse (claude-mem save): ${hasPostTool ? '✅' : '❌'}`);
} catch (error: any) {
console.log(` ⚠️ Could not parse settings`);
}
};
checkSettings('Global', paths.ClaudeSettingsPath());
checkSettings('Project', join(process.cwd(), '.claude', 'settings.json'));
console.log('');
console.log('📦 Compressed Transcripts:');
const claudeProjectsDir = join(paths.ClaudeConfigDirectory(), 'projects');
if (existsSync(claudeProjectsDir)) {
try {
let compressedCount = 0;
let archiveCount = 0;
const searchDir = (dir: string, depth = 0) => {
if (depth > 3) return;
const files = readdirSync(dir);
for (const file of files) {
const fullPath = join(dir, file);
const stats = statSync(fullPath);
if (stats.isDirectory() && !file.startsWith('.')) {
searchDir(fullPath, depth + 1);
} else if (file.endsWith('.jsonl.compressed')) {
compressedCount++;
} else if (file.endsWith('.jsonl.archive')) {
archiveCount++;
}
}
};
searchDir(claudeProjectsDir);
console.log(` Compressed files: ${compressedCount}`);
console.log(` Archive files: ${archiveCount}`);
} catch (error) {
console.log(` ⚠️ Could not scan projects directory`);
}
} else {
console.log(` ️ No Claude projects directory found`);
}
console.log('');
console.log('🔧 Runtime Environment:');
const checkCommand = (cmd: string, name: string) => {
try {
const version = execSync(`${cmd} --version`, { encoding: 'utf8' }).trim();
console.log(`${name}: ${version}`);
} catch {
console.log(`${name}: Not found`);
}
};
checkCommand('node', 'Node.js');
checkCommand('bun', 'Bun');
console.log('');
console.log('🧠 Chroma Storage Status:');
console.log(' ✅ Storage backend: Chroma MCP');
console.log(` 📍 Data location: ${paths.ChromaDirectory()}`);
console.log(' 🔍 Features: Vector search, semantic similarity, document storage');
console.log('');
console.log('📊 Summary:');
const globalPath = paths.ClaudeSettingsPath();
const projectPath = join(process.cwd(), '.claude', 'settings.json');
let isInstalled = false;
let installLocation = 'Not installed';
try {
if (existsSync(globalPath)) {
const settings = JSON.parse(readFileSync(globalPath, 'utf8'));
if (settings.hooks?.SessionStart || settings.hooks?.Stop || settings.hooks?.PostToolUse) {
isInstalled = true;
installLocation = 'Global';
}
}
if (existsSync(projectPath)) {
const settings = JSON.parse(readFileSync(projectPath, 'utf8'));
if (settings.hooks?.SessionStart || settings.hooks?.Stop || settings.hooks?.PostToolUse) {
isInstalled = true;
installLocation = installLocation === 'Global' ? 'Global + Project' : 'Project';
}
}
} catch {}
if (isInstalled) {
console.log(` ✅ Claude Memory System is installed (${installLocation})`);
console.log('');
console.log('💡 To test: Use /compact in Claude Code');
} else {
console.log(` ❌ Claude Memory System is not installed`);
console.log('');
console.log('💡 To install: claude-mem install');
}
}
-66
View File
@@ -1,66 +0,0 @@
import { rmSync, readdirSync, existsSync, statSync } from 'fs';
import { join } from 'path';
import * as p from '@clack/prompts';
import * as paths from '../shared/paths.js';
export async function emptyTrash(options: { force?: boolean } = {}): Promise<void> {
const trashDir = paths.TRASH_DIR;
// Check if trash directory exists
if (!existsSync(trashDir)) {
p.log.info('🗑️ Trash is already empty');
return;
}
try {
const files = readdirSync(trashDir);
if (files.length === 0) {
p.log.info('🗑️ Trash is already empty');
return;
}
// Count items
let folderCount = 0;
let fileCount = 0;
for (const file of files) {
const filePath = join(trashDir, file);
const stats = statSync(filePath);
if (stats.isDirectory()) {
folderCount++;
} else {
fileCount++;
}
}
// Confirm deletion unless --force flag is used
if (!options.force) {
const confirm = await p.confirm({
message: `Permanently delete ${folderCount} folders and ${fileCount} files from trash?`,
initialValue: false
});
if (p.isCancel(confirm) || !confirm) {
p.log.info('Cancelled - trash not emptied');
return;
}
}
// Delete all files in trash
const s = p.spinner();
s.start('Emptying trash...');
for (const file of files) {
const filePath = join(trashDir, file);
rmSync(filePath, { recursive: true, force: true });
}
s.stop(`🗑️ Trash emptied - permanently deleted ${folderCount} folders and ${fileCount} files`);
} catch (error) {
p.log.error('Failed to empty trash');
console.error(error);
process.exit(1);
}
}
-124
View File
@@ -1,124 +0,0 @@
import { readdirSync, statSync } from 'fs';
import { join, basename } from 'path';
import * as p from '@clack/prompts';
import * as paths from '../shared/paths.js';
interface TrashItem {
originalName: string;
trashedName: string;
size: number;
trashedAt: Date;
isDirectory: boolean;
}
function parseTrashName(filename: string): { name: string; timestamp: number } {
const lastDotIndex = filename.lastIndexOf('.');
if (lastDotIndex === -1) return { name: filename, timestamp: 0 };
const timestamp = parseInt(filename.substring(lastDotIndex + 1));
if (isNaN(timestamp)) return { name: filename, timestamp: 0 };
return {
name: filename.substring(0, lastDotIndex),
timestamp
};
}
function formatSize(bytes: number): string {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
function getDirectorySize(dirPath: string): number {
let size = 0;
const files = readdirSync(dirPath);
for (const file of files) {
const filePath = join(dirPath, file);
const stats = statSync(filePath);
if (stats.isDirectory()) {
size += getDirectorySize(filePath);
} else {
size += stats.size;
}
}
return size;
}
export async function viewTrash(): Promise<void> {
const trashDir = paths.TRASH_DIR;
try {
const files = readdirSync(trashDir);
if (files.length === 0) {
p.log.info('🗑️ Trash is empty');
return;
}
const items: TrashItem[] = files.map(file => {
const filePath = join(trashDir, file);
const stats = statSync(filePath);
const { name, timestamp } = parseTrashName(file);
const size = stats.isDirectory() ? getDirectorySize(filePath) : stats.size;
return {
originalName: name,
trashedName: file,
size,
trashedAt: new Date(timestamp),
isDirectory: stats.isDirectory()
};
});
// Sort by date, newest first
items.sort((a, b) => b.trashedAt.getTime() - a.trashedAt.getTime());
// Display header
console.log('\n🗑️ Trash Contents\n');
console.log('─'.repeat(80));
// Display items
let totalSize = 0;
let folderCount = 0;
let fileCount = 0;
for (const item of items) {
totalSize += item.size;
if (item.isDirectory) {
folderCount++;
} else {
fileCount++;
}
const type = item.isDirectory ? '📁' : '📄';
const date = item.trashedAt.toLocaleString();
const size = formatSize(item.size);
console.log(`${type} ${item.originalName}`);
console.log(` Size: ${size} | Trashed: ${date}`);
console.log(` ID: ${item.trashedName}`);
console.log();
}
// Display summary
console.log('─'.repeat(80));
console.log(`Total: ${folderCount} folders, ${fileCount} files (${formatSize(totalSize)})`);
console.log('\nTo restore files: claude-mem restore');
console.log('To empty trash: claude-mem trash empty');
} catch (error) {
if ((error as any).code === 'ENOENT') {
p.log.info('🗑️ Trash is empty');
} else {
p.log.error('Failed to read trash directory');
console.error(error);
}
}
}
-60
View File
@@ -1,60 +0,0 @@
import { renameSync, existsSync, mkdirSync, statSync } from 'fs';
import { join, basename } from 'path';
import { glob } from 'glob';
import * as paths from '../shared/paths.js';
interface TrashOptions {
force?: boolean;
recursive?: boolean;
}
export async function trash(filePaths: string | string[], options: TrashOptions = {}): Promise<void> {
const trashDir = paths.TRASH_DIR;
if (!existsSync(trashDir)) mkdirSync(trashDir, { recursive: true });
// Handle single string or array of paths
const paths = Array.isArray(filePaths) ? filePaths : [filePaths];
for (const filePath of paths) {
// Handle glob patterns
const expandedPaths = await glob(filePath);
const actualPaths = expandedPaths.length > 0 ? expandedPaths : [filePath];
for (const actualPath of actualPaths) {
try {
// Check if file exists
if (!existsSync(actualPath)) {
if (!options.force) {
console.error(`trash: ${actualPath}: No such file or directory`);
continue;
}
// With -f, silently skip missing files
continue;
}
// Check if it's a directory and we need recursive
const stats = statSync(actualPath);
if (stats.isDirectory() && !options.recursive) {
if (!options.force) {
console.error(`trash: ${actualPath}: is a directory`);
continue;
}
}
// Generate unique destination name to avoid conflicts
const fileName = basename(actualPath);
const timestamp = Date.now();
const destination = join(trashDir, `${fileName}.${timestamp}`);
renameSync(actualPath, destination);
console.log(`Moved ${fileName} to trash`);
} catch (error) {
if (!options.force) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error(`trash: ${actualPath}: ${errorMessage}`);
}
}
}
}
}
-161
View File
@@ -1,161 +0,0 @@
import { OptionValues } from 'commander';
import { readFileSync, writeFileSync, existsSync } from 'fs';
import { join } from 'path';
import { homedir } from 'os';
import * as paths from '../shared/paths.js';
async function removeSmartTrashAlias(): Promise<boolean> {
const homeDir = homedir();
const shellConfigs = [
join(homeDir, '.bashrc'),
join(homeDir, '.zshrc'),
join(homeDir, '.bash_profile')
];
const aliasLine = 'alias rm="claude-mem trash"';
// Handle both variations of the comment line
const commentPatterns = [
'# claude-mem smart trash alias',
'# claude-mem trash bin alias'
];
let removedFromAny = false;
for (const configPath of shellConfigs) {
if (!existsSync(configPath)) continue;
let content = readFileSync(configPath, 'utf8');
// Check if alias exists
if (!content.includes(aliasLine)) {
continue; // Not configured in this file
}
// Remove the alias and its comment
const lines = content.split('\n');
const filteredLines = lines.filter((line, index) => {
// Skip the alias line
if (line.trim() === aliasLine) return false;
// Skip any claude-mem comment line if it's right before the alias
for (const commentPattern of commentPatterns) {
if (line.trim() === commentPattern &&
index + 1 < lines.length &&
lines[index + 1].trim() === aliasLine) {
return false;
}
}
return true;
});
const newContent = filteredLines.join('\n');
// Only write if content actually changed
if (newContent !== content) {
// Create backup
const backupPath = configPath + '.backup.' + Date.now();
writeFileSync(backupPath, content);
// Write updated content
writeFileSync(configPath, newContent);
console.log(`✅ Removed Smart Trash alias from ${configPath.replace(homeDir, '~')}`);
removedFromAny = true;
}
}
return removedFromAny;
}
export async function uninstall(options: OptionValues = {}): Promise<void> {
console.log('🔄 Uninstalling Claude Memory System hooks...');
const locations = [];
if (options.all) {
locations.push({
name: 'User',
path: paths.CLAUDE_SETTINGS_PATH
});
locations.push({
name: 'Project',
path: join(process.cwd(), '.claude', 'settings.json')
});
} else {
const isProject = options.project;
locations.push({
name: isProject ? 'Project' : 'User',
path: isProject ? join(process.cwd(), '.claude', 'settings.json') : paths.CLAUDE_SETTINGS_PATH
});
}
let removedCount = 0;
for (const location of locations) {
if (!existsSync(location.path)) {
console.log(`⏭️ No settings found at ${location.name} location`);
continue;
}
const content = readFileSync(location.path, 'utf8');
const settings = JSON.parse(content);
if (!settings.hooks) {
console.log(`⏭️ No hooks configured in ${location.name} settings`);
continue;
}
let modified = false;
// Remove claude-mem hooks (CLI commands)
const hookTypes = ['SessionStart', 'Stop', 'UserPromptSubmit', 'PostToolUse'];
for (const hookType of hookTypes) {
if (settings.hooks[hookType]) {
const filteredHooks = settings.hooks[hookType].filter((matcher: any) =>
!matcher.hooks?.some((hook: any) => hook.command?.includes('claude-mem'))
);
if (filteredHooks.length !== settings.hooks[hookType].length) {
settings.hooks[hookType] = filteredHooks.length ? filteredHooks : undefined;
modified = true;
console.log(`✅ Removed ${hookType} hook from ${location.name} settings`);
}
}
}
// Clean up undefined hooks
hookTypes.forEach(hookType => {
if (settings.hooks[hookType] === undefined) delete settings.hooks[hookType];
});
if (!Object.keys(settings.hooks).length) delete settings.hooks;
if (modified) {
const backupPath = location.path + '.backup.' + Date.now();
writeFileSync(backupPath, content);
console.log(`📋 Created backup: ${backupPath}`);
writeFileSync(location.path, JSON.stringify(settings, null, 2));
removedCount++;
console.log(`✅ Updated ${location.name} settings: ${location.path}`);
} else {
console.log(`️ No Claude Memory System hooks found in ${location.name} settings`);
}
}
// Remove Smart Trash alias from shell configs
const removedAlias = await removeSmartTrashAlias();
console.log('');
if (removedCount > 0 || removedAlias) {
console.log('✨ Uninstallation complete!');
if (removedCount > 0) {
console.log('The Claude Memory System hooks have been removed from your settings.');
}
if (removedAlias) {
console.log('The Smart Trash alias has been removed from your shell configuration.');
console.log('⚠️ Restart your terminal for the alias removal to take effect.');
}
console.log('');
console.log('Note: Your compressed transcripts and archives are preserved.');
console.log('To reinstall: claude-mem install');
} else {
console.log('️ No Claude Memory System hooks or aliases were found to remove.');
}
}
+9 -15
View File
@@ -48,25 +48,19 @@ export function newHook(input?: UserPromptSubmitInput): void {
db.close();
// Start SDK worker in background as detached process
// In plugin mode, use bundled worker; otherwise use global CLI
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT;
let child;
if (pluginRoot) {
// Plugin mode: use bundled worker
const workerPath = path.join(pluginRoot, 'scripts', 'hooks', 'worker.js');
child = spawn('bun', [workerPath, sessionId.toString()], {
detached: true,
stdio: 'ignore'
});
} else {
// Traditional mode: use global CLI
child = spawn('claude-mem', ['worker', sessionId.toString()], {
detached: true,
stdio: 'ignore'
});
if (!pluginRoot) {
throw new Error('CLAUDE_PLUGIN_ROOT not set - claude-mem must be installed as a Claude Code plugin');
}
// Use bundled worker
const workerPath = path.join(pluginRoot, 'scripts', 'hooks', 'worker.js');
const child = spawn('bun', [workerPath, sessionId.toString()], {
detached: true,
stdio: 'ignore'
});
child.unref();
// Output hook response
-3
View File
@@ -45,7 +45,4 @@ try {
export const PACKAGE_NAME = packageName;
export const PACKAGE_VERSION = packageVersion;
export const PACKAGE_DESCRIPTION = packageDescription;
// Export commonly used names
export const CLI_NAME = PACKAGE_NAME; // The CLI command name
// </Block> =======================================