Initial import from GitHub
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
import { rm, stat } from 'node:fs/promises';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
import chalk from 'chalk';
|
||||
import ora from 'ora';
|
||||
import prompts from 'prompts';
|
||||
import type { AIType, ConcreteAIType } from '../types/index.js';
|
||||
import { AI_TYPES, AI_FOLDERS } from '../types/index.js';
|
||||
import { detectAIType, getAITypeDescription } from '../utils/detect.js';
|
||||
import { listBundledSubSkills, loadPlatformConfig } from '../utils/template.js';
|
||||
import { logger } from '../utils/logger.js';
|
||||
|
||||
interface UninstallOptions {
|
||||
ai?: AIType;
|
||||
global?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove skill directory for a given AI type
|
||||
*/
|
||||
async function removeSkillDir(baseDir: string, aiType: ConcreteAIType): Promise<string[]> {
|
||||
const removed: string[] = [];
|
||||
|
||||
// The orchestrator plus the bundled sibling sub-skills installed by init.
|
||||
const skillNames = ['ui-ux-pro-max', ...(await listBundledSubSkills())];
|
||||
|
||||
// Parent directories to clean. Derive the real install location from the
|
||||
// platform config's skillPath (same source the installer uses), so
|
||||
// non-`skills/` platforms are handled — copilot installs under
|
||||
// `.github/prompts/`, kiro under `.kiro/steering/`. Also clean the legacy
|
||||
// `<folder>/skills/` layout (incl. `.shared/`) so older installs are removed.
|
||||
const parents = new Set<string>();
|
||||
try {
|
||||
const { folderStructure } = await loadPlatformConfig(aiType);
|
||||
parents.add(join(folderStructure.root, dirname(folderStructure.skillPath)));
|
||||
} catch {
|
||||
// No platform config — fall back to the legacy folders below.
|
||||
}
|
||||
for (const folder of AI_FOLDERS[aiType]) {
|
||||
parents.add(join(folder, 'skills'));
|
||||
}
|
||||
|
||||
for (const parent of parents) {
|
||||
for (const name of skillNames) {
|
||||
const skillDir = join(baseDir, parent, name);
|
||||
try {
|
||||
await stat(skillDir);
|
||||
await rm(skillDir, { recursive: true, force: true });
|
||||
removed.push(`${parent.replaceAll('\\', '/')}/${name}`);
|
||||
} catch (err: unknown) {
|
||||
// Skip non-existent dirs; re-throw permission or other errors
|
||||
if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
export async function uninstallCommand(options: UninstallOptions): Promise<void> {
|
||||
logger.title('UI/UX Pro Max Uninstaller');
|
||||
|
||||
const isGlobal = !!options.global;
|
||||
const baseDir = isGlobal ? homedir() : process.cwd();
|
||||
const locationLabel = isGlobal ? '~/ (global)' : process.cwd();
|
||||
|
||||
let aiType = options.ai;
|
||||
const { detected: initialDetected } = detectAIType(baseDir);
|
||||
|
||||
// Auto-detect or prompt for AI type
|
||||
if (!aiType) {
|
||||
const detected = initialDetected;
|
||||
|
||||
if (detected.length === 0) {
|
||||
logger.warn('No installed AI skill directories detected.');
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(`Detected installations: ${detected.map(t => chalk.cyan(t)).join(', ')}`);
|
||||
|
||||
const choices = [
|
||||
...detected.map(type => ({
|
||||
title: getAITypeDescription(type),
|
||||
value: type,
|
||||
})),
|
||||
{ title: 'All detected', value: 'all' as AIType },
|
||||
];
|
||||
|
||||
const response = await prompts({
|
||||
type: 'select',
|
||||
name: 'aiType',
|
||||
message: 'Select which AI skill to uninstall:',
|
||||
choices,
|
||||
});
|
||||
|
||||
if (!response.aiType) {
|
||||
logger.warn('Uninstall cancelled');
|
||||
return;
|
||||
}
|
||||
|
||||
aiType = response.aiType as AIType;
|
||||
}
|
||||
|
||||
// Confirm before removing
|
||||
const { confirmed } = await prompts({
|
||||
type: 'confirm',
|
||||
name: 'confirmed',
|
||||
message: `Remove UI/UX Pro Max skill for ${chalk.cyan(getAITypeDescription(aiType))} from ${locationLabel}?`,
|
||||
initial: false,
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
logger.warn('Uninstall cancelled');
|
||||
return;
|
||||
}
|
||||
|
||||
const spinner = ora('Removing skill files...').start();
|
||||
|
||||
try {
|
||||
const allRemoved: string[] = [];
|
||||
|
||||
if (aiType === 'all') {
|
||||
// Remove for all detected platforms
|
||||
for (const type of initialDetected) {
|
||||
const removed = await removeSkillDir(baseDir, type);
|
||||
allRemoved.push(...removed);
|
||||
}
|
||||
} else {
|
||||
const removed = await removeSkillDir(baseDir, aiType);
|
||||
allRemoved.push(...removed);
|
||||
}
|
||||
|
||||
if (allRemoved.length === 0) {
|
||||
spinner.warn('No skill files found to remove');
|
||||
return;
|
||||
}
|
||||
|
||||
spinner.succeed('Skill files removed!');
|
||||
|
||||
console.log();
|
||||
logger.info('Removed:');
|
||||
allRemoved.forEach(folder => {
|
||||
console.log(` ${chalk.red('-')} ${folder}`);
|
||||
});
|
||||
|
||||
console.log();
|
||||
logger.success('UI/UX Pro Max uninstalled successfully!');
|
||||
console.log();
|
||||
} catch (error) {
|
||||
spinner.fail('Uninstall failed');
|
||||
if (error instanceof Error) {
|
||||
logger.error(error.message);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user