diff --git a/bin/superpowers-zh.js b/bin/superpowers-zh.js index 4727353..5c5d75b 100755 --- a/bin/superpowers-zh.js +++ b/bin/superpowers-zh.js @@ -1,8 +1,9 @@ #!/usr/bin/env node -import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, copyFileSync, lstatSync, realpathSync } from 'fs'; +import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, copyFileSync, lstatSync, realpathSync, rmSync } from 'fs'; import { resolve, dirname, join } from 'path'; import { fileURLToPath } from 'url'; +import { homedir } from 'os'; // 手动递归复制:跨 Node 版本和操作系统行为一致 // 不使用 cpSync —— 在 Windows + npx 缓存(含 junction)+ Node 16.7-18 下不稳定 @@ -14,6 +15,7 @@ function copyDirSync(src, dest) { mkdirSync(dest, { recursive: true }); const entries = readdirSync(realSrc, { withFileTypes: true }); for (const entry of entries) { + if (entry.name === '.DS_Store') continue; const srcPath = join(realSrc, entry.name); const destPath = join(dest, entry.name); let stat; @@ -380,6 +382,8 @@ function showHelp() { 用法: npx superpowers-zh 自动检测工具并安装 npx superpowers-zh --tool cursor 指定工具安装(检测不到时使用) + npx superpowers-zh --uninstall 卸载当前目录下的 superpowers-zh + npx superpowers-zh --force 允许在用户主目录(~)安装(默认拒绝) npx superpowers-zh --help 显示帮助 npx superpowers-zh --version 显示版本 @@ -392,6 +396,9 @@ function showHelp() { npx superpowers-zh --tool cursor npx superpowers-zh --tool trae + 误装到主目录可以这样清理: + cd ~ && npx superpowers-zh --uninstall + 项目:https://github.com/jnMetaCode/superpowers-zh `); } @@ -401,8 +408,8 @@ function installForTarget(target) { const srcCount = countDirs(SKILLS_SRC); mkdirSync(dest, { recursive: true }); copyDirSync(SKILLS_SRC, dest); - const count = countDirs(dest); - if (srcCount > 0 && count === 0) { + const totalAfter = countDirs(dest); + if (srcCount > 0 && totalAfter === 0) { throw new Error( `复制 skills 失败:源目录 ${SKILLS_SRC} 有 ${srcCount} 个 skill,但目标 ${dest} 为空。` + `\n 这通常是 npx 缓存目录权限或路径问题。请尝试:\n` + @@ -411,7 +418,7 @@ function installForTarget(target) { ` 3. 或手动克隆复制: 见 https://github.com/jnMetaCode/superpowers-zh#方式二手动安装` ); } - console.log(` ✅ ${target.name}: ${count} 个 skills -> ${dest}`); + console.log(` ✅ ${target.name}: ${srcCount} 个 skills -> ${dest}`); if (target.name === 'Trae') { generateTraeBootstrapRule(PROJECT_DIR); @@ -443,7 +450,139 @@ function installForTarget(target) { } } -function install(forceToolName) { +function isHomeDir(p) { + try { + const a = realpathSync(p); + const b = realpathSync(homedir()); + return a === b; + } catch { return resolve(p) === resolve(homedir()); } +} + +// 卸载支持:完整删除的 bootstrap 文件、需要清理段落的 bootstrap 文件 +const BOOTSTRAP_DELETE = [ + '.trae/rules/superpowers-zh.md', + '.antigravity/rules.md', +]; +const BOOTSTRAP_CLEAN_SECTION = [ + 'CLAUDE.md', + 'GEMINI.md', + 'HERMES.md', + 'CONVENTIONS.md', +]; +const BOOTSTRAP_SECTION_MARKERS = [ + '# Superpowers-ZH 中文增强版', + '# Superpowers-ZH 工作方法论', +]; + +function cleanBootstrapSection(filePath) { + if (!existsSync(filePath)) return false; + const content = readFileSync(filePath, 'utf8'); + let idx = -1; + for (const marker of BOOTSTRAP_SECTION_MARKERS) { + const i = content.indexOf(marker); + if (i !== -1 && (idx === -1 || i < idx)) idx = i; + } + if (idx === -1) return false; + // 段落结束:下一个一级标题(行首 "# ")或文件末尾 + let end = content.length; + const after = content.indexOf('\n# ', idx + 1); + if (after !== -1) end = after + 1; + const cleaned = (content.slice(0, idx) + content.slice(end)).replace(/\s+$/, '') + '\n'; + if (cleaned.trim().length === 0) { + rmSync(filePath); + } else { + writeFileSync(filePath, cleaned, 'utf8'); + } + return true; +} + +function uninstallForTarget(target, srcSkillNames) { + const dest = resolve(PROJECT_DIR, target.dir); + if (!existsSync(dest)) return 0; + let removed = 0; + for (const entry of readdirSync(dest, { withFileTypes: true })) { + if (entry.isDirectory() && srcSkillNames.has(entry.name)) { + rmSync(resolve(dest, entry.name), { recursive: true, force: true }); + removed++; + } + } + // 如果目录已空(或仅剩 .DS_Store),顺手清掉,避免留下空骨架 + try { + if (existsSync(dest)) { + const left = readdirSync(dest).filter(n => n !== '.DS_Store'); + if (left.length === 0) rmSync(dest, { recursive: true, force: true }); + } + } catch {} + return removed; +} + +function uninstall() { + console.log(`\n superpowers-zh v${PKG.version} — 卸载\n`); + console.log(` 目标项目: ${PROJECT_DIR}\n`); + + if (!existsSync(SKILLS_SRC)) { + console.error(' ❌ 错误:skills 源目录不存在,无法识别要卸载的 skill 名单。'); + process.exit(1); + } + + const srcSkillNames = new Set( + readdirSync(SKILLS_SRC, { withFileTypes: true }) + .filter(e => e.isDirectory()) + .map(e => e.name) + ); + + let totalSkills = 0; + for (const target of TARGETS) { + const removed = uninstallForTarget(target, srcSkillNames); + if (removed > 0) { + console.log(` ✅ ${target.name}: 移除 ${removed} 个 skills <- ${resolve(PROJECT_DIR, target.dir)}`); + totalSkills += removed; + } + } + + // 顺带清理 .claude/agents 下我们装过的 agent(agents 源是平铺的文件,例如 code-reviewer.md) + const agentsDest = resolve(PROJECT_DIR, '.claude', 'agents'); + if (existsSync(AGENTS_SRC) && existsSync(agentsDest)) { + const srcAgentNames = new Set(readdirSync(AGENTS_SRC).filter(n => n !== '.DS_Store')); + let agentsRemoved = 0; + for (const entry of readdirSync(agentsDest)) { + if (srcAgentNames.has(entry)) { + rmSync(resolve(agentsDest, entry), { recursive: true, force: true }); + agentsRemoved++; + } + } + if (agentsRemoved > 0) console.log(` ✅ Claude Code agents: 移除 ${agentsRemoved} 个 -> ${agentsDest}`); + try { + const left = readdirSync(agentsDest).filter(n => n !== '.DS_Store'); + if (left.length === 0) rmSync(agentsDest, { recursive: true, force: true }); + } catch {} + } + + let bootstrapsRemoved = 0; + for (const rel of BOOTSTRAP_DELETE) { + const full = resolve(PROJECT_DIR, rel); + if (existsSync(full)) { + rmSync(full); + console.log(` ✅ 删除 bootstrap: ${full}`); + bootstrapsRemoved++; + } + } + for (const rel of BOOTSTRAP_CLEAN_SECTION) { + const full = resolve(PROJECT_DIR, rel); + if (cleanBootstrapSection(full)) { + console.log(` ✅ 清理 bootstrap: ${full}`); + bootstrapsRemoved++; + } + } + + if (totalSkills === 0 && bootstrapsRemoved === 0) { + console.log(' ⚠️ 未在当前目录找到 superpowers-zh 安装痕迹。'); + } else { + console.log(`\n 卸载完成。共移除 ${totalSkills} 个 skill 目录、${bootstrapsRemoved} 个 bootstrap 文件。\n`); + } +} + +function install(forceToolName, force) { try { console.log(`\n superpowers-zh v${PKG.version} — AI 编程超能力中文版\n`); @@ -452,6 +591,27 @@ function install(forceToolName) { process.exit(1); } + if (!force && isHomeDir(PROJECT_DIR)) { + console.error( +` ⚠️ 当前目录是用户主目录: ${PROJECT_DIR} + + superpowers-zh 应该装到具体项目目录,而不是 ~/。 + 在主目录安装会把 skills 和 bootstrap 文件(CLAUDE.md / HERMES.md 等) + 写入你的 home,污染所有项目。 + + 请先 cd 到项目目录: + cd /path/to/your/project + npx superpowers-zh + + 如果你确实要在主目录安装(不推荐),加 --force: + npx superpowers-zh --force + + 如果你已经在主目录误装过,可以用 --uninstall 清理: + npx superpowers-zh --uninstall +`); + process.exit(1); + } + console.log(` 源: ${countDirs(SKILLS_SRC)} 个 skills`); console.log(` 目标项目: ${PROJECT_DIR}\n`); @@ -512,11 +672,16 @@ const args = process.argv.slice(2); const helpIdx = args.findIndex(a => a === '--help' || a === '-h'); const versionIdx = args.findIndex(a => a === '--version' || a === '-v'); const toolIdx = args.findIndex(a => a === '--tool' || a === '-t'); +const uninstallIdx = args.findIndex(a => a === '--uninstall' || a === '-u'); +const forceIdx = args.findIndex(a => a === '--force' || a === '-f'); +const force = forceIdx !== -1; if (helpIdx !== -1) { showHelp(); } else if (versionIdx !== -1) { console.log(PKG.version); +} else if (uninstallIdx !== -1) { + uninstall(); } else if (toolIdx !== -1) { const toolArg = args[toolIdx + 1]; if (!toolArg) { @@ -530,11 +695,11 @@ if (helpIdx !== -1) { console.error(` 支持的工具: ${Object.keys(TOOL_ALIASES).join(', ')}\n`); process.exit(1); } - install(toolName); -} else if (args.length > 0 && args[0].startsWith('-')) { + install(toolName, force); +} else if (args.length > 0 && args[0].startsWith('-') && forceIdx === -1) { console.warn(` 未知参数: ${args[0]}\n`); showHelp(); process.exit(1); } else { - install(); + install(undefined, force); }