feat: Enhance interactive setup for Claude memory integration
- Updated environment check to verify Claude Code presence. - Improved provider selection process with clearer options and descriptions. - Added functionality to keep current settings during provider configuration. - Introduced installation scope selection for cursor hooks (project/user/skip). - Implemented MCP server configuration in Cursor's mcp.json with error handling. - Added utility functions to find MCP server script path and manage configurations.
This commit is contained in:
File diff suppressed because one or more lines are too long
+172
-30
@@ -1097,26 +1097,10 @@ async function runInteractiveSetup(): Promise<number> {
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Step 1: Check if Claude Code is available
|
// Step 1: Check environment
|
||||||
console.log('Step 1: Checking for Claude Code...\n');
|
console.log('Step 1: Checking environment...\n');
|
||||||
|
|
||||||
const hasClaudeCode = await detectClaudeCode();
|
const hasClaudeCode = await detectClaudeCode();
|
||||||
|
|
||||||
if (hasClaudeCode) {
|
|
||||||
console.log('✅ Claude Code detected! Claude SDK will be used for AI processing.\n');
|
|
||||||
console.log(' You can skip provider configuration and proceed with hook installation.\n');
|
|
||||||
} else {
|
|
||||||
console.log('ℹ️ Claude Code not detected. Setting up standalone mode...\n');
|
|
||||||
console.log(' You\'ll need to configure an AI provider for memory compression.\n');
|
|
||||||
|
|
||||||
// Step 2: Provider selection
|
|
||||||
console.log('Step 2: Choose AI Provider\n');
|
|
||||||
console.log(' [1] Gemini (Recommended - 1500 free requests/day)');
|
|
||||||
console.log(' [2] OpenRouter (100+ models, some free)');
|
|
||||||
console.log(' [3] Skip (use Claude SDK if available later)\n');
|
|
||||||
|
|
||||||
const providerChoice = await question('Enter choice [1-3]: ');
|
|
||||||
|
|
||||||
const settingsPath = path.join(homedir(), '.claude-mem', 'settings.json');
|
const settingsPath = path.join(homedir(), '.claude-mem', 'settings.json');
|
||||||
let settings: Record<string, unknown> = {};
|
let settings: Record<string, unknown> = {};
|
||||||
|
|
||||||
@@ -1129,7 +1113,35 @@ async function runInteractiveSetup(): Promise<number> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const currentProvider = settings['CLAUDE_MEM_PROVIDER'] as string || (hasClaudeCode ? 'claude-sdk' : 'none');
|
||||||
|
|
||||||
|
if (hasClaudeCode) {
|
||||||
|
console.log('✅ Claude Code detected\n');
|
||||||
|
} else {
|
||||||
|
console.log('ℹ️ Claude Code not detected\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Current provider: ${currentProvider}\n`);
|
||||||
|
|
||||||
|
// Step 2: Provider selection (always show)
|
||||||
|
console.log('Step 2: Choose AI Provider\n');
|
||||||
|
if (hasClaudeCode) {
|
||||||
|
console.log(' [1] Claude SDK (Recommended - uses your Claude Code subscription)');
|
||||||
|
} else {
|
||||||
|
console.log(' [1] Claude SDK (requires Claude Code subscription)');
|
||||||
|
}
|
||||||
|
console.log(' [2] Gemini (1500 free requests/day)');
|
||||||
|
console.log(' [3] OpenRouter (100+ models, some free)');
|
||||||
|
console.log(' [4] Keep current settings\n');
|
||||||
|
|
||||||
|
const providerChoice = await question('Enter choice [1-4]: ');
|
||||||
|
|
||||||
if (providerChoice === '1') {
|
if (providerChoice === '1') {
|
||||||
|
settings['CLAUDE_MEM_PROVIDER'] = 'claude-sdk';
|
||||||
|
mkdirSync(path.dirname(settingsPath), { recursive: true });
|
||||||
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
||||||
|
console.log('\n✅ Claude SDK configured!\n');
|
||||||
|
} else if (providerChoice === '2') {
|
||||||
console.log('\n📝 Configuring Gemini...\n');
|
console.log('\n📝 Configuring Gemini...\n');
|
||||||
console.log(' Get your free API key at: https://aistudio.google.com/apikey\n');
|
console.log(' Get your free API key at: https://aistudio.google.com/apikey\n');
|
||||||
|
|
||||||
@@ -1141,12 +1153,11 @@ async function runInteractiveSetup(): Promise<number> {
|
|||||||
settings['CLAUDE_MEM_PROVIDER'] = 'gemini';
|
settings['CLAUDE_MEM_PROVIDER'] = 'gemini';
|
||||||
settings['CLAUDE_MEM_GEMINI_API_KEY'] = apiKey.trim();
|
settings['CLAUDE_MEM_GEMINI_API_KEY'] = apiKey.trim();
|
||||||
|
|
||||||
// Save settings
|
|
||||||
mkdirSync(path.dirname(settingsPath), { recursive: true });
|
mkdirSync(path.dirname(settingsPath), { recursive: true });
|
||||||
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
||||||
console.log('\n✅ Gemini configured successfully!\n');
|
console.log('\n✅ Gemini configured successfully!\n');
|
||||||
}
|
}
|
||||||
} else if (providerChoice === '2') {
|
} else if (providerChoice === '3') {
|
||||||
console.log('\n📝 Configuring OpenRouter...\n');
|
console.log('\n📝 Configuring OpenRouter...\n');
|
||||||
console.log(' Get your API key at: https://openrouter.ai/keys\n');
|
console.log(' Get your API key at: https://openrouter.ai/keys\n');
|
||||||
|
|
||||||
@@ -1158,19 +1169,34 @@ async function runInteractiveSetup(): Promise<number> {
|
|||||||
settings['CLAUDE_MEM_PROVIDER'] = 'openrouter';
|
settings['CLAUDE_MEM_PROVIDER'] = 'openrouter';
|
||||||
settings['CLAUDE_MEM_OPENROUTER_API_KEY'] = apiKey.trim();
|
settings['CLAUDE_MEM_OPENROUTER_API_KEY'] = apiKey.trim();
|
||||||
|
|
||||||
// Save settings
|
|
||||||
mkdirSync(path.dirname(settingsPath), { recursive: true });
|
mkdirSync(path.dirname(settingsPath), { recursive: true });
|
||||||
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
||||||
console.log('\n✅ OpenRouter configured successfully!\n');
|
console.log('\n✅ OpenRouter configured successfully!\n');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('\n⚠️ Skipping provider configuration.');
|
console.log('\n✅ Keeping current settings.\n');
|
||||||
console.log(' Claude SDK will be used if Claude Code is installed later.\n');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 3: Install hooks
|
// Step 3: Install location
|
||||||
console.log('Step 3: Installing Cursor hooks...\n');
|
console.log('Step 3: Choose installation scope\n');
|
||||||
|
console.log(' [1] Project (current directory only) - Recommended');
|
||||||
|
console.log(' [2] User (all projects for current user)');
|
||||||
|
console.log(' [3] Skip hook installation\n');
|
||||||
|
|
||||||
|
const scopeChoice = await question('Enter choice [1-3]: ');
|
||||||
|
|
||||||
|
let installTarget: string | null = null;
|
||||||
|
if (scopeChoice === '1') {
|
||||||
|
installTarget = 'project';
|
||||||
|
} else if (scopeChoice === '2') {
|
||||||
|
installTarget = 'user';
|
||||||
|
} else {
|
||||||
|
console.log('\n⚠️ Skipping hook installation.\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: Install hooks (if target selected)
|
||||||
|
if (installTarget) {
|
||||||
|
console.log(`Step 4: Installing Cursor hooks (${installTarget})...\n`);
|
||||||
|
|
||||||
const cursorHooksDir = findCursorHooksDir();
|
const cursorHooksDir = findCursorHooksDir();
|
||||||
if (!cursorHooksDir) {
|
if (!cursorHooksDir) {
|
||||||
@@ -1180,15 +1206,27 @@ async function runInteractiveSetup(): Promise<number> {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const installResult = await installCursorHooks(cursorHooksDir, 'project');
|
const installResult = await installCursorHooks(cursorHooksDir, installTarget);
|
||||||
|
|
||||||
if (installResult !== 0) {
|
if (installResult !== 0) {
|
||||||
rl.close();
|
rl.close();
|
||||||
return installResult;
|
return installResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: Start worker
|
// Step 5: Configure MCP server for memory search
|
||||||
console.log('\nStep 4: Starting claude-mem worker...\n');
|
console.log('\nStep 5: Configuring MCP server for memory search...\n');
|
||||||
|
|
||||||
|
const mcpResult = configureCursorMcp(installTarget);
|
||||||
|
if (mcpResult !== 0) {
|
||||||
|
console.warn('⚠️ MCP configuration failed, but hooks are installed.');
|
||||||
|
console.warn(' You can manually configure MCP later.\n');
|
||||||
|
} else {
|
||||||
|
console.log('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 6: Start worker
|
||||||
|
console.log('\nStep 6: Starting claude-mem worker...\n');
|
||||||
|
|
||||||
const port = getWorkerPort();
|
const port = getWorkerPort();
|
||||||
const alreadyRunning = await waitForHealth(port, 1000);
|
const alreadyRunning = await waitForHealth(port, 1000);
|
||||||
@@ -1234,9 +1272,15 @@ async function runInteractiveSetup(): Promise<number> {
|
|||||||
║ Setup Complete! 🎉 ║
|
║ Setup Complete! 🎉 ║
|
||||||
╚══════════════════════════════════════════════════════════════════╝
|
╚══════════════════════════════════════════════════════════════════╝
|
||||||
|
|
||||||
|
What's installed:
|
||||||
|
✓ Cursor hooks - Automatically capture sessions
|
||||||
|
✓ Context injection - Past work injected into new chats
|
||||||
|
✓ MCP search server - Ask "what did I work on last week?"
|
||||||
|
|
||||||
Next steps:
|
Next steps:
|
||||||
1. Restart Cursor to load the hooks
|
1. Restart Cursor to load the hooks and MCP server
|
||||||
2. Start chatting - your sessions will be remembered!
|
2. Start chatting - your sessions will be remembered!
|
||||||
|
3. Use natural language to search: "find where I fixed the auth bug"
|
||||||
|
|
||||||
Useful commands:
|
Useful commands:
|
||||||
npm run cursor:status Check installation status
|
npm run cursor:status Check installation status
|
||||||
@@ -1307,6 +1351,104 @@ function findCursorHooksDir(): string | null {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find MCP server script path
|
||||||
|
* Searches in order: marketplace install, source repo
|
||||||
|
*/
|
||||||
|
function findMcpServerPath(): string | null {
|
||||||
|
const possiblePaths = [
|
||||||
|
// Marketplace install location
|
||||||
|
path.join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack', 'plugin', 'scripts', 'mcp-server.cjs'),
|
||||||
|
// Development/source location (relative to built worker-service.cjs in plugin/scripts/)
|
||||||
|
path.join(path.dirname(__filename), 'mcp-server.cjs'),
|
||||||
|
// Alternative dev location
|
||||||
|
path.join(process.cwd(), 'plugin', 'scripts', 'mcp-server.cjs'),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const p of possiblePaths) {
|
||||||
|
if (existsSync(p)) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CursorMcpConfig {
|
||||||
|
mcpServers: {
|
||||||
|
[name: string]: {
|
||||||
|
command: string;
|
||||||
|
args?: string[];
|
||||||
|
env?: Record<string, string>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure MCP server in Cursor's mcp.json
|
||||||
|
* @param target 'project' or 'user'
|
||||||
|
* @returns 0 on success, 1 on failure
|
||||||
|
*/
|
||||||
|
function configureCursorMcp(target: string): number {
|
||||||
|
const mcpServerPath = findMcpServerPath();
|
||||||
|
|
||||||
|
if (!mcpServerPath) {
|
||||||
|
console.error('❌ Could not find MCP server script');
|
||||||
|
console.error(' Expected at: ~/.claude/plugins/marketplaces/thedotmack/plugin/scripts/mcp-server.cjs');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mcpJsonDir: string;
|
||||||
|
let mcpJsonPath: string;
|
||||||
|
|
||||||
|
switch (target) {
|
||||||
|
case 'project':
|
||||||
|
mcpJsonDir = path.join(process.cwd(), '.cursor');
|
||||||
|
mcpJsonPath = path.join(mcpJsonDir, 'mcp.json');
|
||||||
|
break;
|
||||||
|
case 'user':
|
||||||
|
mcpJsonDir = path.join(homedir(), '.cursor');
|
||||||
|
mcpJsonPath = path.join(mcpJsonDir, 'mcp.json');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.error(`❌ Invalid target: ${target}. Use: project or user`);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create directory if needed
|
||||||
|
mkdirSync(mcpJsonDir, { recursive: true });
|
||||||
|
|
||||||
|
// Load existing config or create new
|
||||||
|
let config: CursorMcpConfig = { mcpServers: {} };
|
||||||
|
if (existsSync(mcpJsonPath)) {
|
||||||
|
try {
|
||||||
|
config = JSON.parse(readFileSync(mcpJsonPath, 'utf-8'));
|
||||||
|
if (!config.mcpServers) {
|
||||||
|
config.mcpServers = {};
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Start fresh if corrupt
|
||||||
|
config = { mcpServers: {} };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add claude-mem MCP server
|
||||||
|
config.mcpServers['claude-mem'] = {
|
||||||
|
command: 'node',
|
||||||
|
args: [mcpServerPath]
|
||||||
|
};
|
||||||
|
|
||||||
|
writeFileSync(mcpJsonPath, JSON.stringify(config, null, 2));
|
||||||
|
console.log(` ✓ Configured MCP server in ${target === 'user' ? '~/.cursor' : '.cursor'}/mcp.json`);
|
||||||
|
console.log(` Server path: ${mcpServerPath}`);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ Failed to configure MCP: ${(error as Error).message}`);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle cursor subcommand for hooks installation
|
* Handle cursor subcommand for hooks installation
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user