feat: add npx CLI entry point with install, runtime, and IDE detection commands

Replaces the old git-clone installer with a direct npm package copy workflow.
Supports 13 IDE auto-detection targets, runtime delegation to Bun worker,
and pure Node.js install path (no Bun required for installation).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Alex Newman
2026-02-26 23:45:00 -05:00
parent e2a230286d
commit 85eb796b18
8 changed files with 1320 additions and 0 deletions
+174
View File
@@ -0,0 +1,174 @@
/**
* IDE Auto-Detection
*
* Detects which AI coding IDEs / tools are installed on the system by
* probing known config directories and checking for binaries in PATH.
*
* Pure Node.js — no Bun APIs used.
*/
import { execSync } from 'child_process';
import { existsSync, readdirSync } from 'fs';
import { homedir } from 'os';
import { join } from 'path';
import { IS_WINDOWS } from '../utils/paths.js';
// ---------------------------------------------------------------------------
// IDE type and metadata
// ---------------------------------------------------------------------------
export interface IDEInfo {
/** Machine-readable identifier. */
id: string;
/** Human-readable label for display in prompts. */
label: string;
/** Whether the IDE was detected on this system. */
detected: boolean;
/** Whether claude-mem has implemented setup for this IDE. */
supported: boolean;
/** Short hint text shown in the multi-select. */
hint?: string;
}
// ---------------------------------------------------------------------------
// PATH helper
// ---------------------------------------------------------------------------
function isCommandInPath(command: string): boolean {
try {
const whichCommand = IS_WINDOWS ? 'where' : 'which';
execSync(`${whichCommand} ${command}`, { stdio: 'pipe' });
return true;
} catch {
return false;
}
}
// ---------------------------------------------------------------------------
// VS Code extension directory scanner
// ---------------------------------------------------------------------------
function hasVscodeExtension(extensionNameFragment: string): boolean {
const extensionsDirectory = join(homedir(), '.vscode', 'extensions');
if (!existsSync(extensionsDirectory)) return false;
try {
const entries = readdirSync(extensionsDirectory);
return entries.some((entry) => entry.toLowerCase().includes(extensionNameFragment.toLowerCase()));
} catch {
return false;
}
}
// ---------------------------------------------------------------------------
// Detection map
// ---------------------------------------------------------------------------
/**
* Detect all known IDEs and return an array of `IDEInfo` objects.
* Each entry indicates whether the IDE was found and whether claude-mem
* currently supports setting it up.
*/
export function detectInstalledIDEs(): IDEInfo[] {
const home = homedir();
return [
{
id: 'claude-code',
label: 'Claude Code',
detected: existsSync(join(home, '.claude')),
supported: true,
hint: 'recommended',
},
{
id: 'gemini-cli',
label: 'Gemini CLI',
detected: existsSync(join(home, '.gemini')),
supported: false,
hint: 'coming soon',
},
{
id: 'opencode',
label: 'OpenCode',
detected:
existsSync(join(home, '.config', 'opencode')) || isCommandInPath('opencode'),
supported: false,
hint: 'coming soon',
},
{
id: 'openclaw',
label: 'OpenClaw',
detected: existsSync(join(home, '.openclaw')),
supported: false,
hint: 'coming soon',
},
{
id: 'windsurf',
label: 'Windsurf',
detected: existsSync(join(home, '.codeium', 'windsurf')),
supported: false,
hint: 'coming soon',
},
{
id: 'codex-cli',
label: 'Codex CLI',
detected: existsSync(join(home, '.codex')),
supported: false,
hint: 'coming soon',
},
{
id: 'cursor',
label: 'Cursor',
detected: existsSync(join(home, '.cursor')),
supported: true,
},
{
id: 'copilot-cli',
label: 'Copilot CLI',
detected: isCommandInPath('copilot'),
supported: false,
hint: 'coming soon',
},
{
id: 'antigravity',
label: 'Antigravity',
detected: existsSync(join(home, '.gemini', 'antigravity')),
supported: false,
hint: 'coming soon',
},
{
id: 'goose',
label: 'Goose',
detected:
existsSync(join(home, '.config', 'goose')) || isCommandInPath('goose'),
supported: false,
hint: 'coming soon',
},
{
id: 'crush',
label: 'Crush',
detected: isCommandInPath('crush'),
supported: false,
hint: 'coming soon',
},
{
id: 'roo-code',
label: 'Roo Code',
detected: hasVscodeExtension('roo-code'),
supported: false,
hint: 'coming soon',
},
{
id: 'warp',
label: 'Warp',
detected: existsSync(join(home, '.warp')) || isCommandInPath('warp'),
supported: false,
hint: 'coming soon',
},
];
}
/**
* Return only the IDEs that were detected on this system.
*/
export function getDetectedIDEs(): IDEInfo[] {
return detectInstalledIDEs().filter((ide) => ide.detected);
}
+374
View File
@@ -0,0 +1,374 @@
/**
* Install command for `npx claude-mem install`.
*
* Replaces the git-clone + build workflow. The npm package already ships
* a pre-built `plugin/` directory; this command copies it into the right
* locations and registers it with Claude Code.
*
* Pure Node.js — no Bun APIs used.
*/
import * as p from '@clack/prompts';
import pc from 'picocolors';
import { execSync } from 'child_process';
import { cpSync, existsSync, readFileSync } from 'fs';
import { join } from 'path';
import {
claudeSettingsPath,
ensureDirectoryExists,
installedPluginsPath,
IS_WINDOWS,
knownMarketplacesPath,
marketplaceDirectory,
npmPackagePluginDirectory,
npmPackageRootDirectory,
pluginCacheDirectory,
pluginsDirectory,
readJsonFileSafe,
readPluginVersion,
writeJsonFileAtomic,
} from '../utils/paths.js';
import { detectInstalledIDEs } from './ide-detection.js';
// ---------------------------------------------------------------------------
// Registration helpers (mirror installer/src/steps/install.ts)
// ---------------------------------------------------------------------------
function registerMarketplace(): void {
const knownMarketplaces = readJsonFileSafe(knownMarketplacesPath());
knownMarketplaces['thedotmack'] = {
source: {
source: 'github',
repo: 'thedotmack/claude-mem',
},
installLocation: marketplaceDirectory(),
lastUpdated: new Date().toISOString(),
autoUpdate: true,
};
ensureDirectoryExists(pluginsDirectory());
writeJsonFileAtomic(knownMarketplacesPath(), knownMarketplaces);
}
function registerPlugin(version: string): void {
const installedPlugins = readJsonFileSafe(installedPluginsPath());
if (!installedPlugins.version) installedPlugins.version = 2;
if (!installedPlugins.plugins) installedPlugins.plugins = {};
const cachePath = pluginCacheDirectory(version);
const now = new Date().toISOString();
installedPlugins.plugins['claude-mem@thedotmack'] = [
{
scope: 'user',
installPath: cachePath,
version,
installedAt: now,
lastUpdated: now,
},
];
writeJsonFileAtomic(installedPluginsPath(), installedPlugins);
}
function enablePluginInClaudeSettings(): void {
const settings = readJsonFileSafe(claudeSettingsPath());
if (!settings.enabledPlugins) settings.enabledPlugins = {};
settings.enabledPlugins['claude-mem@thedotmack'] = true;
writeJsonFileAtomic(claudeSettingsPath(), settings);
}
// ---------------------------------------------------------------------------
// IDE setup dispatcher
// ---------------------------------------------------------------------------
function setupIDEs(selectedIDEs: string[]): void {
for (const ideId of selectedIDEs) {
switch (ideId) {
case 'claude-code':
// Claude Code picks up the plugin via marketplace registration — nothing
// else to do beyond what registerMarketplace / registerPlugin already did.
p.log.success('Claude Code: plugin registered via marketplace.');
break;
case 'cursor':
p.log.info('Cursor: hook configuration available after first launch.');
p.log.info(` Run: npx claude-mem cursor-setup (coming soon)`);
break;
default: {
const allIDEs = detectInstalledIDEs();
const ide = allIDEs.find((i) => i.id === ideId);
if (ide && !ide.supported) {
p.log.warn(`Support for ${ide.label} coming soon.`);
}
break;
}
}
}
}
// ---------------------------------------------------------------------------
// Interactive IDE selection
// ---------------------------------------------------------------------------
async function promptForIDESelection(): Promise<string[]> {
const detectedIDEs = detectInstalledIDEs();
const detected = detectedIDEs.filter((ide) => ide.detected);
if (detected.length === 0) {
p.log.warn('No supported IDEs detected. Installing for Claude Code by default.');
return ['claude-code'];
}
const options = detected.map((ide) => ({
value: ide.id,
label: ide.label,
hint: ide.supported ? ide.hint : 'coming soon',
}));
const result = await p.multiselect({
message: 'Which IDEs do you use?',
options,
initialValues: detected
.filter((ide) => ide.supported)
.map((ide) => ide.id),
required: true,
});
if (p.isCancel(result)) {
p.cancel('Installation cancelled.');
process.exit(0);
}
return result as string[];
}
// ---------------------------------------------------------------------------
// Core copy logic
// ---------------------------------------------------------------------------
function copyPluginToMarketplace(): void {
const marketplaceDir = marketplaceDirectory();
ensureDirectoryExists(marketplaceDir);
// Copy the entire npm package (not just plugin/) so that package.json,
// node_modules, and scripts are all present in the marketplace dir.
const packageRoot = npmPackageRootDirectory();
cpSync(packageRoot, marketplaceDir, {
recursive: true,
force: true,
filter: (source) => {
// Skip .git and other unnecessary directories
if (source.includes('.git') && !source.includes('.claude-plugin')) return false;
if (source.endsWith('.tgz')) return false;
return true;
},
});
}
function copyPluginToCache(version: string): void {
const sourcePluginDirectory = npmPackagePluginDirectory();
const cachePath = pluginCacheDirectory(version);
ensureDirectoryExists(cachePath);
cpSync(sourcePluginDirectory, cachePath, { recursive: true, force: true });
}
// ---------------------------------------------------------------------------
// npm install in marketplace dir
// ---------------------------------------------------------------------------
function runNpmInstallInMarketplace(): void {
const marketplaceDir = marketplaceDirectory();
const packageJsonPath = join(marketplaceDir, 'package.json');
if (!existsSync(packageJsonPath)) return;
execSync('npm install --production', {
cwd: marketplaceDir,
stdio: 'pipe',
...(IS_WINDOWS ? { shell: true as const } : {}),
});
}
// ---------------------------------------------------------------------------
// Trigger smart-install for Bun / uv
// ---------------------------------------------------------------------------
function runSmartInstall(): void {
const smartInstallPath = join(marketplaceDirectory(), 'plugin', 'scripts', 'smart-install.js');
if (!existsSync(smartInstallPath)) {
p.log.warn('smart-install.js not found — skipping Bun/uv auto-install.');
return;
}
try {
execSync(`node "${smartInstallPath}"`, {
stdio: 'inherit',
...(IS_WINDOWS ? { shell: true as const } : {}),
});
} catch {
p.log.warn('smart-install encountered an issue. You may need to install Bun/uv manually.');
}
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
export interface InstallOptions {
/** When provided, skip the interactive IDE multi-select and use this IDE. */
ide?: string;
}
export async function runInstallCommand(options: InstallOptions = {}): Promise<void> {
const version = readPluginVersion();
p.intro(pc.bgCyan(pc.black(' claude-mem install ')));
p.log.info(`Version: ${pc.cyan(version)}`);
p.log.info(`Platform: ${process.platform} (${process.arch})`);
// Check for existing installation
const marketplaceDir = marketplaceDirectory();
const alreadyInstalled = existsSync(join(marketplaceDir, 'plugin', '.claude-plugin', 'plugin.json'));
if (alreadyInstalled) {
// Read existing version
try {
const existingPluginJson = JSON.parse(
readFileSync(join(marketplaceDir, 'plugin', '.claude-plugin', 'plugin.json'), 'utf-8'),
);
p.log.warn(`Existing installation detected (v${existingPluginJson.version ?? 'unknown'}).`);
} catch {
p.log.warn('Existing installation detected.');
}
if (process.stdin.isTTY) {
const shouldContinue = await p.confirm({
message: 'Overwrite existing installation?',
initialValue: true,
});
if (p.isCancel(shouldContinue) || !shouldContinue) {
p.cancel('Installation cancelled.');
process.exit(0);
}
}
}
// IDE selection
let selectedIDEs: string[];
if (options.ide) {
selectedIDEs = [options.ide];
const allIDEs = detectInstalledIDEs();
const match = allIDEs.find((i) => i.id === options.ide);
if (match && !match.supported) {
p.log.error(`Support for ${match.label} coming soon.`);
process.exit(1);
}
if (!match) {
p.log.error(`Unknown IDE: ${options.ide}`);
p.log.info(`Available IDEs: ${allIDEs.map((i) => i.id).join(', ')}`);
process.exit(1);
}
} else if (process.stdin.isTTY) {
selectedIDEs = await promptForIDESelection();
} else {
// Non-interactive: default to claude-code
selectedIDEs = ['claude-code'];
}
// Run tasks
await p.tasks([
{
title: 'Copying plugin files',
task: async (message) => {
message('Copying to marketplace directory...');
copyPluginToMarketplace();
return `Plugin files copied ${pc.green('OK')}`;
},
},
{
title: 'Caching plugin version',
task: async (message) => {
message(`Caching v${version}...`);
copyPluginToCache(version);
return `Plugin cached (v${version}) ${pc.green('OK')}`;
},
},
{
title: 'Registering marketplace',
task: async () => {
registerMarketplace();
return `Marketplace registered ${pc.green('OK')}`;
},
},
{
title: 'Registering plugin',
task: async () => {
registerPlugin(version);
return `Plugin registered ${pc.green('OK')}`;
},
},
{
title: 'Enabling plugin in Claude settings',
task: async () => {
enablePluginInClaudeSettings();
return `Plugin enabled ${pc.green('OK')}`;
},
},
{
title: 'Installing dependencies',
task: async (message) => {
message('Running npm install...');
try {
runNpmInstallInMarketplace();
return `Dependencies installed ${pc.green('OK')}`;
} catch {
return `Dependencies may need manual install ${pc.yellow('!')}`;
}
},
},
{
title: 'Setting up Bun and uv',
task: async (message) => {
message('Running smart-install...');
try {
runSmartInstall();
return `Runtime dependencies ready ${pc.green('OK')}`;
} catch {
return `Runtime setup may need attention ${pc.yellow('!')}`;
}
},
},
]);
// IDE-specific setup
setupIDEs(selectedIDEs);
// Summary
const summaryLines = [
`Version: ${pc.cyan(version)}`,
`Plugin dir: ${pc.cyan(marketplaceDir)}`,
`IDEs: ${pc.cyan(selectedIDEs.join(', '))}`,
];
p.note(summaryLines.join('\n'), 'Installation Complete');
const nextSteps = [
'Open Claude Code and start a conversation -- memory is automatic!',
`View your memories: ${pc.underline('http://localhost:37777')}`,
`Search past work: use ${pc.bold('/mem-search')} in Claude Code`,
`Start worker: ${pc.bold('npx claude-mem start')}`,
];
p.note(nextSteps.join('\n'), 'Next Steps');
p.outro(pc.green('claude-mem installed successfully!'));
}
+184
View File
@@ -0,0 +1,184 @@
/**
* Runtime command routing for `npx claude-mem start|stop|restart|status|search|transcript`.
*
* These commands delegate to the installed plugin's worker-service.cjs via Bun,
* or hit the worker's HTTP API directly (for `search`).
*
* Pure Node.js — no Bun APIs used.
*/
import { spawn } from 'child_process';
import { existsSync } from 'fs';
import { join } from 'path';
import pc from 'picocolors';
import { resolveBunBinaryPath } from '../utils/bun-resolver.js';
import { isPluginInstalled, marketplaceDirectory } from '../utils/paths.js';
// ---------------------------------------------------------------------------
// Installation guard
// ---------------------------------------------------------------------------
function ensureInstalledOrExit(): void {
if (!isPluginInstalled()) {
console.error(pc.red('claude-mem is not installed.'));
console.error(`Run: ${pc.bold('npx claude-mem install')}`);
process.exit(1);
}
}
// ---------------------------------------------------------------------------
// Bun guard
// ---------------------------------------------------------------------------
function resolveBunOrExit(): string {
const bunPath = resolveBunBinaryPath();
if (!bunPath) {
console.error(pc.red('Bun not found.'));
console.error('Install Bun: https://bun.sh');
console.error('After installation, restart your terminal.');
process.exit(1);
}
return bunPath;
}
// ---------------------------------------------------------------------------
// Worker-service path
// ---------------------------------------------------------------------------
function workerServiceScriptPath(): string {
return join(marketplaceDirectory(), 'plugin', 'scripts', 'worker-service.cjs');
}
// ---------------------------------------------------------------------------
// Spawn helper
// ---------------------------------------------------------------------------
function spawnBunWorkerCommand(command: string, extraArgs: string[] = []): void {
ensureInstalledOrExit();
const bunPath = resolveBunOrExit();
const workerScript = workerServiceScriptPath();
if (!existsSync(workerScript)) {
console.error(pc.red(`Worker script not found at: ${workerScript}`));
console.error('The installation may be corrupted. Try: npx claude-mem install');
process.exit(1);
}
const args = [workerScript, command, ...extraArgs];
const child = spawn(bunPath, args, {
stdio: 'inherit',
cwd: marketplaceDirectory(),
env: process.env,
});
child.on('error', (error) => {
console.error(pc.red(`Failed to start Bun: ${error.message}`));
process.exit(1);
});
child.on('close', (exitCode) => {
process.exit(exitCode ?? 0);
});
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
export function runStartCommand(): void {
spawnBunWorkerCommand('start');
}
export function runStopCommand(): void {
spawnBunWorkerCommand('stop');
}
export function runRestartCommand(): void {
spawnBunWorkerCommand('restart');
}
export function runStatusCommand(): void {
spawnBunWorkerCommand('status');
}
/**
* Search the worker API at `GET /api/search?q=<query>`.
*/
export async function runSearchCommand(queryParts: string[]): Promise<void> {
ensureInstalledOrExit();
const query = queryParts.join(' ').trim();
if (!query) {
console.error(pc.red('Usage: npx claude-mem search <query>'));
process.exit(1);
}
const workerPort = process.env.CLAUDE_MEM_WORKER_PORT || '37777';
const searchUrl = `http://127.0.0.1:${workerPort}/api/search?q=${encodeURIComponent(query)}`;
try {
const response = await fetch(searchUrl);
if (!response.ok) {
if (response.status === 404) {
console.error(pc.red('Search endpoint not found. Is the worker running?'));
console.error(`Try: ${pc.bold('npx claude-mem start')}`);
process.exit(1);
}
console.error(pc.red(`Search failed: HTTP ${response.status}`));
process.exit(1);
}
const data = await response.json();
if (typeof data === 'object' && data !== null) {
console.log(JSON.stringify(data, null, 2));
} else {
console.log(data);
}
} catch (error: any) {
if (error?.cause?.code === 'ECONNREFUSED' || error?.message?.includes('ECONNREFUSED')) {
console.error(pc.red('Worker is not running.'));
console.error(`Start it with: ${pc.bold('npx claude-mem start')}`);
process.exit(1);
}
console.error(pc.red(`Search failed: ${error.message}`));
process.exit(1);
}
}
/**
* Start the transcript watcher via Bun.
*/
export function runTranscriptWatchCommand(): void {
ensureInstalledOrExit();
const bunPath = resolveBunOrExit();
const transcriptWatcherPath = join(
marketplaceDirectory(),
'plugin',
'scripts',
'transcript-watcher.cjs',
);
if (!existsSync(transcriptWatcherPath)) {
// Fall back to worker-service with transcript subcommand
spawnBunWorkerCommand('transcript', ['watch']);
return;
}
const child = spawn(bunPath, [transcriptWatcherPath, 'watch'], {
stdio: 'inherit',
cwd: marketplaceDirectory(),
env: process.env,
});
child.on('error', (error) => {
console.error(pc.red(`Failed to start transcript watcher: ${error.message}`));
process.exit(1);
});
child.on('close', (exitCode) => {
process.exit(exitCode ?? 0);
});
}
+171
View File
@@ -0,0 +1,171 @@
/**
* Uninstall command for `npx claude-mem uninstall`.
*
* Removes the plugin from the marketplace directory, cache, plugin
* registrations, and Claude settings. Optionally cleans up IDE-specific
* configurations.
*
* Pure Node.js — no Bun APIs used.
*/
import * as p from '@clack/prompts';
import pc from 'picocolors';
import { existsSync, rmSync } from 'fs';
import { join } from 'path';
import {
claudeSettingsPath,
installedPluginsPath,
isPluginInstalled,
knownMarketplacesPath,
marketplaceDirectory,
pluginsDirectory,
readJsonFileSafe,
writeJsonFileAtomic,
} from '../utils/paths.js';
// ---------------------------------------------------------------------------
// Cleanup helpers
// ---------------------------------------------------------------------------
function removeMarketplaceDirectory(): boolean {
const marketplaceDir = marketplaceDirectory();
if (existsSync(marketplaceDir)) {
rmSync(marketplaceDir, { recursive: true, force: true });
return true;
}
return false;
}
function removeCacheDirectory(): boolean {
const cacheBaseDirectory = join(pluginsDirectory(), 'cache', 'thedotmack');
if (existsSync(cacheBaseDirectory)) {
rmSync(cacheBaseDirectory, { recursive: true, force: true });
return true;
}
return false;
}
function removeFromKnownMarketplaces(): void {
const knownMarketplaces = readJsonFileSafe(knownMarketplacesPath());
if (knownMarketplaces['thedotmack']) {
delete knownMarketplaces['thedotmack'];
writeJsonFileAtomic(knownMarketplacesPath(), knownMarketplaces);
}
}
function removeFromInstalledPlugins(): void {
const installedPlugins = readJsonFileSafe(installedPluginsPath());
if (installedPlugins.plugins?.['claude-mem@thedotmack']) {
delete installedPlugins.plugins['claude-mem@thedotmack'];
writeJsonFileAtomic(installedPluginsPath(), installedPlugins);
}
}
function removeFromClaudeSettings(): void {
const settings = readJsonFileSafe(claudeSettingsPath());
if (settings.enabledPlugins?.['claude-mem@thedotmack'] !== undefined) {
delete settings.enabledPlugins['claude-mem@thedotmack'];
writeJsonFileAtomic(claudeSettingsPath(), settings);
}
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
export async function runUninstallCommand(): Promise<void> {
p.intro(pc.bgRed(pc.white(' claude-mem uninstall ')));
if (!isPluginInstalled()) {
p.log.warn('claude-mem does not appear to be installed.');
// Still offer to clean up partial state
if (process.stdin.isTTY) {
const shouldCleanup = await p.confirm({
message: 'Clean up any remaining registration data anyway?',
initialValue: false,
});
if (p.isCancel(shouldCleanup) || !shouldCleanup) {
p.outro('Nothing to do.');
return;
}
} else {
p.outro('Nothing to do.');
return;
}
} else if (process.stdin.isTTY) {
const shouldContinue = await p.confirm({
message: 'Are you sure you want to uninstall claude-mem?',
initialValue: false,
});
if (p.isCancel(shouldContinue) || !shouldContinue) {
p.cancel('Uninstall cancelled.');
return;
}
}
// Stop the worker first (best-effort)
try {
const workerPort = process.env.CLAUDE_MEM_WORKER_PORT || '37777';
await fetch(`http://127.0.0.1:${workerPort}/api/admin/shutdown`, {
method: 'POST',
signal: AbortSignal.timeout(5000),
});
p.log.info('Worker service stopped.');
} catch {
// Worker may not be running — that is fine
}
await p.tasks([
{
title: 'Removing marketplace directory',
task: async () => {
const removed = removeMarketplaceDirectory();
return removed
? `Marketplace directory removed ${pc.green('OK')}`
: `Marketplace directory not found ${pc.dim('skipped')}`;
},
},
{
title: 'Removing cache directory',
task: async () => {
const removed = removeCacheDirectory();
return removed
? `Cache directory removed ${pc.green('OK')}`
: `Cache directory not found ${pc.dim('skipped')}`;
},
},
{
title: 'Removing marketplace registration',
task: async () => {
removeFromKnownMarketplaces();
return `Marketplace registration removed ${pc.green('OK')}`;
},
},
{
title: 'Removing plugin registration',
task: async () => {
removeFromInstalledPlugins();
return `Plugin registration removed ${pc.green('OK')}`;
},
},
{
title: 'Removing from Claude settings',
task: async () => {
removeFromClaudeSettings();
return `Claude settings updated ${pc.green('OK')}`;
},
},
]);
p.note(
[
`Your data directory at ${pc.cyan('~/.claude-mem')} was preserved.`,
'To remove it manually: rm -rf ~/.claude-mem',
].join('\n'),
'Note',
);
p.outro(pc.green('claude-mem has been uninstalled.'));
}