mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-01 14:43:28 +08:00
feat(install): add CodeBuddy(Tencent) adaptation with installation scripts (#1038)
* feat(install): add CodeBuddy(Tencent) adaptation with installation scripts * fix: add codebuddy to SUPPORTED_INSTALL_TARGETS * fix(codebuddy): resolve installer path issues, unused vars, and uninstall safety
This commit is contained in:
98
.codebuddy/README.md
Normal file
98
.codebuddy/README.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# Everything Claude Code for CodeBuddy
|
||||
|
||||
Bring Everything Claude Code (ECC) workflows to CodeBuddy IDE. This repository provides custom commands, agents, skills, and rules that can be installed into any CodeBuddy project using the unified Target Adapter architecture.
|
||||
|
||||
## Quick Start (Recommended)
|
||||
|
||||
Use the unified install system for full lifecycle management:
|
||||
|
||||
```bash
|
||||
# Install with default profile
|
||||
node scripts/install-apply.js --target codebuddy --profile developer
|
||||
|
||||
# Install with full profile (all modules)
|
||||
node scripts/install-apply.js --target codebuddy --profile full
|
||||
|
||||
# Dry-run to preview changes
|
||||
node scripts/install-apply.js --target codebuddy --profile full --dry-run
|
||||
```
|
||||
|
||||
## Management Commands
|
||||
|
||||
```bash
|
||||
# Check installation health
|
||||
node scripts/doctor.js --target codebuddy
|
||||
|
||||
# Repair installation
|
||||
node scripts/repair.js --target codebuddy
|
||||
|
||||
# Uninstall cleanly (tracked via install-state)
|
||||
node scripts/uninstall.js --target codebuddy
|
||||
```
|
||||
|
||||
## Shell Script (Legacy)
|
||||
|
||||
The legacy shell scripts are still available for quick setup:
|
||||
|
||||
```bash
|
||||
# Install to current project
|
||||
cd /path/to/your/project
|
||||
.codebuddy/install.sh
|
||||
|
||||
# Install globally
|
||||
.codebuddy/install.sh ~
|
||||
```
|
||||
|
||||
## What's Included
|
||||
|
||||
### Commands
|
||||
|
||||
Commands are on-demand workflows invocable via the `/` menu in CodeBuddy chat. All commands are reused directly from the project root's `commands/` folder.
|
||||
|
||||
### Agents
|
||||
|
||||
Agents are specialized AI assistants with specific tool configurations. All agents are reused directly from the project root's `agents/` folder.
|
||||
|
||||
### Skills
|
||||
|
||||
Skills are on-demand workflows invocable via the `/` menu in chat. All skills are reused directly from the project's `skills/` folder.
|
||||
|
||||
### Rules
|
||||
|
||||
Rules provide always-on rules and context that shape how the agent works with your code. Rules are flattened into namespaced files (e.g., `common-coding-style.md`) for CodeBuddy compatibility.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
.codebuddy/
|
||||
├── commands/ # Command files (reused from project root)
|
||||
├── agents/ # Agent files (reused from project root)
|
||||
├── skills/ # Skill files (reused from skills/)
|
||||
├── rules/ # Rule files (flattened from rules/)
|
||||
├── ecc-install-state.json # Install state tracking
|
||||
├── install.sh # Legacy install script
|
||||
├── uninstall.sh # Legacy uninstall script
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Benefits of Target Adapter Install
|
||||
|
||||
- **Install-state tracking**: Safe uninstall that only removes ECC-managed files
|
||||
- **Doctor checks**: Verify installation health and detect drift
|
||||
- **Repair**: Auto-fix broken installations
|
||||
- **Selective install**: Choose specific modules via profiles
|
||||
- **Cross-platform**: Node.js-based, works on Windows/macOS/Linux
|
||||
|
||||
## Recommended Workflow
|
||||
|
||||
1. **Start with planning**: Use `/plan` command to break down complex features
|
||||
2. **Write tests first**: Invoke `/tdd` command before implementing
|
||||
3. **Review your code**: Use `/code-review` after writing code
|
||||
4. **Check security**: Use `/code-review` again for auth, API endpoints, or sensitive data handling
|
||||
5. **Fix build errors**: Use `/build-fix` if there are build errors
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Open your project in CodeBuddy
|
||||
- Type `/` to see available commands
|
||||
- Enjoy the ECC workflows!
|
||||
98
.codebuddy/README.zh-CN.md
Normal file
98
.codebuddy/README.zh-CN.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# Everything Claude Code for CodeBuddy
|
||||
|
||||
为 CodeBuddy IDE 带来 Everything Claude Code (ECC) 工作流。此仓库提供自定义命令、智能体、技能和规则,可以通过统一的 Target Adapter 架构安装到任何 CodeBuddy 项目中。
|
||||
|
||||
## 快速开始(推荐)
|
||||
|
||||
使用统一安装系统,获得完整的生命周期管理:
|
||||
|
||||
```bash
|
||||
# 使用默认配置安装
|
||||
node scripts/install-apply.js --target codebuddy --profile developer
|
||||
|
||||
# 使用完整配置安装(所有模块)
|
||||
node scripts/install-apply.js --target codebuddy --profile full
|
||||
|
||||
# 预览模式查看变更
|
||||
node scripts/install-apply.js --target codebuddy --profile full --dry-run
|
||||
```
|
||||
|
||||
## 管理命令
|
||||
|
||||
```bash
|
||||
# 检查安装健康状态
|
||||
node scripts/doctor.js --target codebuddy
|
||||
|
||||
# 修复安装
|
||||
node scripts/repair.js --target codebuddy
|
||||
|
||||
# 清洁卸载(通过 install-state 跟踪)
|
||||
node scripts/uninstall.js --target codebuddy
|
||||
```
|
||||
|
||||
## Shell 脚本(旧版)
|
||||
|
||||
旧版 Shell 脚本仍然可用于快速设置:
|
||||
|
||||
```bash
|
||||
# 安装到当前项目
|
||||
cd /path/to/your/project
|
||||
.codebuddy/install.sh
|
||||
|
||||
# 全局安装
|
||||
.codebuddy/install.sh ~
|
||||
```
|
||||
|
||||
## 包含的内容
|
||||
|
||||
### 命令
|
||||
|
||||
命令是通过 CodeBuddy 聊天中的 `/` 菜单调用的按需工作流。所有命令都直接复用自项目根目录的 `commands/` 文件夹。
|
||||
|
||||
### 智能体
|
||||
|
||||
智能体是具有特定工具配置的专门 AI 助手。所有智能体都直接复用自项目根目录的 `agents/` 文件夹。
|
||||
|
||||
### 技能
|
||||
|
||||
技能是通过聊天中的 `/` 菜单调用的按需工作流。所有技能都直接复用自项目的 `skills/` 文件夹。
|
||||
|
||||
### 规则
|
||||
|
||||
规则提供始终适用的规则和上下文,塑造智能体处理代码的方式。规则会被扁平化为命名空间文件(如 `common-coding-style.md`)以兼容 CodeBuddy。
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
.codebuddy/
|
||||
├── commands/ # 命令文件(复用自项目根目录)
|
||||
├── agents/ # 智能体文件(复用自项目根目录)
|
||||
├── skills/ # 技能文件(复用自 skills/)
|
||||
├── rules/ # 规则文件(从 rules/ 扁平化)
|
||||
├── ecc-install-state.json # 安装状态跟踪
|
||||
├── install.sh # 旧版安装脚本
|
||||
├── uninstall.sh # 旧版卸载脚本
|
||||
└── README.zh-CN.md # 此文件
|
||||
```
|
||||
|
||||
## Target Adapter 安装的优势
|
||||
|
||||
- **安装状态跟踪**:安全卸载,仅删除 ECC 管理的文件
|
||||
- **Doctor 检查**:验证安装健康状态并检测偏移
|
||||
- **修复**:自动修复损坏的安装
|
||||
- **选择性安装**:通过配置文件选择特定模块
|
||||
- **跨平台**:基于 Node.js,支持 Windows/macOS/Linux
|
||||
|
||||
## 推荐的工作流
|
||||
|
||||
1. **从计划开始**:使用 `/plan` 命令分解复杂功能
|
||||
2. **先写测试**:在实现之前调用 `/tdd` 命令
|
||||
3. **审查您的代码**:编写代码后使用 `/code-review`
|
||||
4. **检查安全性**:对于身份验证、API 端点或敏感数据处理,再次使用 `/code-review`
|
||||
5. **修复构建错误**:如果有构建错误,使用 `/build-fix`
|
||||
|
||||
## 下一步
|
||||
|
||||
- 在 CodeBuddy 中打开您的项目
|
||||
- 输入 `/` 以查看可用命令
|
||||
- 享受 ECC 工作流!
|
||||
312
.codebuddy/install.js
Executable file
312
.codebuddy/install.js
Executable file
@@ -0,0 +1,312 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* ECC CodeBuddy Installer (Cross-platform Node.js version)
|
||||
* Installs Everything Claude Code workflows into a CodeBuddy project.
|
||||
*
|
||||
* Usage:
|
||||
* node install.js # Install to current directory
|
||||
* node install.js ~ # Install globally to ~/.codebuddy/
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
// Platform detection
|
||||
const isWindows = process.platform === 'win32';
|
||||
|
||||
/**
|
||||
* Get home directory cross-platform
|
||||
*/
|
||||
function getHomeDir() {
|
||||
return process.env.USERPROFILE || process.env.HOME || os.homedir();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure directory exists
|
||||
*/
|
||||
function ensureDir(dirPath) {
|
||||
try {
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.code !== 'EEXIST') {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read lines from a file
|
||||
*/
|
||||
function readLines(filePath) {
|
||||
try {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return [];
|
||||
}
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
return content.split('\n').filter(line => line.length > 0);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if manifest contains an entry
|
||||
*/
|
||||
function manifestHasEntry(manifestPath, entry) {
|
||||
const lines = readLines(manifestPath);
|
||||
return lines.includes(entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add entry to manifest
|
||||
*/
|
||||
function ensureManifestEntry(manifestPath, entry) {
|
||||
try {
|
||||
const lines = readLines(manifestPath);
|
||||
if (!lines.includes(entry)) {
|
||||
const content = lines.join('\n') + (lines.length > 0 ? '\n' : '') + entry + '\n';
|
||||
fs.writeFileSync(manifestPath, content, 'utf8');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Error updating manifest: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a file and manage in manifest
|
||||
*/
|
||||
function copyManagedFile(sourcePath, targetPath, manifestPath, manifestEntry, makeExecutable = false) {
|
||||
const alreadyManaged = manifestHasEntry(manifestPath, manifestEntry);
|
||||
|
||||
// If target file already exists
|
||||
if (fs.existsSync(targetPath)) {
|
||||
if (alreadyManaged) {
|
||||
ensureManifestEntry(manifestPath, manifestEntry);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy the file
|
||||
try {
|
||||
ensureDir(path.dirname(targetPath));
|
||||
fs.copyFileSync(sourcePath, targetPath);
|
||||
|
||||
// Make executable on Unix systems
|
||||
if (makeExecutable && !isWindows) {
|
||||
fs.chmodSync(targetPath, 0o755);
|
||||
}
|
||||
|
||||
ensureManifestEntry(manifestPath, manifestEntry);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error(`Error copying ${sourcePath}: ${err.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively find files in a directory
|
||||
*/
|
||||
function findFiles(dir, extension = '') {
|
||||
const results = [];
|
||||
try {
|
||||
if (!fs.existsSync(dir)) {
|
||||
return results;
|
||||
}
|
||||
|
||||
function walk(currentPath) {
|
||||
try {
|
||||
const entries = fs.readdirSync(currentPath, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(currentPath, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
walk(fullPath);
|
||||
} else if (!extension || entry.name.endsWith(extension)) {
|
||||
results.push(fullPath);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore permission errors
|
||||
}
|
||||
}
|
||||
|
||||
walk(dir);
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
return results.sort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Main install function
|
||||
*/
|
||||
function doInstall() {
|
||||
// Resolve script directory (where this file lives)
|
||||
const scriptDir = path.dirname(path.resolve(__filename));
|
||||
const repoRoot = path.dirname(scriptDir);
|
||||
const codebuddyDirName = '.codebuddy';
|
||||
|
||||
// Parse arguments
|
||||
let targetDir = process.cwd();
|
||||
if (process.argv.length > 2) {
|
||||
const arg = process.argv[2];
|
||||
if (arg === '~' || arg === getHomeDir()) {
|
||||
targetDir = getHomeDir();
|
||||
} else {
|
||||
targetDir = path.resolve(arg);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine codebuddy full path
|
||||
let codebuddyFullPath;
|
||||
const baseName = path.basename(targetDir);
|
||||
|
||||
if (baseName === codebuddyDirName) {
|
||||
codebuddyFullPath = targetDir;
|
||||
} else {
|
||||
codebuddyFullPath = path.join(targetDir, codebuddyDirName);
|
||||
}
|
||||
|
||||
console.log('ECC CodeBuddy Installer');
|
||||
console.log('=======================');
|
||||
console.log('');
|
||||
console.log(`Source: ${repoRoot}`);
|
||||
console.log(`Target: ${codebuddyFullPath}/`);
|
||||
console.log('');
|
||||
|
||||
// Create subdirectories
|
||||
const subdirs = ['commands', 'agents', 'skills', 'rules'];
|
||||
for (const dir of subdirs) {
|
||||
ensureDir(path.join(codebuddyFullPath, dir));
|
||||
}
|
||||
|
||||
// Manifest file
|
||||
const manifest = path.join(codebuddyFullPath, '.ecc-manifest');
|
||||
ensureDir(path.dirname(manifest));
|
||||
|
||||
// Counters
|
||||
let commands = 0;
|
||||
let agents = 0;
|
||||
let skills = 0;
|
||||
let rules = 0;
|
||||
|
||||
// Copy commands
|
||||
const commandsDir = path.join(repoRoot, 'commands');
|
||||
if (fs.existsSync(commandsDir)) {
|
||||
const files = findFiles(commandsDir, '.md');
|
||||
for (const file of files) {
|
||||
if (path.basename(path.dirname(file)) === 'commands') {
|
||||
const localName = path.basename(file);
|
||||
const targetPath = path.join(codebuddyFullPath, 'commands', localName);
|
||||
if (copyManagedFile(file, targetPath, manifest, `commands/${localName}`)) {
|
||||
commands += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy agents
|
||||
const agentsDir = path.join(repoRoot, 'agents');
|
||||
if (fs.existsSync(agentsDir)) {
|
||||
const files = findFiles(agentsDir, '.md');
|
||||
for (const file of files) {
|
||||
if (path.basename(path.dirname(file)) === 'agents') {
|
||||
const localName = path.basename(file);
|
||||
const targetPath = path.join(codebuddyFullPath, 'agents', localName);
|
||||
if (copyManagedFile(file, targetPath, manifest, `agents/${localName}`)) {
|
||||
agents += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy skills (with subdirectories)
|
||||
const skillsDir = path.join(repoRoot, 'skills');
|
||||
if (fs.existsSync(skillsDir)) {
|
||||
const skillDirs = fs.readdirSync(skillsDir, { withFileTypes: true })
|
||||
.filter(entry => entry.isDirectory())
|
||||
.map(entry => entry.name);
|
||||
|
||||
for (const skillName of skillDirs) {
|
||||
const sourceSkillDir = path.join(skillsDir, skillName);
|
||||
const targetSkillDir = path.join(codebuddyFullPath, 'skills', skillName);
|
||||
let skillCopied = false;
|
||||
|
||||
const skillFiles = findFiles(sourceSkillDir);
|
||||
for (const sourceFile of skillFiles) {
|
||||
const relativePath = path.relative(sourceSkillDir, sourceFile);
|
||||
const targetPath = path.join(targetSkillDir, relativePath);
|
||||
const manifestEntry = `skills/${skillName}/${relativePath.replace(/\\/g, '/')}`;
|
||||
|
||||
if (copyManagedFile(sourceFile, targetPath, manifest, manifestEntry)) {
|
||||
skillCopied = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (skillCopied) {
|
||||
skills += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy rules (with subdirectories)
|
||||
const rulesDir = path.join(repoRoot, 'rules');
|
||||
if (fs.existsSync(rulesDir)) {
|
||||
const ruleFiles = findFiles(rulesDir);
|
||||
for (const ruleFile of ruleFiles) {
|
||||
const relativePath = path.relative(rulesDir, ruleFile);
|
||||
const targetPath = path.join(codebuddyFullPath, 'rules', relativePath);
|
||||
const manifestEntry = `rules/${relativePath.replace(/\\/g, '/')}`;
|
||||
|
||||
if (copyManagedFile(ruleFile, targetPath, manifest, manifestEntry)) {
|
||||
rules += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy README files (skip install/uninstall scripts to avoid broken
|
||||
// path references when the copied script runs from the target directory)
|
||||
const readmeFiles = ['README.md', 'README.zh-CN.md'];
|
||||
for (const readmeFile of readmeFiles) {
|
||||
const sourcePath = path.join(scriptDir, readmeFile);
|
||||
if (fs.existsSync(sourcePath)) {
|
||||
const targetPath = path.join(codebuddyFullPath, readmeFile);
|
||||
copyManagedFile(sourcePath, targetPath, manifest, readmeFile);
|
||||
}
|
||||
}
|
||||
|
||||
// Add manifest itself
|
||||
ensureManifestEntry(manifest, '.ecc-manifest');
|
||||
|
||||
// Print summary
|
||||
console.log('Installation complete!');
|
||||
console.log('');
|
||||
console.log('Components installed:');
|
||||
console.log(` Commands: ${commands}`);
|
||||
console.log(` Agents: ${agents}`);
|
||||
console.log(` Skills: ${skills}`);
|
||||
console.log(` Rules: ${rules}`);
|
||||
console.log('');
|
||||
console.log(`Directory: ${path.basename(codebuddyFullPath)}`);
|
||||
console.log('');
|
||||
console.log('Next steps:');
|
||||
console.log(' 1. Open your project in CodeBuddy');
|
||||
console.log(' 2. Type / to see available commands');
|
||||
console.log(' 3. Enjoy the ECC workflows!');
|
||||
console.log('');
|
||||
console.log('To uninstall later:');
|
||||
console.log(` cd ${codebuddyFullPath}`);
|
||||
console.log(' node uninstall.js');
|
||||
console.log('');
|
||||
}
|
||||
|
||||
// Run installer
|
||||
try {
|
||||
doInstall();
|
||||
} catch (error) {
|
||||
console.error(`Error: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
231
.codebuddy/install.sh
Executable file
231
.codebuddy/install.sh
Executable file
@@ -0,0 +1,231 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# ECC CodeBuddy Installer
|
||||
# Installs Everything Claude Code workflows into a CodeBuddy project.
|
||||
#
|
||||
# Usage:
|
||||
# ./install.sh # Install to current directory
|
||||
# ./install.sh ~ # Install globally to ~/.codebuddy/
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# When globs match nothing, expand to empty list instead of the literal pattern
|
||||
shopt -s nullglob
|
||||
|
||||
# Resolve the directory where this script lives
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
# Locate the ECC repo root by walking up from SCRIPT_DIR to find the marker
|
||||
# file (VERSION). This keeps the script working even when it has been copied
|
||||
# into a target project's .codebuddy/ directory.
|
||||
find_repo_root() {
|
||||
local dir="$(dirname "$SCRIPT_DIR")"
|
||||
# First try the parent of SCRIPT_DIR (original layout: .codebuddy/ lives in repo root)
|
||||
if [ -f "$dir/VERSION" ] && [ -d "$dir/commands" ] && [ -d "$dir/agents" ]; then
|
||||
echo "$dir"
|
||||
return 0
|
||||
fi
|
||||
echo ""
|
||||
return 1
|
||||
}
|
||||
|
||||
REPO_ROOT="$(find_repo_root)"
|
||||
if [ -z "$REPO_ROOT" ]; then
|
||||
echo "Error: Cannot locate the ECC repository root."
|
||||
echo "This script must be run from within the ECC repository's .codebuddy/ directory."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# CodeBuddy directory name
|
||||
CODEBUDDY_DIR=".codebuddy"
|
||||
|
||||
ensure_manifest_entry() {
|
||||
local manifest="$1"
|
||||
local entry="$2"
|
||||
|
||||
touch "$manifest"
|
||||
if ! grep -Fqx "$entry" "$manifest"; then
|
||||
echo "$entry" >> "$manifest"
|
||||
fi
|
||||
}
|
||||
|
||||
manifest_has_entry() {
|
||||
local manifest="$1"
|
||||
local entry="$2"
|
||||
|
||||
[ -f "$manifest" ] && grep -Fqx "$entry" "$manifest"
|
||||
}
|
||||
|
||||
copy_managed_file() {
|
||||
local source_path="$1"
|
||||
local target_path="$2"
|
||||
local manifest="$3"
|
||||
local manifest_entry="$4"
|
||||
local make_executable="${5:-0}"
|
||||
|
||||
local already_managed=0
|
||||
if manifest_has_entry "$manifest" "$manifest_entry"; then
|
||||
already_managed=1
|
||||
fi
|
||||
|
||||
if [ -f "$target_path" ]; then
|
||||
if [ "$already_managed" -eq 1 ]; then
|
||||
ensure_manifest_entry "$manifest" "$manifest_entry"
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
|
||||
cp "$source_path" "$target_path"
|
||||
if [ "$make_executable" -eq 1 ]; then
|
||||
chmod +x "$target_path"
|
||||
fi
|
||||
ensure_manifest_entry "$manifest" "$manifest_entry"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Install function
|
||||
do_install() {
|
||||
local target_dir="$PWD"
|
||||
|
||||
# Check if ~ was specified (or expanded to $HOME)
|
||||
if [ "$#" -ge 1 ]; then
|
||||
if [ "$1" = "~" ] || [ "$1" = "$HOME" ]; then
|
||||
target_dir="$HOME"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if we're already inside a .codebuddy directory
|
||||
local current_dir_name="$(basename "$target_dir")"
|
||||
local codebuddy_full_path
|
||||
|
||||
if [ "$current_dir_name" = ".codebuddy" ]; then
|
||||
# Already inside the codebuddy directory, use it directly
|
||||
codebuddy_full_path="$target_dir"
|
||||
else
|
||||
# Normal case: append CODEBUDDY_DIR to target_dir
|
||||
codebuddy_full_path="$target_dir/$CODEBUDDY_DIR"
|
||||
fi
|
||||
|
||||
echo "ECC CodeBuddy Installer"
|
||||
echo "======================="
|
||||
echo ""
|
||||
echo "Source: $REPO_ROOT"
|
||||
echo "Target: $codebuddy_full_path/"
|
||||
echo ""
|
||||
|
||||
# Subdirectories to create
|
||||
SUBDIRS="commands agents skills rules"
|
||||
|
||||
# Create all required codebuddy subdirectories
|
||||
for dir in $SUBDIRS; do
|
||||
mkdir -p "$codebuddy_full_path/$dir"
|
||||
done
|
||||
|
||||
# Manifest file to track installed files
|
||||
MANIFEST="$codebuddy_full_path/.ecc-manifest"
|
||||
touch "$MANIFEST"
|
||||
|
||||
# Counters for summary
|
||||
commands=0
|
||||
agents=0
|
||||
skills=0
|
||||
rules=0
|
||||
|
||||
# Copy commands from repo root
|
||||
if [ -d "$REPO_ROOT/commands" ]; then
|
||||
for f in "$REPO_ROOT/commands"/*.md; do
|
||||
[ -f "$f" ] || continue
|
||||
local_name=$(basename "$f")
|
||||
target_path="$codebuddy_full_path/commands/$local_name"
|
||||
if copy_managed_file "$f" "$target_path" "$MANIFEST" "commands/$local_name"; then
|
||||
commands=$((commands + 1))
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Copy agents from repo root
|
||||
if [ -d "$REPO_ROOT/agents" ]; then
|
||||
for f in "$REPO_ROOT/agents"/*.md; do
|
||||
[ -f "$f" ] || continue
|
||||
local_name=$(basename "$f")
|
||||
target_path="$codebuddy_full_path/agents/$local_name"
|
||||
if copy_managed_file "$f" "$target_path" "$MANIFEST" "agents/$local_name"; then
|
||||
agents=$((agents + 1))
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Copy skills from repo root (if available)
|
||||
if [ -d "$REPO_ROOT/skills" ]; then
|
||||
for d in "$REPO_ROOT/skills"/*/; do
|
||||
[ -d "$d" ] || continue
|
||||
skill_name="$(basename "$d")"
|
||||
target_skill_dir="$codebuddy_full_path/skills/$skill_name"
|
||||
skill_copied=0
|
||||
|
||||
while IFS= read -r source_file; do
|
||||
relative_path="${source_file#$d}"
|
||||
target_path="$target_skill_dir/$relative_path"
|
||||
|
||||
mkdir -p "$(dirname "$target_path")"
|
||||
if copy_managed_file "$source_file" "$target_path" "$MANIFEST" "skills/$skill_name/$relative_path"; then
|
||||
skill_copied=1
|
||||
fi
|
||||
done < <(find "$d" -type f | sort)
|
||||
|
||||
if [ "$skill_copied" -eq 1 ]; then
|
||||
skills=$((skills + 1))
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Copy rules from repo root
|
||||
if [ -d "$REPO_ROOT/rules" ]; then
|
||||
while IFS= read -r rule_file; do
|
||||
relative_path="${rule_file#$REPO_ROOT/rules/}"
|
||||
target_path="$codebuddy_full_path/rules/$relative_path"
|
||||
|
||||
mkdir -p "$(dirname "$target_path")"
|
||||
if copy_managed_file "$rule_file" "$target_path" "$MANIFEST" "rules/$relative_path"; then
|
||||
rules=$((rules + 1))
|
||||
fi
|
||||
done < <(find "$REPO_ROOT/rules" -type f | sort)
|
||||
fi
|
||||
|
||||
# Copy README files (skip install/uninstall scripts to avoid broken
|
||||
# path references when the copied script runs from the target directory)
|
||||
for readme_file in "$SCRIPT_DIR/README.md" "$SCRIPT_DIR/README.zh-CN.md"; do
|
||||
if [ -f "$readme_file" ]; then
|
||||
local_name=$(basename "$readme_file")
|
||||
target_path="$codebuddy_full_path/$local_name"
|
||||
copy_managed_file "$readme_file" "$target_path" "$MANIFEST" "$local_name" || true
|
||||
fi
|
||||
done
|
||||
|
||||
# Add manifest file itself to manifest
|
||||
ensure_manifest_entry "$MANIFEST" ".ecc-manifest"
|
||||
|
||||
# Installation summary
|
||||
echo "Installation complete!"
|
||||
echo ""
|
||||
echo "Components installed:"
|
||||
echo " Commands: $commands"
|
||||
echo " Agents: $agents"
|
||||
echo " Skills: $skills"
|
||||
echo " Rules: $rules"
|
||||
echo ""
|
||||
echo "Directory: $(basename "$codebuddy_full_path")"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Open your project in CodeBuddy"
|
||||
echo " 2. Type / to see available commands"
|
||||
echo " 3. Enjoy the ECC workflows!"
|
||||
echo ""
|
||||
echo "To uninstall later:"
|
||||
echo " cd $codebuddy_full_path"
|
||||
echo " ./uninstall.sh"
|
||||
}
|
||||
|
||||
# Main logic
|
||||
do_install "$@"
|
||||
291
.codebuddy/uninstall.js
Executable file
291
.codebuddy/uninstall.js
Executable file
@@ -0,0 +1,291 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* ECC CodeBuddy Uninstaller (Cross-platform Node.js version)
|
||||
* Uninstalls Everything Claude Code workflows from a CodeBuddy project.
|
||||
*
|
||||
* Usage:
|
||||
* node uninstall.js # Uninstall from current directory
|
||||
* node uninstall.js ~ # Uninstall globally from ~/.codebuddy/
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const readline = require('readline');
|
||||
|
||||
/**
|
||||
* Get home directory cross-platform
|
||||
*/
|
||||
function getHomeDir() {
|
||||
return process.env.USERPROFILE || process.env.HOME || os.homedir();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a path to its canonical form
|
||||
*/
|
||||
function resolvePath(filePath) {
|
||||
try {
|
||||
return fs.realpathSync(filePath);
|
||||
} catch {
|
||||
// If realpath fails, return the path as-is
|
||||
return path.resolve(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a manifest entry is valid (security check)
|
||||
*/
|
||||
function isValidManifestEntry(entry) {
|
||||
// Reject empty, absolute paths, parent directory references
|
||||
if (!entry || entry.length === 0) return false;
|
||||
if (entry.startsWith('/')) return false;
|
||||
if (entry.startsWith('~')) return false;
|
||||
if (entry.includes('/../') || entry.includes('/..')) return false;
|
||||
if (entry.startsWith('../') || entry.startsWith('..\\')) return false;
|
||||
if (entry === '..' || entry === '...' || entry.includes('\\..\\')||entry.includes('/..')) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read lines from manifest file
|
||||
*/
|
||||
function readManifest(manifestPath) {
|
||||
try {
|
||||
if (!fs.existsSync(manifestPath)) {
|
||||
return [];
|
||||
}
|
||||
const content = fs.readFileSync(manifestPath, 'utf8');
|
||||
return content.split('\n').filter(line => line.length > 0);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively find empty directories
|
||||
*/
|
||||
function findEmptyDirs(dirPath) {
|
||||
const emptyDirs = [];
|
||||
|
||||
function walkDirs(currentPath) {
|
||||
try {
|
||||
const entries = fs.readdirSync(currentPath, { withFileTypes: true });
|
||||
const subdirs = entries.filter(e => e.isDirectory());
|
||||
|
||||
for (const subdir of subdirs) {
|
||||
const subdirPath = path.join(currentPath, subdir.name);
|
||||
walkDirs(subdirPath);
|
||||
}
|
||||
|
||||
// Check if directory is now empty
|
||||
try {
|
||||
const remaining = fs.readdirSync(currentPath);
|
||||
if (remaining.length === 0 && currentPath !== dirPath) {
|
||||
emptyDirs.push(currentPath);
|
||||
}
|
||||
} catch {
|
||||
// Directory might have been deleted
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
|
||||
walkDirs(dirPath);
|
||||
return emptyDirs.sort().reverse(); // Sort in reverse for removal
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt user for confirmation
|
||||
*/
|
||||
async function promptConfirm(question) {
|
||||
return new Promise((resolve) => {
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
rl.question(question, (answer) => {
|
||||
rl.close();
|
||||
resolve(/^[yY]$/.test(answer));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Main uninstall function
|
||||
*/
|
||||
async function doUninstall() {
|
||||
const codebuddyDirName = '.codebuddy';
|
||||
|
||||
// Parse arguments
|
||||
let targetDir = process.cwd();
|
||||
if (process.argv.length > 2) {
|
||||
const arg = process.argv[2];
|
||||
if (arg === '~' || arg === getHomeDir()) {
|
||||
targetDir = getHomeDir();
|
||||
} else {
|
||||
targetDir = path.resolve(arg);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine codebuddy full path
|
||||
let codebuddyFullPath;
|
||||
const baseName = path.basename(targetDir);
|
||||
|
||||
if (baseName === codebuddyDirName) {
|
||||
codebuddyFullPath = targetDir;
|
||||
} else {
|
||||
codebuddyFullPath = path.join(targetDir, codebuddyDirName);
|
||||
}
|
||||
|
||||
console.log('ECC CodeBuddy Uninstaller');
|
||||
console.log('==========================');
|
||||
console.log('');
|
||||
console.log(`Target: ${codebuddyFullPath}/`);
|
||||
console.log('');
|
||||
|
||||
// Check if codebuddy directory exists
|
||||
if (!fs.existsSync(codebuddyFullPath)) {
|
||||
console.error(`Error: ${codebuddyDirName} directory not found at ${targetDir}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const codebuddyRootResolved = resolvePath(codebuddyFullPath);
|
||||
const manifest = path.join(codebuddyFullPath, '.ecc-manifest');
|
||||
|
||||
// Handle missing manifest
|
||||
if (!fs.existsSync(manifest)) {
|
||||
console.log('Warning: No manifest file found (.ecc-manifest)');
|
||||
console.log('');
|
||||
console.log('This could mean:');
|
||||
console.log(' 1. ECC was installed with an older version without manifest support');
|
||||
console.log(' 2. The manifest file was manually deleted');
|
||||
console.log('');
|
||||
|
||||
const confirmed = await promptConfirm(`Do you want to remove the entire ${codebuddyDirName} directory? (y/N) `);
|
||||
if (!confirmed) {
|
||||
console.log('Uninstall cancelled.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
try {
|
||||
fs.rmSync(codebuddyFullPath, { recursive: true, force: true });
|
||||
console.log('Uninstall complete!');
|
||||
console.log('');
|
||||
console.log(`Removed: ${codebuddyFullPath}/`);
|
||||
} catch (err) {
|
||||
console.error(`Error removing directory: ${err.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Found manifest file - will only remove files installed by ECC');
|
||||
console.log('');
|
||||
|
||||
const confirmed = await promptConfirm(`Are you sure you want to uninstall ECC from ${codebuddyDirName}? (y/N) `);
|
||||
if (!confirmed) {
|
||||
console.log('Uninstall cancelled.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Read manifest and remove files
|
||||
const manifestLines = readManifest(manifest);
|
||||
let removed = 0;
|
||||
let skipped = 0;
|
||||
|
||||
for (const filePath of manifestLines) {
|
||||
if (!filePath || filePath.length === 0) continue;
|
||||
|
||||
if (!isValidManifestEntry(filePath)) {
|
||||
console.log(`Skipped: ${filePath} (invalid manifest entry)`);
|
||||
skipped += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const fullPath = path.join(codebuddyFullPath, filePath);
|
||||
|
||||
// Security check: use path.relative() to ensure the manifest entry
|
||||
// resolves inside the codebuddy directory. This is stricter than
|
||||
// startsWith and correctly handles edge-cases with symlinks.
|
||||
const relative = path.relative(codebuddyRootResolved, path.resolve(fullPath));
|
||||
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
||||
console.log(`Skipped: ${filePath} (outside target directory)`);
|
||||
skipped += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const stats = fs.lstatSync(fullPath);
|
||||
|
||||
if (stats.isFile() || stats.isSymbolicLink()) {
|
||||
fs.unlinkSync(fullPath);
|
||||
console.log(`Removed: ${filePath}`);
|
||||
removed += 1;
|
||||
} else if (stats.isDirectory()) {
|
||||
try {
|
||||
const files = fs.readdirSync(fullPath);
|
||||
if (files.length === 0) {
|
||||
fs.rmdirSync(fullPath);
|
||||
console.log(`Removed: ${filePath}/`);
|
||||
removed += 1;
|
||||
} else {
|
||||
console.log(`Skipped: ${filePath}/ (not empty - contains user files)`);
|
||||
skipped += 1;
|
||||
}
|
||||
} catch {
|
||||
console.log(`Skipped: ${filePath}/ (not empty - contains user files)`);
|
||||
skipped += 1;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
skipped += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove empty directories
|
||||
const emptyDirs = findEmptyDirs(codebuddyFullPath);
|
||||
for (const emptyDir of emptyDirs) {
|
||||
try {
|
||||
fs.rmdirSync(emptyDir);
|
||||
const relativePath = path.relative(codebuddyFullPath, emptyDir);
|
||||
console.log(`Removed: ${relativePath}/`);
|
||||
removed += 1;
|
||||
} catch {
|
||||
// Directory might not be empty anymore
|
||||
}
|
||||
}
|
||||
|
||||
// Try to remove main codebuddy directory if empty
|
||||
try {
|
||||
const files = fs.readdirSync(codebuddyFullPath);
|
||||
if (files.length === 0) {
|
||||
fs.rmdirSync(codebuddyFullPath);
|
||||
console.log(`Removed: ${codebuddyDirName}/`);
|
||||
removed += 1;
|
||||
}
|
||||
} catch {
|
||||
// Directory not empty
|
||||
}
|
||||
|
||||
// Print summary
|
||||
console.log('');
|
||||
console.log('Uninstall complete!');
|
||||
console.log('');
|
||||
console.log('Summary:');
|
||||
console.log(` Removed: ${removed} items`);
|
||||
console.log(` Skipped: ${skipped} items (not found or user-modified)`);
|
||||
console.log('');
|
||||
|
||||
if (fs.existsSync(codebuddyFullPath)) {
|
||||
console.log(`Note: ${codebuddyDirName} directory still exists (contains user-added files)`);
|
||||
}
|
||||
}
|
||||
|
||||
// Run uninstaller
|
||||
doUninstall().catch((error) => {
|
||||
console.error(`Error: ${error.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
184
.codebuddy/uninstall.sh
Executable file
184
.codebuddy/uninstall.sh
Executable file
@@ -0,0 +1,184 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# ECC CodeBuddy Uninstaller
|
||||
# Uninstalls Everything Claude Code workflows from a CodeBuddy project.
|
||||
#
|
||||
# Usage:
|
||||
# ./uninstall.sh # Uninstall from current directory
|
||||
# ./uninstall.sh ~ # Uninstall globally from ~/.codebuddy/
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Resolve the directory where this script lives
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
# CodeBuddy directory name
|
||||
CODEBUDDY_DIR=".codebuddy"
|
||||
|
||||
resolve_path() {
|
||||
python3 -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$1"
|
||||
}
|
||||
|
||||
is_valid_manifest_entry() {
|
||||
local file_path="$1"
|
||||
|
||||
case "$file_path" in
|
||||
""|/*|~*|*/../*|../*|*/..|..)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Main uninstall function
|
||||
do_uninstall() {
|
||||
local target_dir="$PWD"
|
||||
|
||||
# Check if ~ was specified (or expanded to $HOME)
|
||||
if [ "$#" -ge 1 ]; then
|
||||
if [ "$1" = "~" ] || [ "$1" = "$HOME" ]; then
|
||||
target_dir="$HOME"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if we're already inside a .codebuddy directory
|
||||
local current_dir_name="$(basename "$target_dir")"
|
||||
local codebuddy_full_path
|
||||
|
||||
if [ "$current_dir_name" = ".codebuddy" ]; then
|
||||
# Already inside the codebuddy directory, use it directly
|
||||
codebuddy_full_path="$target_dir"
|
||||
else
|
||||
# Normal case: append CODEBUDDY_DIR to target_dir
|
||||
codebuddy_full_path="$target_dir/$CODEBUDDY_DIR"
|
||||
fi
|
||||
|
||||
echo "ECC CodeBuddy Uninstaller"
|
||||
echo "=========================="
|
||||
echo ""
|
||||
echo "Target: $codebuddy_full_path/"
|
||||
echo ""
|
||||
|
||||
if [ ! -d "$codebuddy_full_path" ]; then
|
||||
echo "Error: $CODEBUDDY_DIR directory not found at $target_dir"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
codebuddy_root_resolved="$(resolve_path "$codebuddy_full_path")"
|
||||
|
||||
# Manifest file path
|
||||
MANIFEST="$codebuddy_full_path/.ecc-manifest"
|
||||
|
||||
if [ ! -f "$MANIFEST" ]; then
|
||||
echo "Warning: No manifest file found (.ecc-manifest)"
|
||||
echo ""
|
||||
echo "This could mean:"
|
||||
echo " 1. ECC was installed with an older version without manifest support"
|
||||
echo " 2. The manifest file was manually deleted"
|
||||
echo ""
|
||||
read -p "Do you want to remove the entire $CODEBUDDY_DIR directory? (y/N) " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "Uninstall cancelled."
|
||||
exit 0
|
||||
fi
|
||||
rm -rf "$codebuddy_full_path"
|
||||
echo "Uninstall complete!"
|
||||
echo ""
|
||||
echo "Removed: $codebuddy_full_path/"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Found manifest file - will only remove files installed by ECC"
|
||||
echo ""
|
||||
read -p "Are you sure you want to uninstall ECC from $CODEBUDDY_DIR? (y/N) " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "Uninstall cancelled."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Counters
|
||||
removed=0
|
||||
skipped=0
|
||||
|
||||
# Read manifest and remove files
|
||||
while IFS= read -r file_path; do
|
||||
[ -z "$file_path" ] && continue
|
||||
|
||||
if ! is_valid_manifest_entry "$file_path"; then
|
||||
echo "Skipped: $file_path (invalid manifest entry)"
|
||||
skipped=$((skipped + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
full_path="$codebuddy_full_path/$file_path"
|
||||
|
||||
# Security check: ensure the path resolves inside the target directory.
|
||||
# Use Python to compute a reliable relative path so symlinks cannot
|
||||
# escape the boundary.
|
||||
relative="$(python3 -c 'import os,sys; print(os.path.relpath(os.path.abspath(sys.argv[1]), sys.argv[2]))' "$full_path" "$codebuddy_root_resolved")"
|
||||
case "$relative" in
|
||||
../*|..)
|
||||
echo "Skipped: $file_path (outside target directory)"
|
||||
skipped=$((skipped + 1))
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -L "$full_path" ] || [ -f "$full_path" ]; then
|
||||
rm -f "$full_path"
|
||||
echo "Removed: $file_path"
|
||||
removed=$((removed + 1))
|
||||
elif [ -d "$full_path" ]; then
|
||||
# Only remove directory if it's empty
|
||||
if [ -z "$(ls -A "$full_path" 2>/dev/null)" ]; then
|
||||
rmdir "$full_path" 2>/dev/null || true
|
||||
if [ ! -d "$full_path" ]; then
|
||||
echo "Removed: $file_path/"
|
||||
removed=$((removed + 1))
|
||||
fi
|
||||
else
|
||||
echo "Skipped: $file_path/ (not empty - contains user files)"
|
||||
skipped=$((skipped + 1))
|
||||
fi
|
||||
else
|
||||
skipped=$((skipped + 1))
|
||||
fi
|
||||
done < "$MANIFEST"
|
||||
|
||||
while IFS= read -r empty_dir; do
|
||||
[ "$empty_dir" = "$codebuddy_full_path" ] && continue
|
||||
relative_dir="${empty_dir#$codebuddy_full_path/}"
|
||||
rmdir "$empty_dir" 2>/dev/null || true
|
||||
if [ ! -d "$empty_dir" ]; then
|
||||
echo "Removed: $relative_dir/"
|
||||
removed=$((removed + 1))
|
||||
fi
|
||||
done < <(find "$codebuddy_full_path" -depth -type d -empty 2>/dev/null | sort -r)
|
||||
|
||||
# Try to remove the main codebuddy directory if it's empty
|
||||
if [ -d "$codebuddy_full_path" ] && [ -z "$(ls -A "$codebuddy_full_path" 2>/dev/null)" ]; then
|
||||
rmdir "$codebuddy_full_path" 2>/dev/null || true
|
||||
if [ ! -d "$codebuddy_full_path" ]; then
|
||||
echo "Removed: $CODEBUDDY_DIR/"
|
||||
removed=$((removed + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Uninstall complete!"
|
||||
echo ""
|
||||
echo "Summary:"
|
||||
echo " Removed: $removed items"
|
||||
echo " Skipped: $skipped items (not found or user-modified)"
|
||||
echo ""
|
||||
if [ -d "$codebuddy_full_path" ]; then
|
||||
echo "Note: $CODEBUDDY_DIR directory still exists (contains user-added files)"
|
||||
fi
|
||||
}
|
||||
|
||||
# Execute uninstall
|
||||
do_uninstall "$@"
|
||||
Reference in New Issue
Block a user