76a880a3d6
Fixes CursorHooksInstaller ESM compatibility, updates install command with improved path resolution, and refreshes built plugin artifacts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
672 lines
21 KiB
TypeScript
672 lines
21 KiB
TypeScript
/**
|
|
* CursorHooksInstaller - Cursor IDE integration for claude-mem
|
|
*
|
|
* Extracted from worker-service.ts monolith to provide centralized Cursor integration.
|
|
* Handles:
|
|
* - Cursor hooks installation/uninstallation
|
|
* - MCP server configuration
|
|
* - Context file generation
|
|
* - Project registry management
|
|
*/
|
|
|
|
import path from 'path';
|
|
import { homedir } from 'os';
|
|
import { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync } from 'fs';
|
|
import { exec } from 'child_process';
|
|
import { promisify } from 'util';
|
|
import { logger } from '../../utils/logger.js';
|
|
import { getWorkerPort, workerHttpRequest } from '../../shared/worker-utils.js';
|
|
import { DATA_DIR, MARKETPLACE_ROOT, CLAUDE_CONFIG_DIR } from '../../shared/paths.js';
|
|
import {
|
|
readCursorRegistry as readCursorRegistryFromFile,
|
|
writeCursorRegistry as writeCursorRegistryToFile,
|
|
writeContextFile,
|
|
type CursorProjectRegistry
|
|
} from '../../utils/cursor-utils.js';
|
|
import type { CursorInstallTarget, CursorHooksJson, CursorMcpConfig, Platform } from './types.js';
|
|
|
|
const execAsync = promisify(exec);
|
|
|
|
// Standard paths
|
|
const CURSOR_REGISTRY_FILE = path.join(DATA_DIR, 'cursor-projects.json');
|
|
|
|
// ============================================================================
|
|
// Platform Detection
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Detect platform for script selection
|
|
*/
|
|
export function detectPlatform(): Platform {
|
|
return process.platform === 'win32' ? 'windows' : 'unix';
|
|
}
|
|
|
|
/**
|
|
* Get script extension based on platform
|
|
*/
|
|
export function getScriptExtension(): string {
|
|
return detectPlatform() === 'windows' ? '.ps1' : '.sh';
|
|
}
|
|
|
|
// ============================================================================
|
|
// Project Registry
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Read the Cursor project registry
|
|
*/
|
|
export function readCursorRegistry(): CursorProjectRegistry {
|
|
return readCursorRegistryFromFile(CURSOR_REGISTRY_FILE);
|
|
}
|
|
|
|
/**
|
|
* Write the Cursor project registry
|
|
*/
|
|
export function writeCursorRegistry(registry: CursorProjectRegistry): void {
|
|
writeCursorRegistryToFile(CURSOR_REGISTRY_FILE, registry);
|
|
}
|
|
|
|
/**
|
|
* Register a project for auto-context updates
|
|
*/
|
|
export function registerCursorProject(projectName: string, workspacePath: string): void {
|
|
const registry = readCursorRegistry();
|
|
registry[projectName] = {
|
|
workspacePath,
|
|
installedAt: new Date().toISOString()
|
|
};
|
|
writeCursorRegistry(registry);
|
|
logger.info('CURSOR', 'Registered project for auto-context updates', { projectName, workspacePath });
|
|
}
|
|
|
|
/**
|
|
* Unregister a project from auto-context updates
|
|
*/
|
|
export function unregisterCursorProject(projectName: string): void {
|
|
const registry = readCursorRegistry();
|
|
if (registry[projectName]) {
|
|
delete registry[projectName];
|
|
writeCursorRegistry(registry);
|
|
logger.info('CURSOR', 'Unregistered project', { projectName });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update Cursor context files for all registered projects matching this project name.
|
|
* Called by SDK agents after saving a summary.
|
|
*/
|
|
export async function updateCursorContextForProject(projectName: string, _port: number): Promise<void> {
|
|
const registry = readCursorRegistry();
|
|
const entry = registry[projectName];
|
|
|
|
if (!entry) return; // Project doesn't have Cursor hooks installed
|
|
|
|
try {
|
|
// Fetch fresh context from worker (uses socket or TCP automatically)
|
|
const response = await workerHttpRequest(
|
|
`/api/context/inject?project=${encodeURIComponent(projectName)}`
|
|
);
|
|
|
|
if (!response.ok) return;
|
|
|
|
const context = await response.text();
|
|
if (!context || !context.trim()) return;
|
|
|
|
// Write to the project's Cursor rules file using shared utility
|
|
writeContextFile(entry.workspacePath, context);
|
|
logger.debug('CURSOR', 'Updated context file', { projectName, workspacePath: entry.workspacePath });
|
|
} catch (error) {
|
|
// [ANTI-PATTERN IGNORED]: Background context update - failure is non-critical, user workflow continues
|
|
logger.error('CURSOR', 'Failed to update context file', { projectName }, error as Error);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Path Finding
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Find MCP server script path
|
|
* Searches in order: marketplace install, source repo
|
|
*/
|
|
export function findMcpServerPath(): string | null {
|
|
const possiblePaths = [
|
|
// Marketplace install location
|
|
path.join(MARKETPLACE_ROOT, 'plugin', 'scripts', 'mcp-server.cjs'),
|
|
// Development/source location
|
|
path.join(process.cwd(), 'plugin', 'scripts', 'mcp-server.cjs'),
|
|
];
|
|
|
|
for (const p of possiblePaths) {
|
|
if (existsSync(p)) {
|
|
return p;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Find worker-service.cjs path for unified CLI
|
|
* Searches in order: marketplace install, source repo
|
|
*/
|
|
export function findWorkerServicePath(): string | null {
|
|
const possiblePaths = [
|
|
// Marketplace install location
|
|
path.join(MARKETPLACE_ROOT, 'plugin', 'scripts', 'worker-service.cjs'),
|
|
// Development/source location
|
|
path.join(process.cwd(), 'plugin', 'scripts', 'worker-service.cjs'),
|
|
];
|
|
|
|
for (const p of possiblePaths) {
|
|
if (existsSync(p)) {
|
|
return p;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Find the Bun executable path
|
|
* Required because worker-service.cjs uses bun:sqlite which is Bun-specific
|
|
* Searches common installation locations across platforms
|
|
*/
|
|
export function findBunPath(): string {
|
|
const possiblePaths = [
|
|
// Standard user install location (most common)
|
|
path.join(homedir(), '.bun', 'bin', 'bun'),
|
|
// Global install locations
|
|
'/usr/local/bin/bun',
|
|
'/usr/bin/bun',
|
|
// Windows locations
|
|
...(process.platform === 'win32' ? [
|
|
path.join(homedir(), '.bun', 'bin', 'bun.exe'),
|
|
path.join(process.env.LOCALAPPDATA || '', 'bun', 'bun.exe'),
|
|
] : []),
|
|
];
|
|
|
|
for (const p of possiblePaths) {
|
|
if (p && existsSync(p)) {
|
|
return p;
|
|
}
|
|
}
|
|
|
|
// Fallback to 'bun' and hope it's in PATH
|
|
// This allows the installation to proceed even if we can't find bun
|
|
// The user will get a clear error when the hook runs if bun isn't available
|
|
return 'bun';
|
|
}
|
|
|
|
/**
|
|
* Get the target directory for Cursor hooks based on install target
|
|
*/
|
|
export function getTargetDir(target: CursorInstallTarget): string | null {
|
|
switch (target) {
|
|
case 'project':
|
|
return path.join(process.cwd(), '.cursor');
|
|
case 'user':
|
|
return path.join(homedir(), '.cursor');
|
|
case 'enterprise':
|
|
if (process.platform === 'darwin') {
|
|
return '/Library/Application Support/Cursor';
|
|
} else if (process.platform === 'linux') {
|
|
return '/etc/cursor';
|
|
} else if (process.platform === 'win32') {
|
|
return path.join(process.env.ProgramData || 'C:\\ProgramData', 'Cursor');
|
|
}
|
|
return null;
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// MCP Configuration
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Configure MCP server in Cursor's mcp.json
|
|
* @param target 'project' or 'user'
|
|
* @returns 0 on success, 1 on failure
|
|
*/
|
|
export function configureCursorMcp(target: CursorInstallTarget): 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;
|
|
}
|
|
|
|
const targetDir = getTargetDir(target);
|
|
if (!targetDir) {
|
|
console.error(`Invalid target: ${target}. Use: project or user`);
|
|
return 1;
|
|
}
|
|
|
|
const mcpJsonPath = path.join(targetDir, 'mcp.json');
|
|
|
|
try {
|
|
// Create directory if needed
|
|
mkdirSync(targetDir, { 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 (error) {
|
|
// [ANTI-PATTERN IGNORED]: Fallback behavior - corrupt config, continue with empty
|
|
logger.error('SYSTEM', 'Corrupt mcp.json, creating new config', { path: mcpJsonPath }, error as Error);
|
|
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;
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Hook Installation
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Install Cursor hooks using unified CLI
|
|
* No longer copies shell scripts - uses node CLI directly
|
|
*/
|
|
export async function installCursorHooks(target: CursorInstallTarget): Promise<number> {
|
|
console.log(`\nInstalling Claude-Mem Cursor hooks (${target} level)...\n`);
|
|
|
|
const targetDir = getTargetDir(target);
|
|
if (!targetDir) {
|
|
console.error(`Invalid target: ${target}. Use: project, user, or enterprise`);
|
|
return 1;
|
|
}
|
|
|
|
// Find the worker-service.cjs path
|
|
const workerServicePath = findWorkerServicePath();
|
|
if (!workerServicePath) {
|
|
console.error('Could not find worker-service.cjs');
|
|
console.error(' Expected at: ~/.claude/plugins/marketplaces/thedotmack/plugin/scripts/worker-service.cjs');
|
|
return 1;
|
|
}
|
|
|
|
const workspaceRoot = process.cwd();
|
|
|
|
try {
|
|
// Create target directory
|
|
mkdirSync(targetDir, { recursive: true });
|
|
|
|
// Generate hooks.json with unified CLI commands
|
|
const hooksJsonPath = path.join(targetDir, 'hooks.json');
|
|
|
|
// Find bun executable - required because worker-service.cjs uses bun:sqlite
|
|
const bunPath = findBunPath();
|
|
const escapedBunPath = bunPath.replace(/\\/g, '\\\\');
|
|
|
|
// Use the absolute path to worker-service.cjs
|
|
// Escape backslashes for JSON on Windows
|
|
const escapedWorkerPath = workerServicePath.replace(/\\/g, '\\\\');
|
|
|
|
// Helper to create hook command using unified CLI with bun runtime
|
|
const makeHookCommand = (command: string) => {
|
|
return `"${escapedBunPath}" "${escapedWorkerPath}" hook cursor ${command}`;
|
|
};
|
|
|
|
console.log(` Using Bun runtime: ${bunPath}`);
|
|
|
|
const hooksJson: CursorHooksJson = {
|
|
version: 1,
|
|
hooks: {
|
|
beforeSubmitPrompt: [
|
|
{ command: makeHookCommand('session-init') },
|
|
{ command: makeHookCommand('context') }
|
|
],
|
|
afterMCPExecution: [
|
|
{ command: makeHookCommand('observation') }
|
|
],
|
|
afterShellExecution: [
|
|
{ command: makeHookCommand('observation') }
|
|
],
|
|
afterFileEdit: [
|
|
{ command: makeHookCommand('file-edit') }
|
|
],
|
|
stop: [
|
|
{ command: makeHookCommand('summarize') }
|
|
]
|
|
}
|
|
};
|
|
|
|
writeFileSync(hooksJsonPath, JSON.stringify(hooksJson, null, 2));
|
|
console.log(` Created hooks.json (unified CLI mode)`);
|
|
console.log(` Worker service: ${workerServicePath}`);
|
|
|
|
// For project-level: create initial context file
|
|
if (target === 'project') {
|
|
await setupProjectContext(targetDir, workspaceRoot);
|
|
}
|
|
|
|
console.log(`
|
|
Installation complete!
|
|
|
|
Hooks installed to: ${targetDir}/hooks.json
|
|
Using unified CLI: bun worker-service.cjs hook cursor <command>
|
|
|
|
Next steps:
|
|
1. Start claude-mem worker: claude-mem start
|
|
2. Restart Cursor to load the hooks
|
|
3. Check Cursor Settings → Hooks tab to verify
|
|
|
|
Context Injection:
|
|
Context from past sessions is stored in .cursor/rules/claude-mem-context.mdc
|
|
and automatically included in every chat. It updates after each session ends.
|
|
`);
|
|
|
|
return 0;
|
|
} catch (error) {
|
|
console.error(`\nInstallation failed: ${(error as Error).message}`);
|
|
if (target === 'enterprise') {
|
|
console.error(' Tip: Enterprise installation may require sudo/admin privileges');
|
|
}
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Setup initial context file for project-level installation
|
|
*/
|
|
async function setupProjectContext(targetDir: string, workspaceRoot: string): Promise<void> {
|
|
const rulesDir = path.join(targetDir, 'rules');
|
|
mkdirSync(rulesDir, { recursive: true });
|
|
|
|
const projectName = path.basename(workspaceRoot);
|
|
let contextGenerated = false;
|
|
|
|
console.log(` Generating initial context...`);
|
|
|
|
try {
|
|
// Check if worker is running (uses socket or TCP automatically)
|
|
const healthResponse = await workerHttpRequest('/api/readiness');
|
|
if (healthResponse.ok) {
|
|
// Fetch context
|
|
const contextResponse = await workerHttpRequest(
|
|
`/api/context/inject?project=${encodeURIComponent(projectName)}`
|
|
);
|
|
if (contextResponse.ok) {
|
|
const context = await contextResponse.text();
|
|
if (context && context.trim()) {
|
|
writeContextFile(workspaceRoot, context);
|
|
contextGenerated = true;
|
|
console.log(` Generated initial context from existing memory`);
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
// [ANTI-PATTERN IGNORED]: Fallback behavior - worker not running, use placeholder
|
|
logger.debug('CURSOR', 'Worker not running during install', {}, error as Error);
|
|
}
|
|
|
|
if (!contextGenerated) {
|
|
// Create placeholder context file
|
|
const rulesFile = path.join(rulesDir, 'claude-mem-context.mdc');
|
|
const placeholderContent = `---
|
|
alwaysApply: true
|
|
description: "Claude-mem context from past sessions (auto-updated)"
|
|
---
|
|
|
|
# Memory Context from Past Sessions
|
|
|
|
*No context yet. Complete your first session and context will appear here.*
|
|
|
|
Use claude-mem's MCP search tools for manual memory queries.
|
|
`;
|
|
writeFileSync(rulesFile, placeholderContent);
|
|
console.log(` Created placeholder context file (will populate after first session)`);
|
|
}
|
|
|
|
// Register project for automatic context updates after summaries
|
|
registerCursorProject(projectName, workspaceRoot);
|
|
console.log(` Registered for auto-context updates`);
|
|
}
|
|
|
|
/**
|
|
* Uninstall Cursor hooks
|
|
*/
|
|
export function uninstallCursorHooks(target: CursorInstallTarget): number {
|
|
console.log(`\nUninstalling Claude-Mem Cursor hooks (${target} level)...\n`);
|
|
|
|
const targetDir = getTargetDir(target);
|
|
if (!targetDir) {
|
|
console.error(`Invalid target: ${target}`);
|
|
return 1;
|
|
}
|
|
|
|
try {
|
|
const hooksDir = path.join(targetDir, 'hooks');
|
|
const hooksJsonPath = path.join(targetDir, 'hooks.json');
|
|
|
|
// Remove legacy shell scripts if they exist (from old installations)
|
|
const bashScripts = ['common.sh', 'session-init.sh', 'context-inject.sh',
|
|
'save-observation.sh', 'save-file-edit.sh', 'session-summary.sh'];
|
|
const psScripts = ['common.ps1', 'session-init.ps1', 'context-inject.ps1',
|
|
'save-observation.ps1', 'save-file-edit.ps1', 'session-summary.ps1'];
|
|
|
|
const allScripts = [...bashScripts, ...psScripts];
|
|
|
|
for (const script of allScripts) {
|
|
const scriptPath = path.join(hooksDir, script);
|
|
if (existsSync(scriptPath)) {
|
|
unlinkSync(scriptPath);
|
|
console.log(` Removed legacy script: ${script}`);
|
|
}
|
|
}
|
|
|
|
// Remove hooks.json
|
|
if (existsSync(hooksJsonPath)) {
|
|
unlinkSync(hooksJsonPath);
|
|
console.log(` Removed hooks.json`);
|
|
}
|
|
|
|
// Remove context file and unregister if project-level
|
|
if (target === 'project') {
|
|
const contextFile = path.join(targetDir, 'rules', 'claude-mem-context.mdc');
|
|
if (existsSync(contextFile)) {
|
|
unlinkSync(contextFile);
|
|
console.log(` Removed context file`);
|
|
}
|
|
|
|
// Unregister from auto-context updates
|
|
const projectName = path.basename(process.cwd());
|
|
unregisterCursorProject(projectName);
|
|
console.log(` Unregistered from auto-context updates`);
|
|
}
|
|
|
|
console.log(`\nUninstallation complete!\n`);
|
|
console.log('Restart Cursor to apply changes.');
|
|
|
|
return 0;
|
|
} catch (error) {
|
|
console.error(`\nUninstallation failed: ${(error as Error).message}`);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check Cursor hooks installation status
|
|
*/
|
|
export function checkCursorHooksStatus(): number {
|
|
console.log('\nClaude-Mem Cursor Hooks Status\n');
|
|
|
|
const locations: Array<{ name: string; dir: string }> = [
|
|
{ name: 'Project', dir: path.join(process.cwd(), '.cursor') },
|
|
{ name: 'User', dir: path.join(homedir(), '.cursor') },
|
|
];
|
|
|
|
if (process.platform === 'darwin') {
|
|
locations.push({ name: 'Enterprise', dir: '/Library/Application Support/Cursor' });
|
|
} else if (process.platform === 'linux') {
|
|
locations.push({ name: 'Enterprise', dir: '/etc/cursor' });
|
|
}
|
|
|
|
let anyInstalled = false;
|
|
|
|
for (const loc of locations) {
|
|
const hooksJson = path.join(loc.dir, 'hooks.json');
|
|
const hooksDir = path.join(loc.dir, 'hooks');
|
|
|
|
if (existsSync(hooksJson)) {
|
|
anyInstalled = true;
|
|
console.log(`${loc.name}: Installed`);
|
|
console.log(` Config: ${hooksJson}`);
|
|
|
|
// Check if using unified CLI mode or legacy shell scripts
|
|
try {
|
|
const hooksContent = JSON.parse(readFileSync(hooksJson, 'utf-8'));
|
|
const firstCommand = hooksContent?.hooks?.beforeSubmitPrompt?.[0]?.command || '';
|
|
|
|
if (firstCommand.includes('worker-service.cjs') && firstCommand.includes('hook cursor')) {
|
|
console.log(` Mode: Unified CLI (bun worker-service.cjs)`);
|
|
} else {
|
|
// Detect legacy shell scripts
|
|
const bashScripts = ['session-init.sh', 'context-inject.sh', 'save-observation.sh'];
|
|
const psScripts = ['session-init.ps1', 'context-inject.ps1', 'save-observation.ps1'];
|
|
|
|
const hasBash = bashScripts.some(s => existsSync(path.join(hooksDir, s)));
|
|
const hasPs = psScripts.some(s => existsSync(path.join(hooksDir, s)));
|
|
|
|
if (hasBash || hasPs) {
|
|
console.log(` Mode: Legacy shell scripts (consider reinstalling for unified CLI)`);
|
|
if (hasBash && hasPs) {
|
|
console.log(` Platform: Both (bash + PowerShell)`);
|
|
} else if (hasBash) {
|
|
console.log(` Platform: Unix (bash)`);
|
|
} else if (hasPs) {
|
|
console.log(` Platform: Windows (PowerShell)`);
|
|
}
|
|
} else {
|
|
console.log(` Mode: Unknown configuration`);
|
|
}
|
|
}
|
|
} catch {
|
|
console.log(` Mode: Unable to parse hooks.json`);
|
|
}
|
|
|
|
// Check for context file (project only)
|
|
if (loc.name === 'Project') {
|
|
const contextFile = path.join(loc.dir, 'rules', 'claude-mem-context.mdc');
|
|
if (existsSync(contextFile)) {
|
|
console.log(` Context: Active`);
|
|
} else {
|
|
console.log(` Context: Not yet generated (will be created on first prompt)`);
|
|
}
|
|
}
|
|
} else {
|
|
console.log(`${loc.name}: Not installed`);
|
|
}
|
|
console.log('');
|
|
}
|
|
|
|
if (!anyInstalled) {
|
|
console.log('No hooks installed. Run: claude-mem cursor install\n');
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Detect if Claude Code is available
|
|
* Checks for the Claude Code CLI and plugin directory
|
|
*/
|
|
export async function detectClaudeCode(): Promise<boolean> {
|
|
try {
|
|
// Check for Claude Code CLI
|
|
const { stdout } = await execAsync('which claude || where claude', { timeout: 5000 });
|
|
if (stdout.trim()) {
|
|
return true;
|
|
}
|
|
} catch (error) {
|
|
// [ANTI-PATTERN IGNORED]: Fallback behavior - CLI not found, continue to directory check
|
|
logger.debug('SYSTEM', 'Claude CLI not in PATH', {}, error as Error);
|
|
}
|
|
|
|
// Check for Claude Code plugin directory (respects CLAUDE_CONFIG_DIR)
|
|
const pluginDir = path.join(CLAUDE_CONFIG_DIR, 'plugins');
|
|
if (existsSync(pluginDir)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Handle cursor subcommand for hooks installation
|
|
*/
|
|
export async function handleCursorCommand(subcommand: string, args: string[]): Promise<number> {
|
|
switch (subcommand) {
|
|
case 'install': {
|
|
const target = (args[0] || 'project') as CursorInstallTarget;
|
|
return installCursorHooks(target);
|
|
}
|
|
|
|
case 'uninstall': {
|
|
const target = (args[0] || 'project') as CursorInstallTarget;
|
|
return uninstallCursorHooks(target);
|
|
}
|
|
|
|
case 'status': {
|
|
return checkCursorHooksStatus();
|
|
}
|
|
|
|
case 'setup': {
|
|
// Interactive guided setup - handled by main() in worker-service.ts
|
|
// This is a placeholder that should not be reached
|
|
console.log('Use the main entry point for setup');
|
|
return 0;
|
|
}
|
|
|
|
default: {
|
|
console.log(`
|
|
Claude-Mem Cursor Integration
|
|
|
|
Usage: claude-mem cursor <command> [options]
|
|
|
|
Commands:
|
|
setup Interactive guided setup (recommended for first-time users)
|
|
|
|
install [target] Install Cursor hooks
|
|
target: project (default), user, or enterprise
|
|
|
|
uninstall [target] Remove Cursor hooks
|
|
target: project (default), user, or enterprise
|
|
|
|
status Check installation status
|
|
|
|
Examples:
|
|
npm run cursor:setup # Interactive wizard (recommended)
|
|
npm run cursor:install # Install for current project
|
|
claude-mem cursor install user # Install globally for user
|
|
claude-mem cursor uninstall # Remove from current project
|
|
claude-mem cursor status # Check if hooks are installed
|
|
|
|
For more info: https://docs.claude-mem.ai/cursor
|
|
`);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|