7fac3e3bb6
- Introduced a detailed reference for implementing hooks in Claude Code, covering configuration, project-specific scripts, plugin hooks, and various hook events. - Explained the input modes available in the Claude Agent SDK, emphasizing the benefits of streaming input mode and providing implementation examples for both streaming and single message input. - Highlighted security considerations and best practices for writing hooks, along with debugging tips and execution details.
587 lines
20 KiB
TypeScript
587 lines
20 KiB
TypeScript
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 { PathDiscovery } from '../services/path-discovery.js';
|
|
import { Platform } from '../utils/platform.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`);
|
|
}
|
|
};
|
|
}
|
|
|
|
// Create animated rainbow text with adjustable speed
|
|
function animatedRainbow(text: string, speed: number = 100): Promise<void> {
|
|
return new Promise((resolve) => {
|
|
let offset = 0;
|
|
const maxFrames = 10;
|
|
|
|
const interval = setInterval(() => {
|
|
// Create a shifted gradient by rotating through different presets
|
|
const gradients = [fastRainbow, vibrantRainbow, gradient.rainbow, gradient.pastel];
|
|
const shifted = gradients[offset % gradients.length](text);
|
|
process.stdout.write('\r' + shifted);
|
|
offset++;
|
|
|
|
if (offset >= maxFrames) {
|
|
clearInterval(interval);
|
|
resolve();
|
|
}
|
|
}, speed);
|
|
});
|
|
}
|
|
|
|
|
|
// 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 detectClaudePath(): string {
|
|
return Platform.findExecutable('claude');
|
|
}
|
|
|
|
function hasExistingInstallation(): boolean {
|
|
const pathDiscovery = PathDiscovery.getInstance();
|
|
return existsSync(pathDiscovery.getHooksDirectory());
|
|
}
|
|
|
|
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 {
|
|
const pathDiscovery = PathDiscovery.getInstance();
|
|
|
|
// Create all data directories
|
|
pathDiscovery.ensureAllDataDirectories();
|
|
|
|
// Create all Claude integration directories
|
|
pathDiscovery.ensureAllClaudeDirectories();
|
|
|
|
// Create package.json in .claude-mem to fix ESM module issues
|
|
const packageJsonPath = join(pathDiscovery.getDataDirectory(), '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()) {
|
|
if (!existsSync(dest)) {
|
|
mkdirSync(dest, { recursive: true });
|
|
}
|
|
const files = readdirSync(src);
|
|
files.forEach((file: string) => {
|
|
copyFileRecursively(join(src, file), join(dest, file));
|
|
});
|
|
} else {
|
|
copyFileSync(src, dest);
|
|
}
|
|
}
|
|
|
|
function writeHookFiles(timeout: number = 180000): void {
|
|
const pathDiscovery = PathDiscovery.getInstance();
|
|
const runtimeHooksDir = pathDiscovery.getHooksDirectory();
|
|
const packageHookTemplatesDir = pathDiscovery.findPackageHookTemplatesDirectory();
|
|
|
|
const hookFiles = ['session-start.js', 'stop.js', 'user-prompt-submit.js', 'post-tool-use.js'];
|
|
|
|
for (const hookFile of hookFiles) {
|
|
const sourceTemplatePath = join(packageHookTemplatesDir, hookFile);
|
|
const runtimeHookPath = join(runtimeHooksDir, hookFile);
|
|
copyFileSync(sourceTemplatePath, runtimeHookPath);
|
|
Platform.makeExecutable(runtimeHookPath);
|
|
}
|
|
|
|
const sourceSharedTemplateDir = join(packageHookTemplatesDir, 'shared');
|
|
const runtimeSharedDir = join(runtimeHooksDir, 'shared');
|
|
if (existsSync(sourceSharedTemplateDir)) {
|
|
copyFileRecursively(sourceSharedTemplateDir, runtimeSharedDir);
|
|
}
|
|
|
|
const hookConfig = {
|
|
packageName: PACKAGE_NAME,
|
|
cliCommand: PACKAGE_NAME,
|
|
backend: 'chroma',
|
|
timeout
|
|
};
|
|
writeFileSync(join(runtimeHooksDir, 'config.json'), JSON.stringify(hookConfig, null, 2));
|
|
|
|
// Create package.json in hooks directory (no dependencies needed with bun:sqlite)
|
|
const hookPackageJson = {
|
|
name: "claude-mem-hooks",
|
|
type: "module"
|
|
};
|
|
writeFileSync(join(runtimeHooksDir, 'package.json'), JSON.stringify(hookPackageJson, null, 2));
|
|
}
|
|
|
|
|
|
function ensureClaudeMdInstructions(): void {
|
|
const pathDiscovery = PathDiscovery.getInstance();
|
|
const claudeMdPath = pathDiscovery.getClaudeMdPath();
|
|
const claudeMdDir = dirname(claudeMdPath);
|
|
|
|
// Ensure .claude directory exists
|
|
if (!existsSync(claudeMdDir)) {
|
|
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 ${PathDiscovery.getInstance().getChromaDirectory()}`;
|
|
execSync(chromaMcpCommand, { stdio: 'inherit' });
|
|
}
|
|
|
|
function createHookConfig(scriptPath: string, timeout: number, matcher?: string) {
|
|
const config: any = {
|
|
hooks: [{ type: "command", command: scriptPath, timeout }]
|
|
};
|
|
if (matcher) config.matcher = matcher;
|
|
return config;
|
|
}
|
|
|
|
function configureHooks(settingsPath: string): void {
|
|
const pathDiscovery = PathDiscovery.getInstance();
|
|
const hooksDir = pathDiscovery.getHooksDirectory();
|
|
|
|
let settings: any = existsSync(settingsPath)
|
|
? JSON.parse(readFileSync(settingsPath, 'utf8'))
|
|
: { hooks: {} };
|
|
|
|
mkdirSync(dirname(settingsPath), { recursive: true });
|
|
|
|
if (!settings.hooks) settings.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))
|
|
);
|
|
}
|
|
});
|
|
|
|
settings.hooks.SessionStart = [createHookConfig(join(hooksDir, 'session-start.js'), 180)];
|
|
settings.hooks.Stop = [createHookConfig(join(hooksDir, 'stop.js'), 60)];
|
|
settings.hooks.UserPromptSubmit = [createHookConfig(join(hooksDir, 'user-prompt-submit.js'), 60)];
|
|
settings.hooks.PostToolUse = [createHookConfig(join(hooksDir, 'post-tool-use.js'), 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 PathDiscovery.getInstance().getClaudeSettingsPath();
|
|
}
|
|
}
|
|
|
|
function configureUserSettings(config: InstallConfig): void {
|
|
const pathDiscovery = PathDiscovery.getInstance();
|
|
const userSettingsPath = pathDiscovery.getUserSettingsPath();
|
|
|
|
let userSettings: Settings = existsSync(userSettingsPath)
|
|
? JSON.parse(readFileSync(userSettingsPath, 'utf8'))
|
|
: {};
|
|
|
|
userSettings.backend = 'chroma';
|
|
userSettings.installed = true;
|
|
userSettings.embedded = true;
|
|
userSettings.saveMemoriesOnClear = config.saveMemoriesOnClear || false;
|
|
userSettings.claudePath = detectClaudePath();
|
|
|
|
writeFileSync(userSettingsPath, JSON.stringify(userSettings, null, 2));
|
|
}
|
|
|
|
function configureSmartTrashAlias(): void {
|
|
const shellConfigs = Platform.getShellConfigPaths();
|
|
const aliasDefinition = Platform.getAliasDefinition('rm', 'claude-mem trash');
|
|
const commentLine = Platform.isWindows()
|
|
? '# claude-mem smart trash alias'
|
|
: '# 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);
|
|
if (!existsSync(dir)) {
|
|
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 pathDiscovery = PathDiscovery.getInstance();
|
|
const claudeCommandsDir = pathDiscovery.getClaudeCommandsDirectory();
|
|
const packageCommandsDir = pathDiscovery.findPackageCommandsDirectory();
|
|
|
|
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: 'Installing memory hooks', fn: () => writeHookFiles(config.hookTimeout) },
|
|
{ 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'));
|
|
}
|