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:
@@ -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.'));
|
||||
}
|
||||
Reference in New Issue
Block a user