From 7e3d9007cd2100df0ecc03f72bdb7a6e2f7a0032 Mon Sep 17 00:00:00 2001 From: catlog22 Date: Sun, 4 Jan 2026 12:04:13 +0800 Subject: [PATCH] Remove remote installation script (install-remote.sh) due to deprecation and transition to new installation methods. --- CONTRIBUTING.md | 11 +- FAQ.md | 20 +- INSTALL.md | 183 ++-- INSTALL_CN.md | 352 +++----- Install-Claude.ps1 | 2132 -------------------------------------------- install-remote.ps1 | 763 ---------------- install-remote.sh | 886 ------------------ 7 files changed, 196 insertions(+), 4151 deletions(-) delete mode 100644 Install-Claude.ps1 delete mode 100644 install-remote.ps1 delete mode 100644 install-remote.sh diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9cef6da9..c373a791 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -102,11 +102,14 @@ git checkout -b feature/your-feature-name ### 4. Install CCW for Testing ```bash -# Install your development version -bash Install-Claude.sh +# Install dependencies +npm install -# Or on Windows -powershell -ExecutionPolicy Bypass -File Install-Claude.ps1 +# Link your development version globally +npm link + +# Verify installation +ccw --version ``` --- diff --git a/FAQ.md b/FAQ.md index b7a298a0..6b8018f8 100644 --- a/FAQ.md +++ b/FAQ.md @@ -74,16 +74,17 @@ CCW is **language-agnostic**. It works with any programming language or framewor ### How do I install CCW? -**One-line installation**: +**NPM Global Install** (Recommended): -**Windows**: -```powershell -Invoke-Expression (Invoke-WebRequest -Uri "https://raw.githubusercontent.com/catlog22/Claude-Code-Workflow/main/install-remote.ps1" -UseBasicParsing).Content +```bash +npm install -g claude-code-workflow ``` -**Linux/macOS**: +**Verify Installation**: ```bash -bash <(curl -fsSL https://raw.githubusercontent.com/catlog22/Claude-Code-Workflow/main/install-remote.sh) +ccw --version +ccw dashboard # Start Dashboard +ccw view # Start View interface ``` See [INSTALL.md](INSTALL.md) for detailed instructions. @@ -117,9 +118,12 @@ CCW installs to your home directory: ### How do I update CCW to the latest version? -Run the installation command again. It will overwrite existing files with the latest version. +Update via npm: +```bash +npm update -g claude-code-workflow +``` -**Note**: Custom modifications in `~/.claude/` will be overwritten. Back up customizations first. +**Note**: Custom modifications in `~/.claude/` will be preserved. The npm package only updates core CCW files. ### Do I need to install CLI tools? diff --git a/INSTALL.md b/INSTALL.md index f42f4322..6ddb1060 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -2,118 +2,56 @@ **English** | [中文](INSTALL_CN.md) -Interactive installation guide for Claude Code with Agent workflow coordination and distributed memory system. +Installation guide for Claude Code Agent workflow coordination and distributed memory system. -## ⚡ Quick One-Line Installation +> **Version 6.3.18: Native CodexLens & Dashboard Revolution** - Built-in code indexing engine (FTS + semantic search + HNSW vector index), new Dashboard views, TypeScript backend, session clustering intelligent memory management. -**Windows (PowerShell):** -```powershell -Invoke-Expression (Invoke-WebRequest -Uri "https://raw.githubusercontent.com/catlog22/Claude-Code-Workflow/main/install-remote.ps1" -UseBasicParsing).Content -``` +## ⚡ Quick Installation (Recommended) + +### NPM Global Install -**Linux/macOS (Bash/Zsh):** ```bash -bash <(curl -fsSL https://raw.githubusercontent.com/catlog22/Claude-Code-Workflow/main/install-remote.sh) +npm install -g claude-code-workflow ``` -### Interactive Version Selection +### Verify Installation -After running the installation command, you'll see an interactive menu with real-time version information: - -``` -Detecting latest release and commits... -Latest stable: v6.3.18 (2026-01-03 UTC) -Latest commit: bab5625 (2026-01-03 UTC) - -==================================================== - Version Selection Menu -==================================================== - -1) Latest Stable Release (Recommended) - |-- Version: v6.3.18 - |-- Released: 2026-01-03 UTC - \-- Production-ready - -2) Latest Development Version - |-- Branch: main - |-- Commit: bab5625 - |-- Updated: 2026-01-03 UTC - |-- Cutting-edge features - \-- May contain experimental changes - -3) Specific Release Version - |-- Install a specific tagged release - \-- Recent: v6.3.18, v6.3.0, v6.2.0 - -==================================================== - -Select version to install (1-3, default: 1): -``` - -**Version Options:** -- **Option 1 (Recommended)**: Latest stable release with verified production quality -- **Option 2**: Latest development version from main branch with newest features -- **Option 3**: Specific version tag for controlled deployments - -> 💡 **Pro Tip**: The installer automatically detects and displays the latest version numbers and release dates from GitHub. Just press Enter to select the recommended stable release. - -## 📂 Local Installation (Install-Claude.ps1) - -For local installation without network access, use the bundled PowerShell installer: - -**Installation Modes:** -```powershell -# Interactive mode with prompts (recommended) -.\Install-Claude.ps1 - -# Quick install with automatic backup -.\Install-Claude.ps1 -Force -BackupAll - -# Non-interactive install -.\Install-Claude.ps1 -NonInteractive -Force -``` - -**Installation Options:** - -| Mode | Description | Installs To | -|------|-------------|-------------| -| **Global** | System-wide installation (default) | `~/.claude/`, `~/.codex/`, `~/.gemini/` | -| **Path** | Custom directory + global hybrid | Local: `agents/`, `commands/`
Global: `workflows/`, `scripts/` | - -**Backup Behavior:** -- **Default**: Automatic backup enabled (`-BackupAll`) -- **Disable**: Use `-NoBackup` flag (⚠️ overwrites without backup) -- **Backup location**: `claude-backup-{timestamp}/` in installation directory - -**⚠️ Important Warnings:** -- `-Force -BackupAll`: Silent file overwrite (with backup) -- `-NoBackup -Force`: Permanent file overwrite (no recovery) -- Global mode modifies user profile directories - -### ✅ Verify Installation -After installation, open **Claude Code** and check if the workflow commands are available by running: ```bash -/workflow:session:list +# Check ccw command +ccw --version + +# Start Dashboard +ccw dashboard + +# Start View interface (alternative UI) +ccw view ``` -This command should be recognized in Claude Code's interface. If you see the workflow slash commands (e.g., `/workflow:*`, `/cli:*`), the installation was successful. +## 📂 Install from Source -> **📝 Installation Notes:** -> - The installer will automatically install/update `.codex/` and `.gemini/` directories -> - **Global mode**: Installs to `~/.codex` and `~/.gemini` -> - **Path mode**: Installs to your specified directory (e.g., `project/.codex`, `project/.gemini`) -> - **Backup**: Existing files are backed up by default to `claude-backup-{timestamp}/` -> - **Safety**: Use interactive mode for first-time installation to review changes +If you want to install from source or contribute to development: + +```bash +# Clone repository +git clone https://github.com/catlog22/Claude-Code-Workflow.git +cd Claude-Code-Workflow + +# Install dependencies +npm install + +# Global link (development mode) +npm link +``` ## Platform Requirements -- **Windows**: PowerShell 5.1+ or PowerShell Core 6+ -- **Linux/macOS**: Bash/Zsh (for installer) or PowerShell Core 6+ (for manual Install-Claude.ps1) +- **Node.js**: 16.0.0 or higher +- **OS**: Windows, Linux, macOS -**Install PowerShell Core (if needed):** -- **Ubuntu/Debian**: `sudo apt install powershell` -- **macOS**: `brew install powershell` -- **Download**: https://github.com/PowerShell/PowerShell +Check Node.js version: +```bash +node --version # Should be >= 16.0.0 +``` ## ⚙️ Configuration @@ -151,8 +89,8 @@ While CCW works with Claude alone, installing these tools provides enhanced anal | Tool | Purpose | Installation | |------|---------|--------------| -| **ripgrep (rg)** | Fast code search | **macOS**: `brew install ripgrep`
**Linux**: `apt install ripgrep` (Ubuntu) / `dnf install ripgrep` (Fedora)
**Windows**: `winget install ripgrep` / `choco install ripgrep` / `scoop install ripgrep`
**Verify**: `rg --version` | -| **jq** | JSON processing | **macOS**: `brew install jq`
**Linux**: `apt install jq` (Ubuntu) / `dnf install jq` (Fedora)
**Windows**: `winget install jq` / `choco install jq` / `scoop install jq`
**Verify**: `jq --version` | +| **ripgrep (rg)** | Fast code search | **macOS**: `brew install ripgrep`
**Linux**: `apt install ripgrep`
**Windows**: `winget install ripgrep` | +| **jq** | JSON processing | **macOS**: `brew install jq`
**Linux**: `apt install jq`
**Windows**: `winget install jq` | #### External AI Tools @@ -175,31 +113,52 @@ MCP (Model Context Protocol) tools provide advanced codebase analysis. **Recomme > **Note**: Code Index MCP has been replaced by CCW's built-in **CodexLens** (`mcp__ccw-tools__codex_lens`). No additional installation required for code indexing. -⚠️ **Note**: Some workflows expect MCP tools to be available. Without them, you may experience: -- Slower code analysis and search operations -- Reduced context quality in some scenarios -- Fallback to less efficient traditional tools -- Potential unexpected behavior in advanced workflows +## ✅ Verify Installation + +After installation, open **Claude Code** and check if the workflow commands are available by running: + +```bash +/workflow:session:list +``` + +This command should be recognized in Claude Code's interface. If you see the workflow slash commands (e.g., `/workflow:*`, `/cli:*`), the installation was successful. ## Troubleshooting -### PowerShell Execution Policy (Windows) -If you get execution policy errors: -```powershell -Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser +### Permission Errors (npm global install) + +**Linux/macOS**: +```bash +# Option 1: Use nvm to manage Node.js (recommended) +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash + +# Option 2: Fix npm permissions +mkdir ~/.npm-global +npm config set prefix '~/.npm-global' +echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc +source ~/.bashrc ``` +**Windows**: Run Command Prompt or PowerShell as Administrator + ### Workflow Commands Not Working + - Verify installation: `ls ~/.claude` (should show agents/, commands/, workflows/) - Restart Claude Code after installation - Check `/workflow:session:list` command is recognized -### Permission Errors -- **Windows**: Run PowerShell as Administrator -- **Linux/macOS**: May need `sudo` for global PowerShell installation +### ccw Command Not Found + +```bash +# Check global install location +npm list -g --depth=0 + +# Ensure npm bin directory is in PATH +npm bin -g +``` ## Support - **Issues**: [GitHub Issues](https://github.com/catlog22/Claude-Code-Workflow/issues) - **Getting Started**: [Quick Start Guide](GETTING_STARTED.md) -- **Documentation**: [Main README](README.md) \ No newline at end of file +- **Documentation**: [Main README](README.md) diff --git a/INSTALL_CN.md b/INSTALL_CN.md index 870d5859..611557bf 100644 --- a/INSTALL_CN.md +++ b/INSTALL_CN.md @@ -2,303 +2,163 @@ [English](INSTALL.md) | **中文** -Claude Code Agent 工作流协调和分布式内存系统的交互式安装指南。 +Claude Code Agent 工作流协调和分布式内存系统的安装指南。 > **版本 6.3.18:原生 CodexLens 与 Dashboard 革新** - 内置代码索引引擎(FTS + 语义搜索 + HNSW 向量索引),全新 Dashboard 视图,TypeScript 后端,会话聚类智能记忆管理。 -## ⚡ 一键远程安装(推荐) +## ⚡ 快速安装(推荐) -### 所有平台 - 远程 PowerShell 安装 -```powershell -# 从功能分支进行交互式远程安装(最新版本) -iex (iwr -useb https://raw.githubusercontent.com/catlog22/Claude-Code-Workflow/main/install-remote.ps1) +### NPM 全局安装 -# 包含统一文件输出系统的全局安装 -iex (iwr -useb https://raw.githubusercontent.com/catlog22/Claude-Code-Workflow/main/install-remote.ps1) -Global - -# 强制覆盖(非交互式)- 包含所有新的工作流文件生成功能 -iex (iwr -useb https://raw.githubusercontent.com/catlog22/Claude-Code-Workflow/main/install-remote.ps1) -Force -NonInteractive - -# 一键备份所有现有文件(无需确认) -iex (iwr -useb https://raw.githubusercontent.com/catlog22/Claude-Code-Workflow/main/install-remote.ps1) -BackupAll +```bash +npm install -g claude-code-workflow ``` -**远程安装器的功能:** -- ✅ 检查系统要求(PowerShell 版本、网络连接) -- ✅ 从 GitHub 下载最新版本(main 分支) -- ✅ 包含所有新的统一文件输出系统功能 -- ✅ 自动解压并运行本地安装程序 -- ✅ 安全确认和用户提示 -- ✅ 自动清理临时文件 -- ✅ 设置 .workflow/ 目录结构进行会话管理 +### 验证安装 -**注意**:为了跨平台兼容性,界面使用英文 +```bash +# 检查 ccw 命令 +ccw --version -## 📂 本地安装 +# 启动 Dashboard +ccw dashboard -### 所有平台(PowerShell) -```powershell -# 克隆包含最新功能的仓库 +# 启动 View 界面(替代 UI) +ccw view +``` + +## 📂 从源码安装 + +如果你想从源码安装或参与开发: + +```bash +# 克隆仓库 +git clone https://github.com/catlog22/Claude-Code-Workflow.git cd Claude-Code-Workflow -# Windows PowerShell 5.1+ 或 PowerShell Core(仅支持全局安装) -.\Install-Claude.ps1 +# 安装依赖 +npm install -# Linux/macOS PowerShell Core(仅支持全局安装) -pwsh ./Install-Claude.ps1 -``` - -**注意**:功能分支包含所有最新的统一文件输出系统增强功能,应用于新安装。 - -## 安装选项 - -### 远程安装参数 - -所有参数都可以传递给远程安装器: - -```powershell -# 全局安装 -iex (iwr -useb https://raw.githubusercontent.com/catlog22/Claude-Code-Workflow/main/install-remote.ps1) -Global - -# 安装到指定目录 -iex (iwr -useb https://raw.githubusercontent.com/catlog22/Claude-Code-Workflow/main/install-remote.ps1) -Directory "C:\MyProject" - -# 强制覆盖而不提示 -iex (iwr -useb https://raw.githubusercontent.com/catlog22/Claude-Code-Workflow/main/install-remote.ps1) -Force -NonInteractive - -# 从特定分支安装 -iex (iwr -useb https://raw.githubusercontent.com/catlog22/Claude-Code-Workflow/main/install-remote.ps1) -Branch "dev" - -# 跳过备份(更快) -iex (iwr -useb https://raw.githubusercontent.com/catlog22/Claude-Code-Workflow/main/install-remote.ps1) -NoBackup -``` - -### 本地安装选项 - -### 全局安装(默认且唯一模式) -安装到用户主目录(`~/.claude`): -```powershell -# 所有平台 - 全局安装(默认) -.\Install-Claude.ps1 - -# 自动备份(从 v1.1.0 开始默认) -.\Install-Claude.ps1 -BackupAll - -# 禁用自动备份(不推荐) -.\Install-Claude.ps1 -NoBackup - -# 自动化的非交互式模式 -.\Install-Claude.ps1 -Force -NonInteractive -``` - -**全局安装结构:** -``` -~/.claude/ -├── agents/ -├── commands/ -├── output-styles/ -├── settings.local.json -└── CLAUDE.md -``` - -**注意**:从 v1.2.0 开始,仅支持全局安装。移除了本地目录和自定义路径安装,以简化安装流程并确保所有平台的一致行为。 - -## 高级选项 - -### 强制安装 -覆盖现有文件: -```powershell -.\Install-Claude.ps1 -Force -``` - -### 跳过备份 -不创建备份文件: -```powershell -.\Install-Claude.ps1 -NoBackup -``` - -### 卸载 -删除安装: -```powershell -.\Install-Claude.ps1 -Uninstall -Force +# 全局链接(开发模式) +npm link ``` ## 平台要求 -### PowerShell(推荐) -- **Windows**:PowerShell 5.1+ 或 PowerShell Core 6+ -- **Linux**:PowerShell Core 6+ -- **macOS**:PowerShell Core 6+ +- **Node.js**: 16.0.0 或更高版本 +- **操作系统**: Windows、Linux、macOS -安装 PowerShell Core: -- **Ubuntu/Debian**:`sudo apt install powershell` -- **CentOS/RHEL**:`sudo dnf install powershell` -- **macOS**:`brew install powershell` -- **或下载**:https://github.com/PowerShell/PowerShell - -## 完整安装示例 - -### ⚡ 超快速(一键) -```powershell -# 一条命令完成安装 -iex (iwr -useb https://raw.githubusercontent.com/catlog22/Claude-Code-Workflow/main/install-remote.ps1) -Global - -# 完成!🎉 -# 开始使用 Claude Code Agent 工作流! +检查 Node.js 版本: +```bash +node --version # 应该 >= 16.0.0 ``` -### 📂 手动安装方法 -```powershell -# 手动安装步骤: -# 1. 安装 PowerShell Core(如果需要) -# Windows:从 GitHub 下载 -# Linux:sudo apt install powershell -# macOS:brew install powershell +## ⚙️ 配置 -# 2. 下载 Claude Code 工作流系统 -git clone https://github.com/catlog22/Claude-Code-Workflow.git -cd Claude-Code-Workflow +### 工具控制系统 -# 3. 全局安装(交互式) -.\Install-Claude.ps1 -Global +CCW 使用**基于配置的工具控制系统**,使外部 CLI 工具成为**可选**而非必需。这允许你: -# 4. 开始使用 Claude Code Agent 工作流! -# 使用 /workflow 命令和内存系统进行开发 +- ✅ **从仅 Claude 模式开始** - 无需安装额外工具即可立即使用 +- ✅ **渐进式增强** - 按需选择性添加外部工具 +- ✅ **优雅降级** - 工具不可用时自动回退 +- ✅ **灵活配置** - 每个项目控制工具可用性 + +**配置文件**:`~/.claude/workflows/tool-control.yaml` + +```yaml +tools: + gemini: + enabled: false # 可选:AI 分析和文档 + qwen: + enabled: true # 可选:AI 架构和代码生成 + codex: + enabled: true # 可选:AI 开发和实现 ``` -## 先决条件和推荐工具 +**行为**: +- **禁用时**:CCW 自动回退到其他已启用的工具或 Claude 的原生能力 +- **启用时**:使用专门工具发挥其特定优势 +- **默认**:所有工具禁用 - 仅 Claude 模式开箱即用 -为了充分发挥 CCW 的全部潜力,强烈建议安装这些额外工具。 +### 可选 CLI 工具(增强功能) -### 系统工具 (推荐) +虽然 CCW 仅使用 Claude 即可工作,但安装这些工具可提供增强的分析和扩展上下文: -这些工具增强了文件搜索和数据处理能力。 +#### 系统工具 -- **`ripgrep` (rg)**: 一款高速代码搜索工具。 - - **Windows**: - - **WinGet**: `winget install ripgrep` (推荐,自动选择 MSVC 版本) - - **Chocolatey**: `choco install ripgrep` - - **Scoop**: `scoop install ripgrep` - - **手动下载**: 从 [GitHub Releases](https://github.com/BurntSushi/ripgrep/releases) 下载预编译二进制文件 - - **macOS**: `brew install ripgrep` - - **Linux**: `sudo apt-get install ripgrep` (Debian/Ubuntu) 或 `sudo dnf install ripgrep` (Fedora) - - **验证**: `rg --version` +| 工具 | 用途 | 安装方式 | +|------|------|----------| +| **ripgrep (rg)** | 快速代码搜索 | **macOS**: `brew install ripgrep`
**Linux**: `apt install ripgrep`
**Windows**: `winget install ripgrep` | +| **jq** | JSON 处理 | **macOS**: `brew install jq`
**Linux**: `apt install jq`
**Windows**: `winget install jq` | -- **`jq`**: 一款命令行 JSON 处理器。 - - **Windows**: - - **WinGet**: `winget install jq` (推荐) - - **Chocolatey**: `choco install jq` - - **Scoop**: `scoop install jq` - - **手动下载**: 从 [GitHub Releases](https://github.com/jqlang/jq/releases) 下载 `jq-windows-amd64.exe` 并重命名为 `jq.exe` - - **macOS**: `brew install jq` - - **Linux**: `sudo apt-get install jq` (Debian/Ubuntu) 或 `sudo dnf install jq` (Fedora) - - **验证**: `jq --version` +#### 外部 AI 工具 -### 模型上下文协议 (MCP) 工具 (可选) +在 `~/.claude/workflows/tool-control.yaml` 中配置这些工具: -MCP 工具从外部来源提供高级上下文检索,增强 AI 的理解能力。关于安装,请参考各个工具的官方文档。 +| 工具 | 用途 | 安装方式 | +|------|------|----------| +| **Gemini CLI** | AI 分析和文档 | 遵循[官方文档](https://ai.google.dev) - 免费配额,扩展上下文 | +| **Codex CLI** | AI 开发和实现 | 遵循[官方文档](https://github.com/openai/codex) - 自主开发 | +| **Qwen Code** | AI 架构和代码生成 | 遵循[官方文档](https://github.com/QwenLM/qwen-code) - 大上下文窗口 | -| 工具 | 用途 | 官方源码 | -|---|---|---| -| **Exa MCP** | 用于搜索代码和网络。 | [exa-labs/exa-mcp-server](https://github.com/exa-labs/exa-mcp-server) | -| **Chrome DevTools MCP** | 用于与网页交互以提取布局和样式信息。 | [ChromeDevTools/chrome-devtools-mcp](https://github.com/ChromeDevTools/chrome-devtools-mcp) | +### 推荐:MCP 工具(增强分析) -> **注意**: Code Index MCP 已被 CCW 内置的 **CodexLens** (`mcp__ccw-tools__codex_lens`) 替代。无需额外安装代码索引工具。 +MCP(模型上下文协议)工具提供高级代码库分析。**推荐安装** - 虽然 CCW 有回退机制,但不安装 MCP 工具可能导致某些工作流的意外行为或性能下降。 -- **先决条件**: Node.js 和 npm (或兼容的 JavaScript 运行时)。 -- **验证**: 安装后,检查服务器是否可以启动 (具体请查阅 MCP 文档)。 +| MCP 服务器 | 用途 | 安装指南 | +|------------|------|----------| +| **Exa MCP** | 外部 API 模式和最佳实践 | [安装指南](https://smithery.ai/server/exa) | +| **Chrome DevTools MCP** | ⚠️ **UI 工作流必需** - URL 模式设计提取 | [安装指南](https://github.com/ChromeDevTools/chrome-devtools-mcp) | -### 可选的 AI CLI 工具 +> **注意**:Code Index MCP 已被 CCW 内置的 **CodexLens** (`mcp__ccw-tools__codex_lens`) 替代。无需额外安装代码索引工具。 -CCW 使用包装脚本与底层的 AI 模型进行交互。为了使这些包装器正常工作,必须在您的系统上安装和配置相应的 CLI 工具。 +## ✅ 验证安装 -- **Gemini CLI**: 用于分析、文档和探索。 - - **用途**: 提供对 Google Gemini 模型的访问。 - - **安装**: 请遵循 Google AI 官方文档来安装和配置 Gemini CLI。确保 `gemini` 命令在您的系统 PATH 中可用。 +安装后,在 **Claude Code** 中检查工作流命令是否可用: -- **Codex CLI**: 用于自主开发和实现。 - - **用途**: 提供对 OpenAI Codex 模型的访问,用于代码生成和修改。 - - **安装**: 请遵循您环境中使用的特定 Codex CLI 工具的安装说明。确保 `codex` 命令在您的系统 PATH 中可用。 +```bash +/workflow:session:list +``` -- **Qwen Code**: 用于架构和代码生成。 - - **用途**: 提供对阿里巴巴通义千问模型的访问。 - - **安装**: 请遵循通义千问官方文档来安装和配置其 CLI 工具。确保 `qwen` 命令在您的系统 PATH 中可用。 - -## 验证 - -安装后,验证: - -1. **检查安装:** - ```bash - # 全局 - ls ~/.claude - - # 本地 - ls ./.claude - ``` - -2. **测试 Claude Code:** - - 在项目中打开 Claude Code - - 检查全局 `.claude` 目录是否被识别 - - 验证工作流命令和内存命令是否可用 - - 测试 `/workflow` 命令的 Agent 协调功能 - - 测试 `/workflow version` 检查版本信息 +此命令应在 Claude Code 界面中被识别。如果看到工作流斜杠命令(如 `/workflow:*`、`/cli:*`),则安装成功。 ## 故障排除 -### PowerShell 执行策略 -如果出现执行策略错误: -```powershell -Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser +### 权限错误(npm 全局安装) + +**Linux/macOS**: +```bash +# 选项 1:使用 nvm 管理 Node.js(推荐) +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash + +# 选项 2:修复 npm 权限 +mkdir ~/.npm-global +npm config set prefix '~/.npm-global' +echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc +source ~/.bashrc ``` +**Windows**:以管理员身份运行命令提示符或 PowerShell + ### 工作流命令无效 -- 确保项目中存在 `.claude` 目录 -- 验证 workflow.md 和 agent 文件是否正确安装 -- 检查 Claude Code 是否识别配置 -### 权限错误 -- **Windows**:以管理员身份运行 -- **Linux/macOS**:如果需要全局 PowerShell 安装,使用 `sudo` +- 验证安装:`ls ~/.claude`(应显示 agents/、commands/、workflows/) +- 安装后重启 Claude Code +- 检查 `/workflow:session:list` 命令是否被识别 -## 安装器功能说明 +### ccw 命令未找到 -### 🛡️ 用户配置保护 -安装器会智能处理现有文件: -- **新文件**:直接复制 -- **非配置文件**:覆盖更新 -- **用户配置文件**(如 `settings.local.json`):提供选项 - - 保留现有文件(推荐) - - 覆盖为新文件 - - 备份后覆盖 +```bash +# 检查全局安装位置 +npm list -g --depth=0 -### 📦 安全卸载 -卸载时保护用户数据: -- **选项 1**:仅删除安装的文件(保留用户配置) -- **选项 2**:删除整个目录(需要二次确认) -- **选项 3**:不删除任何内容 - -### 🔄 智能备份 -- 自动为现有文件创建带时间戳的备份 -- 备份文件格式:`filename.backup_yyyyMMdd_HHmmss` -- 可以使用 `-NoBackup` 跳过备份 - -## 常见问题 - -**Q:为什么安装器界面是英文的?** -A:为了确保跨平台兼容性并避免字符编码问题,安装器使用英文界面。 - -**Q:可以自定义安装位置吗?** -A:是的,使用 `-Directory` 参数指定任何位置。 - -**Q:如何更新到最新版本?** -A:再次运行一键安装命令,它会自动获取最新版本。 - -**Q:卸载会删除我的配置吗?** -A:默认情况下,卸载只删除我们安装的文件,保留您的个人配置。 +# 确保 npm bin 目录在 PATH 中 +npm bin -g +``` ## 支持 - **问题**:[GitHub Issues](https://github.com/catlog22/Claude-Code-Workflow/issues) -- **文档**:[主 README](README.md) | [中文 README](README_CN.md) -- **工作流文档**:[.claude/commands/workflow.md](.claude/commands/workflow.md) \ No newline at end of file +- **快速入门**:[快速入门指南](GETTING_STARTED_CN.md) +- **文档**:[主 README](README_CN.md) diff --git a/Install-Claude.ps1 b/Install-Claude.ps1 deleted file mode 100644 index b8998ddf..00000000 --- a/Install-Claude.ps1 +++ /dev/null @@ -1,2132 +0,0 @@ -#Requires -Version 5.1 - -<# -.SYNOPSIS - Claude Code Workflow System Interactive Installer with Version Management - -.DESCRIPTION - Installation script for Claude Code Workflow System with Agent coordination and distributed memory system. - Features automatic version management: - - Tracks all installed files and directories in manifest - - Automatically moves old installation to backup folder before new installation - - Preserves version history in backup folders - - Supports clean uninstallation based on manifest records - - Installs globally to user profile directory (~/.claude) by default. - -.PARAMETER InstallMode - Installation mode: "Global" (default) or "Path" (hybrid local + global) - -.PARAMETER TargetPath - Target path for Path installation mode - -.PARAMETER Force - Skip confirmation prompts - -.PARAMETER NonInteractive - Run in non-interactive mode with default options - -.PARAMETER BackupAll - Automatically backup all existing files without confirmation prompts (enabled by default) - -.PARAMETER NoBackup - Disable automatic backup functionality - -.PARAMETER Uninstall - Uninstall Claude Code Workflow System based on installation manifest - -.EXAMPLE - .\Install-Claude.ps1 - Interactive installation with mode selection - -.EXAMPLE - .\Install-Claude.ps1 -InstallMode Global -Force - Global installation without prompts - -.EXAMPLE - .\Install-Claude.ps1 -Force -NonInteractive - Global installation without prompts - -.EXAMPLE - .\Install-Claude.ps1 -BackupAll - Global installation with automatic backup of all existing files - -.EXAMPLE - .\Install-Claude.ps1 -NoBackup - Installation without any backup (overwrite existing files) - -.EXAMPLE - .\Install-Claude.ps1 -Uninstall - Uninstall Claude Code Workflow System - -.EXAMPLE - .\Install-Claude.ps1 -Uninstall -Force - Uninstall without confirmation prompts - -.NOTES - Version Management: - - First installation: Creates manifest and installs files - - Subsequent installations: Automatically moves old files to backup folder (claude-backup-old-TIMESTAMP) - - Manifest location: ~/.claude-manifests/ - - Each installation path maintains its own manifest - - Uninstall uses manifest to remove only installed files - - Backup Folders: - - Old version backups: claude-backup-old-YYYYMMDD-HHMMSS (automatic on reinstall) - - File conflict backups: claude-backup-YYYYMMDD-HHMMSS (when BackupAll enabled) -#> - -param( - [ValidateSet("Global", "Path")] - [string]$InstallMode = "", - - [string]$TargetPath = "", - - [switch]$Force, - - [switch]$NonInteractive, - - [switch]$BackupAll, - - [switch]$NoBackup, - - [switch]$Uninstall, - - [string]$SourceVersion = "", - - [string]$SourceBranch = "", - - [string]$SourceCommit = "" -) - -# Set encoding for proper Unicode support -if ($PSVersionTable.PSVersion.Major -ge 6) { - $OutputEncoding = [System.Text.Encoding]::UTF8 - [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 - [Console]::InputEncoding = [System.Text.Encoding]::UTF8 -} else { - # For Windows PowerShell 5.1 - chcp 65001 | Out-Null -} - -# Script metadata -$ScriptName = "Claude Code Workflow System Installer" -$ScriptVersion = "2.3.0" # Installer script version - Added automatic version cleanup - -# Default version (will be overridden by -SourceVersion from install-remote.ps1) -$DefaultVersion = "unknown" - -# Initialize backup behavior - backup is enabled by default unless NoBackup is specified -if (-not $BackupAll -and -not $NoBackup) { - $BackupAll = $true - Write-Verbose "Auto-backup enabled by default. Use -NoBackup to disable." -} - -# Colors for output -$ColorSuccess = "Green" -$ColorInfo = "Cyan" -$ColorWarning = "Yellow" -$ColorError = "Red" -$ColorPrompt = "Magenta" - -# Global manifest directory location -$script:ManifestDir = Join-Path ([Environment]::GetFolderPath("UserProfile")) ".claude-manifests" - -function Write-ColorOutput { - param( - [string]$Message, - [string]$Color = "White" - ) - Write-Host $Message -ForegroundColor $Color -} - -function Show-Banner { - Write-Host "" - # CLAUDE - Cyan color - Write-Host ' ______ __ __ ' -ForegroundColor Cyan - Write-Host ' / \ | \ | \ ' -ForegroundColor Cyan - Write-Host '| $$$$$$\| $$ ______ __ __ ____| $$ ______ ' -ForegroundColor Cyan - Write-Host '| $$ \$$| $$ | \ | \ | \ / $$ / \ ' -ForegroundColor Cyan - Write-Host '| $$ | $$ \$$$$$$\| $$ | $$| $$$$$$$| $$$$$$\ ' -ForegroundColor Cyan - Write-Host '| $$ __ | $$ / $$| $$ | $$| $$ | $$| $$ $$ ' -ForegroundColor Cyan - Write-Host '| $$__/ \| $$| $$$$$$$| $$__/ $$| $$__| $$| $$$$$$$$ ' -ForegroundColor Cyan - Write-Host ' \$$ $$| $$ \$$ $$ \$$ $$ \$$ $$ \$$ \ ' -ForegroundColor Cyan - Write-Host ' \$$$$$$ \$$ \$$$$$$$ \$$$$$$ \$$$$$$$ \$$$$$$$ ' -ForegroundColor Cyan - Write-Host "" - - # CODE - Green color - Write-Host ' ______ __ ' -ForegroundColor Green - Write-Host '/ \ | \ ' -ForegroundColor Green - Write-Host '| $$$$$$\ ______ ____| $$ ______ ' -ForegroundColor Green - Write-Host '| $$ \$$ / \ / $$ / \ ' -ForegroundColor Green - Write-Host '| $$ | $$$$$$\| $$$$$$$| $$$$$$\ ' -ForegroundColor Green - Write-Host '| $$ __ | $$ | $$| $$ | $$| $$ $$ ' -ForegroundColor Green - Write-Host '| $$__/ \| $$__/ $$| $$__| $$| $$$$$$$$ ' -ForegroundColor Green - Write-Host ' \$$ $$ \$$ $$ \$$ $$ \$$ \ ' -ForegroundColor Green - Write-Host ' \$$$$$$ \$$$$$$ \$$$$$$$ \$$$$$$$ ' -ForegroundColor Green - Write-Host "" - - # WORKFLOW - Yellow color - Write-Host '__ __ __ ______ __ ' -ForegroundColor Yellow - Write-Host '| \ _ | \ | \ / \ | \ ' -ForegroundColor Yellow - Write-Host '| $$ / \ | $$ ______ ______ | $$ __ | $$$$$$\| $$ ______ __ __ __ ' -ForegroundColor Yellow - Write-Host '| $$/ $\| $$ / \ / \ | $$ / \| $$_ \$$| $$ / \ | \ | \ | \' -ForegroundColor Yellow - Write-Host '| $$ $$$\ $$| $$$$$$\| $$$$$$\| $$_/ $$| $$ \ | $$| $$$$$$\| $$ | $$ | $$' -ForegroundColor Yellow - Write-Host '| $$ $$\$$\$$| $$ | $$| $$ \$$| $$ $$ | $$$$ | $$| $$ | $$| $$ | $$ | $$' -ForegroundColor Yellow - Write-Host '| $$$$ \$$$$| $$__/ $$| $$ | $$$$$$\ | $$ | $$| $$__/ $$| $$_/ $$_/ $$' -ForegroundColor Yellow - Write-Host '| $$$ \$$$ \$$ $$| $$ | $$ \$$\| $$ | $$ \$$ $$ \$$ $$ $$' -ForegroundColor Yellow - Write-Host ' \$$ \$$ \$$$$$$ \$$ \$$ \$$ \$$ \$$ \$$$$$$ \$$$$$\$$$$' -ForegroundColor Yellow - Write-Host "" -} - -function Show-Header { - param( - [string]$InstallVersion = $DefaultVersion - ) - - Show-Banner - Write-ColorOutput " $ScriptName v$ScriptVersion" $ColorInfo - if ($InstallVersion -ne "unknown") { - Write-ColorOutput " Installing Claude Code Workflow v$InstallVersion" $ColorInfo - } - Write-ColorOutput " Unified workflow system with comprehensive coordination" $ColorInfo - Write-ColorOutput "========================================================================" $ColorInfo - if ($NoBackup) { - Write-ColorOutput "WARNING: Backup disabled - existing files will be overwritten!" $ColorWarning - } else { - Write-ColorOutput "Auto-backup enabled - existing files will be backed up" $ColorSuccess - } - Write-Host "" -} - -function Test-Prerequisites { - # Test PowerShell version - if ($PSVersionTable.PSVersion.Major -lt 5) { - Write-ColorOutput "ERROR: PowerShell 5.1 or higher is required" $ColorError - Write-ColorOutput "Current version: $($PSVersionTable.PSVersion)" $ColorError - return $false - } - - # Test source files exist - $sourceDir = $PSScriptRoot - $claudeDir = Join-Path $sourceDir ".claude" - $claudeMd = Join-Path $sourceDir "CLAUDE.md" - $codexDir = Join-Path $sourceDir ".codex" - $geminiDir = Join-Path $sourceDir ".gemini" - $qwenDir = Join-Path $sourceDir ".qwen" - - if (-not (Test-Path $claudeDir)) { - Write-ColorOutput "ERROR: .claude directory not found in $sourceDir" $ColorError - return $false - } - - if (-not (Test-Path $claudeMd)) { - Write-ColorOutput "ERROR: CLAUDE.md file not found in $sourceDir" $ColorError - return $false - } - - if (-not (Test-Path $codexDir)) { - Write-ColorOutput "ERROR: .codex directory not found in $sourceDir" $ColorError - return $false - } - - if (-not (Test-Path $geminiDir)) { - Write-ColorOutput "ERROR: .gemini directory not found in $sourceDir" $ColorError - return $false - } - - if (-not (Test-Path $qwenDir)) { - Write-ColorOutput "ERROR: .qwen directory not found in $sourceDir" $ColorError - return $false - } - - Write-ColorOutput "Prerequisites check passed" $ColorSuccess - return $true -} - -function Get-UserChoiceWithArrows { - param( - [string]$Prompt, - [string[]]$Options, - [int]$DefaultIndex = 0 - ) - - if ($NonInteractive) { - Write-ColorOutput "Non-interactive mode: Using default '$($Options[$DefaultIndex])'" $ColorInfo - return $Options[$DefaultIndex] - } - - # Test if we can use console features (interactive terminal) - $canUseConsole = $true - try { - $null = [Console]::CursorVisible - $null = $Host.UI.RawUI.ReadKey - } - catch { - $canUseConsole = $false - } - - # Fallback to simple numbered menu if console not available - if (-not $canUseConsole) { - Write-ColorOutput "Arrow navigation not available in this environment. Using numbered menu." $ColorWarning - return Get-UserChoice -Prompt $Prompt -Options $Options -Default $Options[$DefaultIndex] - } - - $selectedIndex = $DefaultIndex - $cursorVisible = $true - - try { - $cursorVisible = [Console]::CursorVisible - [Console]::CursorVisible = $false - } - catch { - # Silently continue if cursor control fails - } - - try { - Write-Host "" - Write-ColorOutput $Prompt $ColorPrompt - Write-Host "" - - while ($true) { - # Display options - for ($i = 0; $i -lt $Options.Count; $i++) { - $prefix = if ($i -eq $selectedIndex) { " > " } else { " " } - $color = if ($i -eq $selectedIndex) { $ColorSuccess } else { "White" } - - # Clear line and write option - Write-Host "`r$prefix$($Options[$i])".PadRight(80) -ForegroundColor $color - } - - Write-Host "" - Write-Host " Use " -NoNewline -ForegroundColor DarkGray - Write-Host "UP/DOWN" -NoNewline -ForegroundColor Yellow - Write-Host " arrows to navigate, " -NoNewline -ForegroundColor DarkGray - Write-Host "ENTER" -NoNewline -ForegroundColor Yellow - Write-Host " to select, or type " -NoNewline -ForegroundColor DarkGray - Write-Host "1-$($Options.Count)" -NoNewline -ForegroundColor Yellow - Write-Host "" -ForegroundColor DarkGray - - # Read key - $key = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") - - # Handle arrow keys - if ($key.VirtualKeyCode -eq 38) { - # Up arrow - $selectedIndex = if ($selectedIndex -gt 0) { $selectedIndex - 1 } else { $Options.Count - 1 } - } - elseif ($key.VirtualKeyCode -eq 40) { - # Down arrow - $selectedIndex = if ($selectedIndex -lt ($Options.Count - 1)) { $selectedIndex + 1 } else { 0 } - } - elseif ($key.VirtualKeyCode -eq 13) { - # Enter key - Write-Host "" - return $Options[$selectedIndex] - } - elseif ($key.Character -match '^\d$') { - # Number key - $num = [int]::Parse($key.Character) - if ($num -ge 1 -and $num -le $Options.Count) { - Write-Host "" - return $Options[$num - 1] - } - } - - # Move cursor back up to redraw menu - $linesToMove = $Options.Count + 2 - try { - for ($i = 0; $i -lt $linesToMove; $i++) { - [Console]::SetCursorPosition(0, [Console]::CursorTop - 1) - } - } - catch { - # If cursor positioning fails, just continue - break - } - } - } - finally { - try { - [Console]::CursorVisible = $cursorVisible - } - catch { - # Silently continue if cursor control fails - } - } -} - -function Get-UserChoice { - param( - [string]$Prompt, - [string[]]$Options, - [string]$Default = $null - ) - - if ($NonInteractive -and $Default) { - Write-ColorOutput "Non-interactive mode: Using default '$Default'" $ColorInfo - return $Default - } - - Write-ColorOutput $Prompt $ColorPrompt - for ($i = 0; $i -lt $Options.Count; $i++) { - if ($Default -and $Options[$i] -eq $Default) { - $marker = " (default)" - } else { - $marker = "" - } - Write-Host " $($i + 1). $($Options[$i])$marker" - } - - do { - $input = Read-Host "Please select (1-$($Options.Count))" - if ([string]::IsNullOrWhiteSpace($input) -and $Default) { - return $Default - } - - $index = $null - if ([int]::TryParse($input, [ref]$index) -and $index -ge 1 -and $index -le $Options.Count) { - return $Options[$index - 1] - } - - Write-ColorOutput "Invalid selection. Please enter a number between 1 and $($Options.Count)" $ColorWarning - } while ($true) -} - -function Confirm-Action { - param( - [string]$Message, - [switch]$DefaultYes - ) - - if ($Force) { - Write-ColorOutput "Force mode: Proceeding with '$Message'" $ColorInfo - return $true - } - - if ($NonInteractive) { - if ($DefaultYes) { - $result = $true - } else { - $result = $false - } - if ($result) { - $resultText = 'Yes' - } else { - $resultText = 'No' - } - Write-ColorOutput "Non-interactive mode: $Message - $resultText" $ColorInfo - return $result - } - - if ($DefaultYes) { - $defaultChar = "Y" - $prompt = "(Y/n)" - } else { - $defaultChar = "N" - $prompt = "(y/N)" - } - - do { - $response = Read-Host "$Message $prompt" - if ([string]::IsNullOrWhiteSpace($response)) { - return $DefaultYes - } - - switch ($response.ToLower()) { - { $_ -in @('y', 'yes') } { return $true } - { $_ -in @('n', 'no') } { return $false } - default { - Write-ColorOutput "Please answer 'y' or 'n'" $ColorWarning - } - } - } while ($true) -} - -function Get-BackupDirectory { - param( - [string]$TargetDirectory - ) - - $timestamp = Get-Date -Format "yyyyMMdd-HHmmss" - $backupDirName = "claude-backup-$timestamp" - $backupPath = Join-Path $TargetDirectory $backupDirName - - # Ensure backup directory exists - if (-not (Test-Path $backupPath)) { - New-Item -ItemType Directory -Path $backupPath -Force | Out-Null - } - - return $backupPath -} - -function Backup-FileToFolder { - param( - [string]$FilePath, - [string]$BackupFolder, - [switch]$Quiet - ) - - if (-not (Test-Path $FilePath)) { - return $false - } - - try { - $fileName = Split-Path $FilePath -Leaf - $relativePath = "" - - # Try to determine relative path structure for better organization - $fileDir = Split-Path $FilePath -Parent - if ($fileDir -match '\.claude') { - # Extract path relative to .claude directory - $claudeIndex = $fileDir.LastIndexOf('.claude') - if ($claudeIndex -ge 0) { - $relativePath = $fileDir.Substring($claudeIndex + 7) # +7 for ".claude\" - if ($relativePath.StartsWith('\')) { - $relativePath = $relativePath.Substring(1) - } - } - } - - # Create subdirectory structure in backup if needed - $backupSubDir = $BackupFolder - if (-not [string]::IsNullOrEmpty($relativePath)) { - $backupSubDir = Join-Path $BackupFolder $relativePath - if (-not (Test-Path $backupSubDir)) { - New-Item -ItemType Directory -Path $backupSubDir -Force | Out-Null - } - } - - $backupFilePath = Join-Path $backupSubDir $fileName - Copy-Item -Path $FilePath -Destination $backupFilePath -Force - - if (-not $Quiet) { - Write-ColorOutput "Backed up: $fileName" $ColorInfo - } - return $true - } catch { - if (-not $Quiet) { - Write-ColorOutput "WARNING: Failed to backup file $FilePath`: $($_.Exception.Message)" $ColorWarning - } - return $false - } -} - -function Backup-DirectoryToFolder { - param( - [string]$DirectoryPath, - [string]$BackupFolder - ) - - if (-not (Test-Path $DirectoryPath)) { - return $false - } - - try { - $dirName = Split-Path $DirectoryPath -Leaf - $backupDirPath = Join-Path $BackupFolder $dirName - - Copy-Item -Path $DirectoryPath -Destination $backupDirPath -Recurse -Force - Write-ColorOutput "Backed up directory: $dirName" $ColorInfo - return $true - } catch { - Write-ColorOutput "WARNING: Failed to backup directory $DirectoryPath`: $($_.Exception.Message)" $ColorWarning - return $false - } -} - -function Copy-DirectoryRecursive { - param( - [string]$Source, - [string]$Destination - ) - - if (-not (Test-Path $Source)) { - throw "Source directory does not exist: $Source" - } - - # Create destination directory if it doesn't exist - if (-not (Test-Path $Destination)) { - New-Item -ItemType Directory -Path $Destination -Force | Out-Null - } - - try { - # Copy all items recursively - Copy-Item -Path "$Source\*" -Destination $Destination -Recurse -Force - Write-ColorOutput "Directory copied: $Source -> $Destination" $ColorSuccess - } catch { - throw "Failed to copy directory: $($_.Exception.Message)" - } -} - -function Copy-FileToDestination { - param( - [string]$Source, - [string]$Destination, - [string]$Description = "file", - [string]$BackupFolder = $null - ) - - if (Test-Path $Destination) { - # Use BackupAll mode for automatic backup without confirmation (default behavior) - if ($BackupAll -and -not $NoBackup) { - if ($BackupFolder -and (Backup-FileToFolder -FilePath $Destination -BackupFolder $BackupFolder)) { - Write-ColorOutput "Auto-backed up: $Description" $ColorSuccess - } - Copy-Item -Path $Source -Destination $Destination -Force - Write-ColorOutput "$Description updated (with backup)" $ColorSuccess - return $true - } elseif ($NoBackup) { - # No backup mode - ask for confirmation - if (Confirm-Action "$Description already exists. Replace it? (NO BACKUP)" -DefaultYes:$false) { - Copy-Item -Path $Source -Destination $Destination -Force - Write-ColorOutput "$Description updated (no backup)" $ColorWarning - return $true - } else { - Write-ColorOutput "Skipping $Description installation" $ColorWarning - return $false - } - } elseif (Confirm-Action "$Description already exists. Replace it?" -DefaultYes:$false) { - if ($BackupFolder -and (Backup-FileToFolder -FilePath $Destination -BackupFolder $BackupFolder)) { - Write-ColorOutput "Existing $Description backed up" $ColorSuccess - } - Copy-Item -Path $Source -Destination $Destination -Force - Write-ColorOutput "$Description updated" $ColorSuccess - return $true - } else { - Write-ColorOutput "Skipping $Description installation" $ColorWarning - return $false - } - } else { - # Ensure destination directory exists - $destinationDir = Split-Path $Destination -Parent - if (-not (Test-Path $destinationDir)) { - New-Item -ItemType Directory -Path $destinationDir -Force | Out-Null - } - Copy-Item -Path $Source -Destination $Destination -Force - Write-ColorOutput "$Description installed" $ColorSuccess - return $true - } -} - -function Backup-AndReplaceDirectory { - param( - [string]$Source, - [string]$Destination, - [string]$Description = "directory", - [string]$BackupFolder = $null - ) - - if (-not (Test-Path $Source)) { - Write-ColorOutput "WARNING: Source $Description not found: $Source" $ColorWarning - return $false - } - - # Backup destination if it exists - if (Test-Path $Destination) { - Write-ColorOutput "Found existing $Description at: $Destination" $ColorInfo - - # Backup entire directory if backup is enabled - if (-not $NoBackup -and $BackupFolder) { - Write-ColorOutput "Backing up entire $Description..." $ColorInfo - if (Backup-DirectoryToFolder -DirectoryPath $Destination -BackupFolder $BackupFolder) { - Write-ColorOutput "Backed up $Description to: $BackupFolder" $ColorSuccess - } - } elseif ($NoBackup) { - if (-not (Confirm-Action "Replace existing $Description without backup?" -DefaultYes:$false)) { - Write-ColorOutput "Skipping $Description installation" $ColorWarning - return $false - } - } - - # Get all items from source to determine what to clear in destination - Write-ColorOutput "Clearing conflicting items in destination $Description..." $ColorInfo - $sourceItems = Get-ChildItem -Path $Source -Force - - foreach ($sourceItem in $sourceItems) { - $destItemPath = Join-Path $Destination $sourceItem.Name - if (Test-Path $destItemPath) { - Write-ColorOutput "Removing existing: $($sourceItem.Name)" $ColorInfo - Remove-Item -Path $destItemPath -Recurse -Force -ErrorAction SilentlyContinue - } - } - Write-ColorOutput "Cleared conflicting items in destination" $ColorSuccess - } else { - # Create destination directory if it doesn't exist - New-Item -ItemType Directory -Path $Destination -Force | Out-Null - Write-ColorOutput "Created destination directory: $Destination" $ColorInfo - } - - # Copy all items from source to destination - Write-ColorOutput "Copying $Description from $Source to $Destination..." $ColorInfo - $sourceItems = Get-ChildItem -Path $Source -Force - foreach ($item in $sourceItems) { - $destPath = Join-Path $Destination $item.Name - Copy-Item -Path $item.FullName -Destination $destPath -Recurse -Force - } - Write-ColorOutput "$Description installed successfully" $ColorSuccess - - return $true -} - -function Backup-CriticalConfigFiles { - param( - [string]$TargetDirectory, - [string]$BackupFolder, - [string[]]$FileNames - ) - - if (-not $BackupFolder -or $NoBackup) { - return - } - - if (-not (Test-Path $TargetDirectory)) { - return - } - - $backedUpCount = 0 - foreach ($fileName in $FileNames) { - $filePath = Join-Path $TargetDirectory $fileName - if (Test-Path $filePath) { - if (Backup-FileToFolder -FilePath $filePath -BackupFolder $BackupFolder) { - Write-ColorOutput "Critical config backed up: $fileName" $ColorSuccess - $backedUpCount++ - } - } - } - - if ($backedUpCount -gt 0) { - Write-ColorOutput "Backed up $backedUpCount critical configuration file(s)" $ColorInfo - } -} - -function Merge-DirectoryContents { - param( - [string]$Source, - [string]$Destination, - [string]$Description = "directory contents", - [string]$BackupFolder = $null - ) - - if (-not (Test-Path $Source)) { - Write-ColorOutput "WARNING: Source $Description not found: $Source" $ColorWarning - return $false - } - - # Create destination directory if it doesn't exist - if (-not (Test-Path $Destination)) { - New-Item -ItemType Directory -Path $Destination -Force | Out-Null - Write-ColorOutput "Created destination directory: $Destination" $ColorInfo - } - - # Get all items in source directory - $sourceItems = Get-ChildItem -Path $Source -Recurse -File - $totalFiles = $sourceItems.Count - $mergedCount = 0 - $skippedCount = 0 - $backedUpCount = 0 - $processedCount = 0 - - Write-ColorOutput "Processing $totalFiles files in $Description..." $ColorInfo - - foreach ($item in $sourceItems) { - $processedCount++ - - # Calculate relative path from source - $relativePath = $item.FullName.Substring($Source.Length + 1) - $destinationPath = Join-Path $Destination $relativePath - - # Ensure destination directory exists - $destinationDir = Split-Path $destinationPath -Parent - if (-not (Test-Path $destinationDir)) { - New-Item -ItemType Directory -Path $destinationDir -Force | Out-Null - } - - # Handle file merging - if (Test-Path $destinationPath) { - $fileName = Split-Path $relativePath -Leaf - # Use BackupAll mode for automatic backup without confirmation (default behavior) - if ($BackupAll -and -not $NoBackup) { - if ($BackupFolder -and (Backup-FileToFolder -FilePath $destinationPath -BackupFolder $BackupFolder -Quiet)) { - $backedUpCount++ - } - Copy-Item -Path $item.FullName -Destination $destinationPath -Force - $mergedCount++ - } elseif ($NoBackup) { - # No backup mode - ask for confirmation - if (Confirm-Action "File '$relativePath' already exists. Replace it? (NO BACKUP)" -DefaultYes:$false) { - Copy-Item -Path $item.FullName -Destination $destinationPath -Force - $mergedCount++ - } else { - $skippedCount++ - } - } elseif (Confirm-Action "File '$relativePath' already exists. Replace it?" -DefaultYes:$false) { - if ($BackupFolder -and (Backup-FileToFolder -FilePath $destinationPath -BackupFolder $BackupFolder -Quiet)) { - $backedUpCount++ - } - Copy-Item -Path $item.FullName -Destination $destinationPath -Force - $mergedCount++ - } else { - $skippedCount++ - } - } else { - Copy-Item -Path $item.FullName -Destination $destinationPath -Force - $mergedCount++ - } - - # Show progress every 20 files - if ($processedCount % 20 -eq 0 -or $processedCount -eq $totalFiles) { - Write-Progress -Activity "Merging $Description" -Status "$processedCount/$totalFiles files processed" -PercentComplete (($processedCount / $totalFiles) * 100) - } - } - - Write-Progress -Activity "Merging $Description" -Completed - - if ($backedUpCount -gt 0) { - Write-ColorOutput "Merged $mergedCount files ($backedUpCount backed up), skipped $skippedCount files" $ColorSuccess - } else { - Write-ColorOutput "Merged $mergedCount files, skipped $skippedCount files" $ColorSuccess - } - return $true -} - -# ============================================================================ -# INSTALLATION MANIFEST MANAGEMENT -# ============================================================================ - -function New-InstallManifest { - <# - .SYNOPSIS - Create a new installation manifest to track installed files - #> - param( - [string]$InstallationMode, - [string]$InstallationPath - ) - - # Create manifest directory if it doesn't exist - if (-not (Test-Path $script:ManifestDir)) { - New-Item -ItemType Directory -Path $script:ManifestDir -Force | Out-Null - } - - # Generate unique manifest ID based on timestamp and mode - # Distinguish between Global and Path installations with clear naming - $timestamp = Get-Date -Format "yyyyMMdd-HHmmss" - $modePrefix = if ($InstallationMode -eq "Global") { "manifest-global" } else { "manifest-path" } - $manifestId = "$modePrefix-$timestamp" - - $manifest = @{ - manifest_id = $manifestId - version = "1.0" - installation_mode = $InstallationMode - installation_path = $InstallationPath - installation_date = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") - installer_version = $ScriptVersion - files = @() - directories = @() - } - - return $manifest -} - -function Add-ManifestEntry { - <# - .SYNOPSIS - Add a file or directory entry to the manifest - #> - param( - [Parameter(Mandatory=$true)] - [hashtable]$Manifest, - - [Parameter(Mandatory=$true)] - [string]$Path, - - [Parameter(Mandatory=$true)] - [ValidateSet("File", "Directory")] - [string]$Type - ) - - $entry = @{ - path = $Path - type = $Type - timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") - } - - if ($Type -eq "File") { - $Manifest.files += $entry - } else { - $Manifest.directories += $entry - } -} - -function Add-ManifestEntriesBulk { - <# - .SYNOPSIS - Bulk add file entries to manifest for better performance - #> - param( - [Parameter(Mandatory=$true)] - [hashtable]$Manifest, - - [Parameter(Mandatory=$true)] - [string]$SourceDirectory, - - [Parameter(Mandatory=$true)] - [string]$TargetDirectory, - - [string]$Description = "files" - ) - - if (-not (Test-Path $SourceDirectory)) { - Write-ColorOutput "WARNING: Source directory not found: $SourceDirectory" $ColorWarning - return 0 - } - - $timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") - - # Add directory entry - Add-ManifestEntry -Manifest $Manifest -Path $TargetDirectory -Type "Directory" - - # Get all source files - $sourceFiles = Get-ChildItem -Path $SourceDirectory -Recurse -File - $fileEntries = [System.Collections.ArrayList]::new() - - Write-ColorOutput "Adding $($sourceFiles.Count) $Description to manifest..." $ColorInfo - - foreach ($file in $sourceFiles) { - $relativePath = $file.FullName.Substring($SourceDirectory.Length) - $targetPath = $TargetDirectory + $relativePath - - $entry = @{ - path = $targetPath - type = "File" - timestamp = $timestamp - } - $null = $fileEntries.Add($entry) - } - - # Bulk add to manifest - $Manifest.files += $fileEntries - - Write-ColorOutput "Added $($fileEntries.Count) $Description to manifest" $ColorSuccess - return $fileEntries.Count -} - -function Remove-OldManifestsForPath { - <# - .SYNOPSIS - Remove old manifest files for the same installation path and mode - #> - param( - [Parameter(Mandatory=$true)] - [string]$InstallationPath, - - [Parameter(Mandatory=$true)] - [string]$InstallationMode - ) - - if (-not (Test-Path $script:ManifestDir)) { - return - } - - try { - # Get both new (manifest-*) and old (install-*) format manifest files - $manifestFiles = Get-ChildItem -Path $script:ManifestDir -Filter "*-*.json" -File | - Where-Object { $_.Name -match '^(manifest|install)-' } - $removedCount = 0 - - foreach ($file in $manifestFiles) { - try { - $manifestJson = Get-Content -Path $file.FullName -Raw -Encoding utf8 - $manifest = $manifestJson | ConvertFrom-Json - - # Normalize paths for comparison (case-insensitive, handle trailing slashes) - $manifestPath = if ($manifest.installation_path) { - $manifest.installation_path.TrimEnd('\', '/').ToLower() - } else { - "" - } - $targetPath = $InstallationPath.TrimEnd('\', '/').ToLower() - - # Get installation mode - $manifestMode = if ($manifest.installation_mode) { - $manifest.installation_mode - } else { - "Global" - } - - # Only remove if BOTH path and mode match - if ($manifestPath -eq $targetPath -and $manifestMode -eq $InstallationMode) { - Remove-Item -Path $file.FullName -Force - Write-ColorOutput "Removed old manifest: $($file.Name)" $ColorInfo - $removedCount++ - } - } catch { - Write-ColorOutput "WARNING: Failed to process manifest $($file.Name): $($_.Exception.Message)" $ColorWarning - } - } - - if ($removedCount -gt 0) { - Write-ColorOutput "Removed $removedCount old manifest(s) for installation path: $InstallationPath" $ColorSuccess - } - } catch { - Write-ColorOutput "WARNING: Failed to clean old manifests: $($_.Exception.Message)" $ColorWarning - } -} - -function Save-InstallManifest { - <# - .SYNOPSIS - Save the installation manifest to disk - #> - param( - [Parameter(Mandatory=$true)] - [hashtable]$Manifest - ) - - try { - # Remove old manifests for the same installation path and mode - if ($Manifest.installation_path -and $Manifest.installation_mode) { - Remove-OldManifestsForPath -InstallationPath $Manifest.installation_path -InstallationMode $Manifest.installation_mode - } - - # Use manifest ID to create unique file name - $manifestFileName = "$($Manifest.manifest_id).json" - $manifestPath = Join-Path $script:ManifestDir $manifestFileName - - $Manifest | ConvertTo-Json -Depth 10 | Out-File -FilePath $manifestPath -Encoding utf8 -Force - Write-ColorOutput "Installation manifest saved: $manifestPath" $ColorSuccess - return $true - } catch { - Write-ColorOutput "WARNING: Failed to save installation manifest: $($_.Exception.Message)" $ColorWarning - return $false - } -} - -function Migrate-LegacyManifest { - <# - .SYNOPSIS - Migrate old single manifest file to new multi-manifest system - #> - - $legacyManifestPath = Join-Path ([Environment]::GetFolderPath("UserProfile")) ".claude-install-manifest.json" - - if (-not (Test-Path $legacyManifestPath)) { - return - } - - try { - Write-ColorOutput "Found legacy manifest file, migrating to new system..." $ColorInfo - - # Create manifest directory if it doesn't exist - if (-not (Test-Path $script:ManifestDir)) { - New-Item -ItemType Directory -Path $script:ManifestDir -Force | Out-Null - } - - # Read legacy manifest - $legacyJson = Get-Content -Path $legacyManifestPath -Raw -Encoding utf8 - $legacy = $legacyJson | ConvertFrom-Json - - # Generate new manifest ID with new naming convention - $timestamp = Get-Date -Format "yyyyMMdd-HHmmss" - $mode = if ($legacy.installation_mode) { $legacy.installation_mode } else { "Global" } - $modePrefix = if ($mode -eq "Global") { "manifest-global" } else { "manifest-path" } - $manifestId = "$modePrefix-$timestamp-migrated" - - # Create new manifest with all fields - $newManifest = @{ - manifest_id = $manifestId - version = if ($legacy.version) { $legacy.version } else { "1.0" } - installation_mode = $mode - installation_path = if ($legacy.installation_path) { $legacy.installation_path } else { [Environment]::GetFolderPath("UserProfile") } - installation_date = if ($legacy.installation_date) { $legacy.installation_date } else { (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") } - installer_version = if ($legacy.installer_version) { $legacy.installer_version } else { "unknown" } - files = if ($legacy.files) { @($legacy.files) } else { @() } - directories = if ($legacy.directories) { @($legacy.directories) } else { @() } - } - - # Save to new location - $newManifestPath = Join-Path $script:ManifestDir "$manifestId.json" - $newManifest | ConvertTo-Json -Depth 10 | Out-File -FilePath $newManifestPath -Encoding utf8 -Force - - # Rename old manifest (don't delete, keep as backup) - $backupPath = "$legacyManifestPath.migrated" - Move-Item -Path $legacyManifestPath -Destination $backupPath -Force - - Write-ColorOutput "Legacy manifest migrated successfully" $ColorSuccess - Write-ColorOutput "Old manifest backed up to: $backupPath" $ColorInfo - } catch { - Write-ColorOutput "WARNING: Failed to migrate legacy manifest: $($_.Exception.Message)" $ColorWarning - } -} - -function Move-OldInstallation { - <# - .SYNOPSIS - Move old installation files to backup folder based on manifest - .DESCRIPTION - This function finds the previous installation manifest for the given path, - moves (cuts) the old files to a backup folder, and removes empty directories. - #> - param( - [Parameter(Mandatory=$true)] - [string]$InstallationPath, - - [Parameter(Mandatory=$true)] - [string]$InstallationMode - ) - - # Find existing manifest for this installation path - $manifests = Get-AllInstallManifests - - if (-not $manifests -or $manifests.Count -eq 0) { - Write-ColorOutput "No previous installation found - proceeding with fresh installation" $ColorInfo - return $null - } - - # Normalize paths for comparison - $targetPath = $InstallationPath.TrimEnd('\', '/').ToLower() - - # Find manifest matching this installation path and mode - $oldManifest = $manifests | Where-Object { - $manifestPath = $_.installation_path.TrimEnd('\', '/').ToLower() - $manifestPath -eq $targetPath -and $_.installation_mode -eq $InstallationMode - } | Select-Object -First 1 - - if (-not $oldManifest) { - Write-ColorOutput "No previous $InstallationMode installation found at this path" $ColorInfo - return $null - } - - Write-ColorOutput "Found previous installation from $($oldManifest.installation_date)" $ColorInfo - Write-ColorOutput "Files: $($oldManifest.files_count), Directories: $($oldManifest.directories_count)" $ColorInfo - - # Create backup folder - $timestamp = Get-Date -Format "yyyyMMdd-HHmmss" - $backupDirName = "claude-backup-old-$timestamp" - $backupPath = Join-Path $InstallationPath $backupDirName - - if (-not (Test-Path $backupPath)) { - New-Item -ItemType Directory -Path $backupPath -Force | Out-Null - Write-ColorOutput "Created backup folder: $backupPath" $ColorSuccess - } - - $movedFiles = 0 - $movedDirs = 0 - $failedItems = @() - - # Move files first (from manifest) - Write-ColorOutput "Moving old installation files to backup..." $ColorInfo - foreach ($fileEntry in $oldManifest.files) { - $filePath = $fileEntry.path - - if (Test-Path $filePath) { - try { - # Calculate relative path from installation root (generic approach) - # This works for any directory structure without hardcoding - $normalizedFilePath = $filePath.ToLower() - $normalizedInstallPath = $InstallationPath.ToLower() - - $relativeSubPath = "" - $fileName = Split-Path $filePath -Leaf - $relativeDir = "" - - # Check if file is under installation path - if ($normalizedFilePath.StartsWith($normalizedInstallPath)) { - # Get path relative to installation root - $relativeSubPath = $filePath.Substring($InstallationPath.Length).TrimStart('\', '/') - - # Separate directory from filename - if ($relativeSubPath -ne $fileName) { - $relativeDir = Split-Path -Path $relativeSubPath -Parent - } - } - - # Create backup subdirectory structure - $backupSubDir = $backupPath - if (-not [string]::IsNullOrEmpty($relativeDir)) { - $backupSubDir = Join-Path $backupPath $relativeDir - if (-not (Test-Path $backupSubDir)) { - New-Item -ItemType Directory -Path $backupSubDir -Force | Out-Null - } - } - - $backupFilePath = Join-Path $backupSubDir $fileName - - # Move (cut) instead of copy - Move-Item -Path $filePath -Destination $backupFilePath -Force -ErrorAction Stop - $movedFiles++ - } catch { - Write-ColorOutput " WARNING: Failed to move file: $filePath - $($_.Exception.Message)" $ColorWarning - $failedItems += $filePath - } - } - } - - # Remove empty directories (in reverse order to handle nested dirs) - Write-ColorOutput "Cleaning up empty directories..." $ColorInfo - $sortedDirs = $oldManifest.directories | Sort-Object { $_.path.Length } -Descending - - foreach ($dirEntry in $sortedDirs) { - $dirPath = $dirEntry.path - - if (Test-Path $dirPath) { - try { - # Check if directory is empty - $dirContents = Get-ChildItem -Path $dirPath -Force -ErrorAction SilentlyContinue - - if (-not $dirContents -or ($dirContents | Measure-Object).Count -eq 0) { - Remove-Item -Path $dirPath -Force -ErrorAction Stop - Write-ColorOutput " Removed empty directory: $dirPath" $ColorInfo - $movedDirs++ - } else { - Write-ColorOutput " Directory not empty (preserved): $dirPath" $ColorInfo - } - } catch { - # Silently continue if directory removal fails - } - } - } - - # Note: Old manifest will be automatically removed by Save-InstallManifest - # via Remove-OldManifestsForPath to ensure robust cleanup - - Write-Host "" - Write-ColorOutput "Old installation cleanup summary:" $ColorInfo - Write-Host " Files moved: $movedFiles" - Write-Host " Directories removed: $movedDirs" - Write-Host " Backup location: $backupPath" - - if ($failedItems.Count -gt 0) { - Write-ColorOutput " Failed items: $($failedItems.Count)" $ColorWarning - } - - Write-Host "" - - # Return backup path for reference - return $backupPath -} - -function Get-AllInstallManifests { - <# - .SYNOPSIS - Get all installation manifests (only latest per installation path) - #> - - # Migrate legacy manifest if exists - Migrate-LegacyManifest - - if (-not (Test-Path $script:ManifestDir)) { - return @() - } - - try { - # Get both new (manifest-*) and old (install-*) format manifest files for backward compatibility - $manifestFiles = @(Get-ChildItem -Path $script:ManifestDir -Filter "*-*.json" -File | - Where-Object { $_.Name -match '^(manifest|install)-' } | - Sort-Object LastWriteTime -Descending) - - # Simple array to hold results - $results = @() - - foreach ($file in $manifestFiles) { - try { - $manifestJson = Get-Content -Path $file.FullName -Raw -Encoding utf8 - $manifest = $manifestJson | ConvertFrom-Json - - # Safely get array counts - $filesCount = 0 - $dirsCount = 0 - - if ($manifest.files) { - if ($manifest.files -is [System.Array]) { - $filesCount = $manifest.files.Count - } else { - $filesCount = 1 - } - } - - if ($manifest.directories) { - if ($manifest.directories -is [System.Array]) { - $dirsCount = $manifest.directories.Count - } else { - $dirsCount = 1 - } - } - - # Create PSCustomObject instead of hashtable for better compatibility - $manifestObj = [PSCustomObject]@{ - manifest_id = if ($manifest.manifest_id) { $manifest.manifest_id } else { $file.BaseName } - manifest_file = $file.FullName - version = if ($manifest.version) { $manifest.version } else { "1.0" } - installation_mode = if ($manifest.installation_mode) { $manifest.installation_mode } else { "Unknown" } - installation_path = if ($manifest.installation_path) { $manifest.installation_path } else { "" } - installation_date = if ($manifest.installation_date) { $manifest.installation_date } else { $file.LastWriteTime.ToString("yyyy-MM-ddTHH:mm:ssZ") } - installer_version = if ($manifest.installer_version) { $manifest.installer_version } else { "unknown" } - files = if ($manifest.files) { @($manifest.files) } else { @() } - directories = if ($manifest.directories) { @($manifest.directories) } else { @() } - files_count = $filesCount - directories_count = $dirsCount - application_version = 'unknown' - } - - # Read application version from version.json file - try { - $installPath = $manifestObj.installation_path - if ($installPath) { - $versionJsonPath = Join-Path (Join-Path $installPath ".claude") "version.json" - - if (Test-Path $versionJsonPath) { - $versionJsonContent = Get-Content -Path $versionJsonPath -Raw -Encoding utf8 - $versionInfo = $versionJsonContent | ConvertFrom-Json - if ($versionInfo.version) { - $manifestObj.application_version = $versionInfo.version - } - } - } - } catch { - # Silently fail - 'unknown' will be used - } - - # Add to results array - $results += $manifestObj - - } catch { - Write-ColorOutput "WARNING: Failed to load manifest $($file.Name): $($_.Exception.Message)" $ColorWarning - } - } - - # Group by installation_path and keep only the latest per path - $pathGroups = @{} - foreach ($m in $results) { - $normalizedPath = if ($m.installation_path) { - $m.installation_path.TrimEnd('\', '/').ToLower() - } else { - "" - } - - if (-not $pathGroups.ContainsKey($normalizedPath)) { - $pathGroups[$normalizedPath] = @() - } - $pathGroups[$normalizedPath] += $m - } - - # Select the latest manifest for each path - $finalResults = @() - foreach ($pathKey in $pathGroups.Keys) { - $groupManifests = $pathGroups[$pathKey] - - # Sort by installation_date descending and take the first - $latest = $groupManifests | Sort-Object { - try { [DateTime]::Parse($_.installation_date) } catch { [DateTime]::MinValue } - } -Descending | Select-Object -First 1 - - if ($latest) { - $finalResults += $latest - } - } - - # Sort final results by installation_date descending - if ($finalResults.Count -eq 0) { - return @() - } - - # Force array output from Sort-Object - $sortedResults = @($finalResults | Sort-Object { - try { [DateTime]::Parse($_.installation_date) } catch { [DateTime]::MinValue } - } -Descending) - - # Use comma operator to prevent PowerShell from unwrapping single-element array - return ,$sortedResults - - } catch { - Write-ColorOutput "ERROR: Failed to list installation manifests: $($_.Exception.Message)" $ColorError - return @() - } -} - -# ============================================================================ -# UNINSTALLATION FUNCTIONS -# ============================================================================ - -function Uninstall-ClaudeWorkflow { - <# - .SYNOPSIS - Uninstall Claude Code Workflow based on installation manifest - #> - - Write-ColorOutput "Claude Code Workflow System Uninstaller" $ColorInfo - Write-ColorOutput "========================================" $ColorInfo - Write-Host "" - - # Load all manifests - $manifests = Get-AllInstallManifests - - if (-not $manifests -or $manifests.Count -eq 0) { - Write-ColorOutput "ERROR: No installation manifests found in: $script:ManifestDir" $ColorError - Write-ColorOutput "Cannot proceed with uninstallation without manifest." $ColorError - Write-Host "" - Write-ColorOutput "Manual uninstallation instructions:" $ColorInfo - Write-Host "For Global installation, remove these directories:" - Write-Host " - ~/.claude/agents" - Write-Host " - ~/.claude/commands" - Write-Host " - ~/.claude/output-styles" - Write-Host " - ~/.claude/workflows" - Write-Host " - ~/.claude/scripts" - Write-Host " - ~/.claude/prompt-templates" - Write-Host " - ~/.claude/python_script" - Write-Host " - ~/.claude/skills" - Write-Host " - ~/.claude/version.json" - Write-Host " - ~/.claude/CLAUDE.md" - Write-Host " - ~/.codex" - Write-Host " - ~/.gemini" - Write-Host " - ~/.qwen" - return $false - } - - # Display available installations - Write-ColorOutput "Found $($manifests.Count) installation(s):" $ColorInfo - Write-Host "" - - # If only one manifest, use it directly - $selectedManifest = $null - if ($manifests.Count -eq 1) { - $selectedManifest = $manifests[0] - - # Display simplified info for single installation - $versionStr = if ($selectedManifest.application_version -and $selectedManifest.application_version -ne 'unknown') { - "v$($selectedManifest.application_version)" - } else { - "Version Unknown" - } - Write-ColorOutput "Found installation: $versionStr - $($selectedManifest.installation_path)" $ColorInfo - } else { - # Multiple manifests - let user choose (simplified: only version and path) - $options = @() - for ($i = 0; $i -lt $manifests.Count; $i++) { - $m = $manifests[$i] - - # Get version string - $versionStr = if ($m.application_version -and $m.application_version -ne 'unknown') { - "v$($m.application_version)" - } else { - "Version Unknown" - } - - # Get path string - $pathStr = if ($m.installation_path) { - $m.installation_path - } else { - "Path Unknown" - } - - # Simplified format: only version and path - $option = "$($i + 1). $versionStr - $pathStr" - $options += $option - } - $options += "Cancel - Don't uninstall anything" - - Write-Host "" - $selection = Get-UserChoiceWithArrows -Prompt "Select installation to uninstall:" -Options $options -DefaultIndex 0 - - if ($selection -like "Cancel*") { - Write-ColorOutput "Uninstallation cancelled." $ColorWarning - return $false - } - - # Parse selection to get index - $selectedIndex = [int]($selection.Split('.')[0]) - 1 - $selectedManifest = $manifests[$selectedIndex] - } - - # Display selected installation info (simplified - only path and version) - Write-Host "" - Write-ColorOutput "Uninstallation Target:" $ColorInfo - - # Display application version (handle unknown version) - $displayVersion = if ($selectedManifest.application_version -and $selectedManifest.application_version -ne 'unknown') { - "v$($selectedManifest.application_version)" - } else { - "Version: Unknown" - } - Write-Host " $displayVersion" - Write-Host " Path: $($selectedManifest.installation_path)" - Write-Host "" - - # Confirm uninstallation - if (-not (Confirm-Action "Do you want to uninstall this installation?" -DefaultYes:$false)) { - Write-ColorOutput "Uninstallation cancelled." $ColorWarning - return $false - } - - # Use the selected manifest for uninstallation - $manifest = $selectedManifest - - $removedFiles = 0 - $failedItems = @() - $skippedFiles = 0 - - # Check if this is a Path mode uninstallation and if Global installation exists - $isPathMode = ($manifest.installation_mode -eq "Path") - $hasGlobalInstallation = $false - - if ($isPathMode) { - # Check if any Global installation manifest exists - if (Test-Path $script:ManifestDir) { - $globalManifestFiles = Get-ChildItem -Path $script:ManifestDir -Filter "manifest-global-*.json" -File - if ($globalManifestFiles -and $globalManifestFiles.Count -gt 0) { - $hasGlobalInstallation = $true - Write-ColorOutput "Found Global installation, global files will be preserved" $ColorWarning - Write-Host "" - } - } - } - - # Only remove files listed in manifest - do NOT remove directories - Write-ColorOutput "Removing installed files..." $ColorInfo - foreach ($fileEntry in $manifest.files) { - $filePath = $fileEntry.path - - # For Path mode uninstallation, skip global files if Global installation exists - if ($isPathMode -and $hasGlobalInstallation) { - $userProfile = [Environment]::GetFolderPath("UserProfile") - $globalClaudeDir = Join-Path $userProfile ".claude" - $normalizedFilePath = $filePath.ToLower() - $normalizedGlobalDir = $globalClaudeDir.ToLower() - - # Skip files under global .claude directory - if ($normalizedFilePath.StartsWith($normalizedGlobalDir)) { - Write-ColorOutput " Skipping global file (Global installation exists): $filePath" $ColorInfo - $skippedFiles++ - continue - } - } - - if (Test-Path $filePath) { - try { - Remove-Item -Path $filePath -Force -ErrorAction Stop - $removedFiles++ - } catch { - Write-ColorOutput " WARNING: Failed to remove: $filePath" $ColorWarning - $failedItems += $filePath - } - } - } - - # Display removal summary - if ($skippedFiles -gt 0) { - Write-ColorOutput "Removed $removedFiles files, skipped $skippedFiles global files" $ColorSuccess - } else { - Write-ColorOutput "Removed $removedFiles files" $ColorSuccess - } - - # Remove manifest file - if (Test-Path $manifest.manifest_file) { - try { - Remove-Item -Path $manifest.manifest_file -Force - Write-ColorOutput "Removed installation manifest: $($manifest.manifest_id)" $ColorSuccess - } catch { - Write-ColorOutput "WARNING: Failed to remove manifest file" $ColorWarning - } - } - - # Show summary - Write-Host "" - if ($failedItems.Count -gt 0) { - Write-ColorOutput "Failed to remove $($failedItems.Count) files" $ColorWarning - } - - if ($skippedFiles -gt 0) { - Write-ColorOutput "Note: $skippedFiles global files were preserved due to existing Global installation" $ColorInfo - } - - if ($failedItems.Count -eq 0) { - $summaryMsg = if ($skippedFiles -gt 0) { - "Uninstallation complete! Removed $removedFiles files, preserved $skippedFiles global files." - } else { - "Uninstallation complete! Removed $removedFiles files." - } - Write-ColorOutput $summaryMsg $ColorSuccess - } else { - Write-ColorOutput "Uninstallation completed with warnings." $ColorWarning - } - - return $true -} - -function Create-VersionJson { - param( - [string]$TargetClaudeDir, - [string]$InstallationMode - ) - - # Determine version from source parameter (passed from install-remote.ps1) - $versionNumber = if ($SourceVersion) { $SourceVersion } else { $DefaultVersion } - $sourceBranch = if ($SourceBranch) { $SourceBranch } else { "unknown" } - $commitSha = if ($SourceCommit) { $SourceCommit } else { "unknown" } - - # Create version.json content - $versionInfo = @{ - version = $versionNumber - commit_sha = $commitSha - installation_mode = $InstallationMode - installation_path = $TargetClaudeDir - installation_date_utc = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") - source_branch = $sourceBranch - installer_version = $ScriptVersion - } - - $versionJsonPath = Join-Path $TargetClaudeDir "version.json" - - try { - $versionInfo | ConvertTo-Json | Out-File -FilePath $versionJsonPath -Encoding utf8 -Force - Write-ColorOutput "Created version.json: $versionNumber ($commitSha) - $InstallationMode" $ColorSuccess - return $true - } catch { - Write-ColorOutput "WARNING: Failed to create version.json: $($_.Exception.Message)" $ColorWarning - return $false - } -} - -function Install-Global { - Write-ColorOutput "Installing Claude Code Workflow System globally..." $ColorInfo - - # Determine user profile directory - $userProfile = [Environment]::GetFolderPath("UserProfile") - $globalClaudeDir = Join-Path $userProfile ".claude" - $globalClaudeMd = Join-Path $globalClaudeDir "CLAUDE.md" - $globalCodexDir = Join-Path $userProfile ".codex" - $globalGeminiDir = Join-Path $userProfile ".gemini" - $globalQwenDir = Join-Path $userProfile ".qwen" - - Write-ColorOutput "Global installation path: $userProfile" $ColorInfo - - # Clean up old installation before proceeding - Write-Host "" - Write-ColorOutput "Checking for previous installation..." $ColorInfo - $oldBackupPath = Move-OldInstallation -InstallationPath $userProfile -InstallationMode "Global" - - if ($oldBackupPath) { - Write-ColorOutput "Previous installation moved to: $oldBackupPath" $ColorSuccess - } - - # Initialize manifest - $manifest = New-InstallManifest -InstallationMode "Global" -InstallationPath $userProfile - - # Source paths - $sourceDir = $PSScriptRoot - $sourceClaudeDir = Join-Path $sourceDir ".claude" - $sourceClaudeMd = Join-Path $sourceDir "CLAUDE.md" - $sourceCodexDir = Join-Path $sourceDir ".codex" - $sourceGeminiDir = Join-Path $sourceDir ".gemini" - $sourceQwenDir = Join-Path $sourceDir ".qwen" - - # Create backup folder if needed (default behavior unless NoBackup is specified) - $backupFolder = $null - if (-not $NoBackup) { - if ((Test-Path $globalClaudeDir) -or (Test-Path $globalCodexDir) -or (Test-Path $globalGeminiDir) -or (Test-Path $globalQwenDir)) { - $existingFiles = @() - if (Test-Path $globalClaudeDir) { - $existingFiles += Get-ChildItem $globalClaudeDir -Recurse -File -ErrorAction SilentlyContinue - } - if (Test-Path $globalCodexDir) { - $existingFiles += Get-ChildItem $globalCodexDir -Recurse -File -ErrorAction SilentlyContinue - } - if (Test-Path $globalGeminiDir) { - $existingFiles += Get-ChildItem $globalGeminiDir -Recurse -File -ErrorAction SilentlyContinue - } - if (Test-Path $globalQwenDir) { - $existingFiles += Get-ChildItem $globalQwenDir -Recurse -File -ErrorAction SilentlyContinue - } - if (($existingFiles -and ($existingFiles | Measure-Object).Count -gt 0)) { - $backupFolder = Get-BackupDirectory -TargetDirectory $userProfile - Write-ColorOutput "Backup folder created: $backupFolder" $ColorInfo - } - } elseif (Test-Path $globalClaudeMd) { - # Create backup folder even if .claude directory doesn't exist but CLAUDE.md does - $backupFolder = Get-BackupDirectory -TargetDirectory $userProfile - Write-ColorOutput "Backup folder created: $backupFolder" $ColorInfo - } - } - - # Merge .claude directory (incremental overlay - preserves user files) - Write-ColorOutput "Installing .claude directory (incremental merge)..." $ColorInfo - $claudeInstalled = Merge-DirectoryContents -Source $sourceClaudeDir -Destination $globalClaudeDir -Description ".claude directory" -BackupFolder $backupFolder - - # Track .claude directory in manifest (bulk add) - if ($claudeInstalled) { - Add-ManifestEntriesBulk -Manifest $manifest -SourceDirectory $sourceClaudeDir -TargetDirectory $globalClaudeDir -Description ".claude files" - } - - # Handle CLAUDE.md file in .claude directory - Write-ColorOutput "Installing CLAUDE.md to global .claude directory..." $ColorInfo - $claudeMdInstalled = Copy-FileToDestination -Source $sourceClaudeMd -Destination $globalClaudeMd -Description "CLAUDE.md" -BackupFolder $backupFolder - - # Track CLAUDE.md in manifest - if ($claudeMdInstalled) { - Add-ManifestEntry -Manifest $manifest -Path $globalClaudeMd -Type "File" - } - - # Backup critical config files in .codex directory before installation - Backup-CriticalConfigFiles -TargetDirectory $globalCodexDir -BackupFolder $backupFolder -FileNames @("AGENTS.md") - - # Merge .codex directory (incremental overlay - preserves user files) - Write-ColorOutput "Installing .codex directory (incremental merge)..." $ColorInfo - $codexInstalled = Merge-DirectoryContents -Source $sourceCodexDir -Destination $globalCodexDir -Description ".codex directory" -BackupFolder $backupFolder - - # Track .codex directory in manifest (bulk add) - if ($codexInstalled) { - Add-ManifestEntriesBulk -Manifest $manifest -SourceDirectory $sourceCodexDir -TargetDirectory $globalCodexDir -Description ".codex files" - } - - # Backup critical config files in .gemini directory before installation - Backup-CriticalConfigFiles -TargetDirectory $globalGeminiDir -BackupFolder $backupFolder -FileNames @("GEMINI.md", "CLAUDE.md") - - # Merge .gemini directory (incremental overlay - preserves user files) - Write-ColorOutput "Installing .gemini directory (incremental merge)..." $ColorInfo - $geminiInstalled = Merge-DirectoryContents -Source $sourceGeminiDir -Destination $globalGeminiDir -Description ".gemini directory" -BackupFolder $backupFolder - - # Track .gemini directory in manifest (bulk add) - if ($geminiInstalled) { - Add-ManifestEntriesBulk -Manifest $manifest -SourceDirectory $sourceGeminiDir -TargetDirectory $globalGeminiDir -Description ".gemini files" - } - - # Backup critical config files in .qwen directory before installation - Backup-CriticalConfigFiles -TargetDirectory $globalQwenDir -BackupFolder $backupFolder -FileNames @("QWEN.md") - - # Merge .qwen directory (incremental overlay - preserves user files) - Write-ColorOutput "Installing .qwen directory (incremental merge)..." $ColorInfo - $qwenInstalled = Merge-DirectoryContents -Source $sourceQwenDir -Destination $globalQwenDir -Description ".qwen directory" -BackupFolder $backupFolder - - # Track .qwen directory in manifest (bulk add) - if ($qwenInstalled) { - Add-ManifestEntriesBulk -Manifest $manifest -SourceDirectory $sourceQwenDir -TargetDirectory $globalQwenDir -Description ".qwen files" - } - - # Create version.json in global .claude directory - Write-ColorOutput "Creating version.json..." $ColorInfo - Create-VersionJson -TargetClaudeDir $globalClaudeDir -InstallationMode "Global" - - if ($backupFolder -and (Test-Path $backupFolder)) { - $backupFiles = Get-ChildItem $backupFolder -Recurse -File -ErrorAction SilentlyContinue - if (-not $backupFiles -or ($backupFiles | Measure-Object).Count -eq 0) { - # Remove empty backup folder - Remove-Item -Path $backupFolder -Force - Write-ColorOutput "Removed empty backup folder" $ColorInfo - } - } - - # Save installation manifest - Save-InstallManifest -Manifest $manifest - - return $true -} - -function Install-Path { - param( - [string]$TargetDirectory - ) - - Write-ColorOutput "Installing Claude Code Workflow System in hybrid mode..." $ColorInfo - Write-ColorOutput "Local path: $TargetDirectory" $ColorInfo - - # Determine user profile directory for global files - $userProfile = [Environment]::GetFolderPath("UserProfile") - $globalClaudeDir = Join-Path $userProfile ".claude" - - Write-ColorOutput "Global path: $userProfile" $ColorInfo - - # Clean up old installation before proceeding - Write-Host "" - Write-ColorOutput "Checking for previous installation..." $ColorInfo - $oldBackupPath = Move-OldInstallation -InstallationPath $TargetDirectory -InstallationMode "Path" - - if ($oldBackupPath) { - Write-ColorOutput "Previous installation moved to: $oldBackupPath" $ColorSuccess - } - - # Initialize manifest - $manifest = New-InstallManifest -InstallationMode "Path" -InstallationPath $TargetDirectory - - # Source paths - $sourceDir = $PSScriptRoot - $sourceClaudeDir = Join-Path $sourceDir ".claude" - $sourceClaudeMd = Join-Path $sourceDir "CLAUDE.md" - $sourceCodexDir = Join-Path $sourceDir ".codex" - $sourceGeminiDir = Join-Path $sourceDir ".gemini" - $sourceQwenDir = Join-Path $sourceDir ".qwen" - - # Local paths - for agents, commands, output-styles, .codex, .gemini, .qwen - $localClaudeDir = Join-Path $TargetDirectory ".claude" - $localCodexDir = Join-Path $TargetDirectory ".codex" - $localGeminiDir = Join-Path $TargetDirectory ".gemini" - $localQwenDir = Join-Path $TargetDirectory ".qwen" - - # Create backup folder if needed - $backupFolder = $null - if (-not $NoBackup) { - if ((Test-Path $localClaudeDir) -or (Test-Path $localCodexDir) -or (Test-Path $localGeminiDir) -or (Test-Path $localQwenDir) -or (Test-Path $globalClaudeDir)) { - $backupFolder = Get-BackupDirectory -TargetDirectory $TargetDirectory - Write-ColorOutput "Backup folder created: $backupFolder" $ColorInfo - } - } - - # Create local .claude directory - if (-not (Test-Path $localClaudeDir)) { - New-Item -ItemType Directory -Path $localClaudeDir -Force | Out-Null - Write-ColorOutput "Created local .claude directory" $ColorSuccess - } - - # Local folders to install (agents, commands, output-styles) - $localFolders = @("agents", "commands", "output-styles") - - Write-ColorOutput "Installing local components (agents, commands, output-styles)..." $ColorInfo - foreach ($folder in $localFolders) { - $sourceFolderPath = Join-Path $sourceClaudeDir $folder - $destFolderPath = Join-Path $localClaudeDir $folder - - if (Test-Path $sourceFolderPath) { - # Use incremental merge for local folders (preserves user customizations) - $folderInstalled = Merge-DirectoryContents -Source $sourceFolderPath -Destination $destFolderPath -Description "$folder folder" -BackupFolder $backupFolder - - # Track local folder in manifest (bulk add) - if ($folderInstalled) { - Add-ManifestEntriesBulk -Manifest $manifest -SourceDirectory $sourceFolderPath -TargetDirectory $destFolderPath -Description "$folder files" - } - } else { - Write-ColorOutput "WARNING: Source folder not found: $folder" $ColorWarning - } - } - - # Global components - exclude local folders (use same efficient method as Global mode) - Write-ColorOutput "Installing global components to $globalClaudeDir..." $ColorInfo - - # Create temporary directory for global files only - $tempGlobalDir = Join-Path ([System.IO.Path]::GetTempPath()) "claude-global-$((Get-Date).Ticks)" - New-Item -ItemType Directory -Path $tempGlobalDir -Force | Out-Null - - try { - # Copy global files to temp directory (excluding local folders) - Write-ColorOutput "Preparing global components..." $ColorInfo - $sourceItems = Get-ChildItem -Path $sourceClaudeDir -Recurse -File | Where-Object { - $relativePath = $_.FullName.Substring($sourceClaudeDir.Length + 1) - $topFolder = $relativePath.Split([System.IO.Path]::DirectorySeparatorChar)[0] - $topFolder -notin $localFolders - } - - foreach ($item in $sourceItems) { - $relativePath = $item.FullName.Substring($sourceClaudeDir.Length + 1) - $tempDestPath = Join-Path $tempGlobalDir $relativePath - $tempDestDir = Split-Path $tempDestPath -Parent - - if (-not (Test-Path $tempDestDir)) { - New-Item -ItemType Directory -Path $tempDestDir -Force | Out-Null - } - Copy-Item -Path $item.FullName -Destination $tempDestPath -Force - } - - # Use bulk merge method (same as Global mode - fast!) - $globalInstalled = Merge-DirectoryContents -Source $tempGlobalDir -Destination $globalClaudeDir -Description "global components" -BackupFolder $backupFolder - - # Track global files in manifest using bulk method (fast!) - if ($globalInstalled) { - Add-ManifestEntriesBulk -Manifest $manifest -SourceDirectory $tempGlobalDir -TargetDirectory $globalClaudeDir -Description "global files" - } - } finally { - # Clean up temp directory - if (Test-Path $tempGlobalDir) { - Remove-Item -Path $tempGlobalDir -Recurse -Force -ErrorAction SilentlyContinue - } - } - - # Handle CLAUDE.md file in global .claude directory - $globalClaudeMd = Join-Path $globalClaudeDir "CLAUDE.md" - Write-ColorOutput "Installing CLAUDE.md to global .claude directory..." $ColorInfo - $claudeMdInstalled = Copy-FileToDestination -Source $sourceClaudeMd -Destination $globalClaudeMd -Description "CLAUDE.md" -BackupFolder $backupFolder - - # Track CLAUDE.md in manifest - if ($claudeMdInstalled) { - Add-ManifestEntry -Manifest $manifest -Path $globalClaudeMd -Type "File" - } - - # Backup critical config files in .codex directory before installation - Backup-CriticalConfigFiles -TargetDirectory $localCodexDir -BackupFolder $backupFolder -FileNames @("AGENTS.md") - - # Merge .codex directory to local location (incremental overlay - preserves user files) - Write-ColorOutput "Installing .codex directory to local location (incremental merge)..." $ColorInfo - $codexInstalled = Merge-DirectoryContents -Source $sourceCodexDir -Destination $localCodexDir -Description ".codex directory" -BackupFolder $backupFolder - - # Track .codex directory in manifest (bulk add) - if ($codexInstalled) { - Add-ManifestEntriesBulk -Manifest $manifest -SourceDirectory $sourceCodexDir -TargetDirectory $localCodexDir -Description ".codex files" - } - - # Backup critical config files in .gemini directory before installation - Backup-CriticalConfigFiles -TargetDirectory $localGeminiDir -BackupFolder $backupFolder -FileNames @("GEMINI.md", "CLAUDE.md") - - # Merge .gemini directory to local location (incremental overlay - preserves user files) - Write-ColorOutput "Installing .gemini directory to local location (incremental merge)..." $ColorInfo - $geminiInstalled = Merge-DirectoryContents -Source $sourceGeminiDir -Destination $localGeminiDir -Description ".gemini directory" -BackupFolder $backupFolder - - # Track .gemini directory in manifest (bulk add) - if ($geminiInstalled) { - Add-ManifestEntriesBulk -Manifest $manifest -SourceDirectory $sourceGeminiDir -TargetDirectory $localGeminiDir -Description ".gemini files" - } - - # Backup critical config files in .qwen directory before installation - Backup-CriticalConfigFiles -TargetDirectory $localQwenDir -BackupFolder $backupFolder -FileNames @("QWEN.md") - - # Merge .qwen directory to local location (incremental overlay - preserves user files) - Write-ColorOutput "Installing .qwen directory to local location (incremental merge)..." $ColorInfo - $qwenInstalled = Merge-DirectoryContents -Source $sourceQwenDir -Destination $localQwenDir -Description ".qwen directory" -BackupFolder $backupFolder - - # Track .qwen directory in manifest (bulk add) - if ($qwenInstalled) { - Add-ManifestEntriesBulk -Manifest $manifest -SourceDirectory $sourceQwenDir -TargetDirectory $localQwenDir -Description ".qwen files" - } - - # Create version.json in local .claude directory - Write-ColorOutput "Creating version.json in local directory..." $ColorInfo - Create-VersionJson -TargetClaudeDir $localClaudeDir -InstallationMode "Path" - - # Also create version.json in global .claude directory - Write-ColorOutput "Creating version.json in global directory..." $ColorInfo - Create-VersionJson -TargetClaudeDir $globalClaudeDir -InstallationMode "Global" - - if ($backupFolder -and (Test-Path $backupFolder)) { - $backupFiles = Get-ChildItem $backupFolder -Recurse -File -ErrorAction SilentlyContinue - if (-not $backupFiles -or ($backupFiles | Measure-Object).Count -eq 0) { - Remove-Item -Path $backupFolder -Force - Write-ColorOutput "Removed empty backup folder" $ColorInfo - } - } - - # Save installation manifest - Save-InstallManifest -Manifest $manifest - - return $true -} - - -function Get-InstallationMode { - if ($InstallMode) { - Write-ColorOutput "Installation mode: $InstallMode" $ColorInfo - return $InstallMode - } - - $modes = @( - "Global - Install to user profile (~/.claude/)", - "Path - Install to custom directory (partial local + global)" - ) - - Write-Host "" - $selection = Get-UserChoiceWithArrows -Prompt "Choose installation mode:" -Options $modes -DefaultIndex 0 - - if ($selection -like "Global*") { - return "Global" - } elseif ($selection -like "Path*") { - return "Path" - } - - return "Global" -} - -function Get-InstallationPath { - param( - [string]$Mode - ) - - if ($Mode -eq "Global") { - return [Environment]::GetFolderPath("UserProfile") - } - - if ($TargetPath) { - if (Test-Path $TargetPath) { - return $TargetPath - } - Write-ColorOutput "WARNING: Specified target path does not exist: $TargetPath" $ColorWarning - } - - # Interactive path selection - do { - Write-Host "" - Write-ColorOutput "Enter the target directory path for installation:" $ColorPrompt - Write-ColorOutput "(This will install agents, commands, output-styles locally, other files globally)" $ColorInfo - $path = Read-Host "Path" - - if ([string]::IsNullOrWhiteSpace($path)) { - Write-ColorOutput "Path cannot be empty" $ColorWarning - continue - } - - # Expand environment variables and relative paths - $expandedPath = [System.Environment]::ExpandEnvironmentVariables($path) - $expandedPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($expandedPath) - - if (Test-Path $expandedPath) { - return $expandedPath - } - - Write-ColorOutput "Path does not exist: $expandedPath" $ColorWarning - if (Confirm-Action "Create this directory?" -DefaultYes) { - try { - New-Item -ItemType Directory -Path $expandedPath -Force | Out-Null - Write-ColorOutput "Directory created successfully" $ColorSuccess - return $expandedPath - } catch { - Write-ColorOutput "Failed to create directory: $($_.Exception.Message)" $ColorError - } - } - } while ($true) -} - - -function Show-Summary { - param( - [string]$Mode, - [string]$Path, - [bool]$Success - ) - - Write-Host "" - if ($Success) { - Write-ColorOutput "Installation completed successfully!" $ColorSuccess - } else { - Write-ColorOutput "Installation completed with warnings" $ColorWarning - } - - Write-ColorOutput "Installation Details:" $ColorInfo - Write-Host " Mode: $Mode" - - if ($Mode -eq "Path") { - Write-Host " Local Path: $Path" - Write-Host " Global Path: $([Environment]::GetFolderPath('UserProfile'))" - Write-Host " Local Components: agents, commands, output-styles, .codex, .gemini, .qwen" - Write-Host " Global Components: workflows, scripts, python_script, etc." - } else { - Write-Host " Path: $Path" - Write-Host " Global Components: .claude, .codex, .gemini, .qwen" - } - - if ($NoBackup) { - Write-Host " Backup: Disabled (no backup created)" - } elseif ($BackupAll) { - Write-Host " Backup: Enabled (automatic backup of all existing files)" - } else { - Write-Host " Backup: Enabled (default behavior)" - } - - Write-Host "" - Write-ColorOutput "Next steps:" $ColorInfo - Write-Host "1. Review CLAUDE.md - Customize guidelines for your project" - Write-Host "2. Review .codex/Agent.md - Codex agent execution protocol" - Write-Host "3. Review .gemini/CLAUDE.md - Gemini agent execution protocol" - Write-Host "4. Review .qwen/QWEN.md - Qwen agent execution protocol" - Write-Host "5. Configure settings - Edit .claude/settings.local.json as needed" - Write-Host "6. Start using Claude Code with Agent workflow coordination!" - Write-Host "7. Use /workflow commands for task execution" - Write-Host "8. Use /update-memory commands for memory system management" - - Write-Host "" - Write-ColorOutput "Documentation: https://github.com/catlog22/Claude-CCW" $ColorInfo - Write-ColorOutput "Features: Unified workflow system with comprehensive file output generation" $ColorInfo -} - -function Main { - # Use SourceVersion parameter if provided, otherwise use default - $installVersion = if ($SourceVersion) { $SourceVersion } else { $DefaultVersion } - - # Show banner first - Show-Banner - - # Check for uninstall mode from parameter or ask user interactively - $operationMode = "Install" - - if ($Uninstall) { - $operationMode = "Uninstall" - } elseif (-not $NonInteractive -and -not $InstallMode) { - # Interactive mode selection - Write-Host "" - $operations = @( - "Install - Install Claude Code Workflow System", - "Uninstall - Remove Claude Code Workflow System" - ) - $selection = Get-UserChoiceWithArrows -Prompt "Choose operation:" -Options $operations -DefaultIndex 0 - - if ($selection -like "Uninstall*") { - $operationMode = "Uninstall" - } - } - - # Handle uninstall mode - if ($operationMode -eq "Uninstall") { - $result = Uninstall-ClaudeWorkflow - - if (-not $NonInteractive) { - Write-Host "" - Write-ColorOutput "Press any key to exit..." $ColorPrompt - $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") - } - - return $(if ($result) { 0 } else { 1 }) - } - - # Continue with installation - Show-Header -InstallVersion $installVersion - - # Test prerequisites - Write-ColorOutput "Checking system requirements..." $ColorInfo - if (-not (Test-Prerequisites)) { - Write-ColorOutput "Prerequisites check failed!" $ColorError - return 1 - } - - try { - # Get installation mode - $mode = Get-InstallationMode - $installPath = "" - $success = $false - - if ($mode -eq "Global") { - $installPath = [Environment]::GetFolderPath("UserProfile") - $result = Install-Global - $success = $result -eq $true - } - elseif ($mode -eq "Path") { - $installPath = Get-InstallationPath -Mode $mode - $result = Install-Path -TargetDirectory $installPath - $success = $result -eq $true - } - - Show-Summary -Mode $mode -Path $installPath -Success ([bool]$success) - - # Wait for user confirmation before exit in interactive mode - if (-not $NonInteractive) { - Write-Host "" - Write-ColorOutput "Installation completed. Press any key to exit..." $ColorPrompt - $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") - } - - if ($success) { - return 0 - } else { - return 1 - } - - } catch { - Write-ColorOutput "CRITICAL ERROR: $($_.Exception.Message)" $ColorError - Write-ColorOutput "Stack trace: $($_.ScriptStackTrace)" $ColorError - - # Wait for user confirmation before exit in interactive mode - if (-not $NonInteractive) { - Write-Host "" - Write-ColorOutput "An error occurred. Press any key to exit..." $ColorPrompt - $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") - } - - return 1 - } -} - -# Run main function -exit (Main) \ No newline at end of file diff --git a/install-remote.ps1 b/install-remote.ps1 deleted file mode 100644 index b50e53c2..00000000 --- a/install-remote.ps1 +++ /dev/null @@ -1,763 +0,0 @@ -#!/usr/bin/env pwsh -<# -.SYNOPSIS - Claude Code Workflow (CCW) - Remote Installation Script - -.DESCRIPTION - One-liner remote installation for Claude Code Workflow system. - Downloads and installs CCW from GitHub with flexible version selection. - -.PARAMETER Version - Installation version type: - - "stable" (default): Latest stable release tag - - "latest": Latest main branch (development version) - - "branch": Install from specific branch - -.PARAMETER Tag - Specific release tag to install (e.g., "v3.2.0") - Only used when Version is "stable" - -.PARAMETER Branch - Branch name to install from (default: "main") - Only used when Version is "branch" - -.PARAMETER Global - Install to global user directory (~/.claude) - -.PARAMETER Directory - Install to custom directory - -.PARAMETER Force - Skip confirmation prompts - -.PARAMETER NoBackup - Skip backup of existing installation - -.PARAMETER NonInteractive - Run in non-interactive mode - -.PARAMETER BackupAll - Backup all files including git-ignored files - -.EXAMPLE - # Install latest stable release (recommended) - .\install-remote.ps1 - -.EXAMPLE - # Install specific stable version - .\install-remote.ps1 -Version stable -Tag "v3.2.0" - -.EXAMPLE - # Install latest development version - .\install-remote.ps1 -Version latest - -.EXAMPLE - # Install from specific branch - .\install-remote.ps1 -Version branch -Branch "feature/new-feature" - -.EXAMPLE - # Install to global directory without prompts - .\install-remote.ps1 -Global -Force - -.LINK - https://github.com/catlog22/Claude-Code-Workflow -#> - -[CmdletBinding()] -param( - [ValidateSet("stable", "latest", "branch")] - [string]$Version = "stable", - - [string]$Tag = "", - - [string]$Branch = "main", - - [switch]$Global, - - [string]$Directory = "", - - [switch]$Force, - - [switch]$NoBackup, - - [switch]$NonInteractive, - - [switch]$BackupAll -) - -# Set encoding for proper Unicode support -if ($PSVersionTable.PSVersion.Major -ge 6) { - $OutputEncoding = [System.Text.Encoding]::UTF8 - [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 - [Console]::InputEncoding = [System.Text.Encoding]::UTF8 -} else { - # Windows PowerShell 5.1 - $OutputEncoding = [System.Text.Encoding]::UTF8 - chcp 65001 | Out-Null -} - -# Script metadata -$ScriptName = "Claude Code Workflow (CCW) Remote Installer" -$InstallerVersion = "2.2.0" - -# Colors for output -$ColorSuccess = "Green" -$ColorInfo = "Cyan" -$ColorWarning = "Yellow" -$ColorError = "Red" - -function Write-ColorOutput { - param( - [string]$Message, - [string]$Color = "White" - ) - Write-Host $Message -ForegroundColor $Color -} - -function Show-Header { - Write-ColorOutput "==== $ScriptName v$InstallerVersion ====" $ColorInfo - Write-ColorOutput "========================================================" $ColorInfo - Write-Host "" -} - -function Test-Prerequisites { - # Test PowerShell version - if ($PSVersionTable.PSVersion.Major -lt 5) { - Write-ColorOutput "ERROR: PowerShell 5.1 or higher is required" $ColorError - Write-ColorOutput "Current version: $($PSVersionTable.PSVersion)" $ColorError - return $false - } - - # Check for optional but recommended commands - $gitAvailable = $false - try { - $gitVersion = git --version 2>$null - if ($LASTEXITCODE -eq 0) { - Write-ColorOutput "✓ Git available" $ColorSuccess - $gitAvailable = $true - } - } catch { - # Git not found - } - - if (-not $gitAvailable) { - Write-ColorOutput "WARNING: 'git' not found - version detection may be limited" $ColorWarning - Write-ColorOutput "Hint: Install Git for Windows for better version tracking" $ColorInfo - Write-ColorOutput " Download from: https://git-scm.com/download/win" $ColorInfo - Write-Host "" - } - - # Test internet connectivity - try { - $null = Invoke-WebRequest -Uri "https://github.com" -Method Head -TimeoutSec 10 -UseBasicParsing - Write-ColorOutput "✓ Network connection OK" $ColorSuccess - } catch { - Write-ColorOutput "ERROR: Cannot connect to GitHub" $ColorError - Write-ColorOutput "Please check your network connection and try again." $ColorError - Write-Host "" - Write-ColorOutput "Common causes:" $ColorInfo - Write-Host " • Internet connection is down or unstable" - Write-Host " • Firewall or proxy is blocking GitHub access" - Write-Host " • DNS resolution issues" - Write-Host " • GitHub is temporarily unavailable" - Write-Host "" - Write-ColorOutput "Troubleshooting steps:" $ColorInfo - Write-Host " 1. Check your internet connection" - Write-Host " 2. Try accessing https://github.com in your browser" - Write-Host " 3. If using a proxy, configure it properly" - Write-Host " 4. Check firewall settings" - Write-Host " 5. Wait a few minutes and try again" - Write-Host "" - Write-ColorOutput "Error details: $($_.Exception.Message)" $ColorError - return $false - } - - return $true -} - -function Get-TempDirectory { - $tempDir = Join-Path ([System.IO.Path]::GetTempPath()) "claude-code-workflow-install" - if (Test-Path $tempDir) { - Remove-Item -Path $tempDir -Recurse -Force - } - New-Item -Path $tempDir -ItemType Directory -Force | Out-Null - return $tempDir -} - -function Get-LatestRelease { - try { - $apiUrl = "https://api.github.com/repos/catlog22/Claude-Code-Workflow/releases/latest" - $response = Invoke-RestMethod -Uri $apiUrl -UseBasicParsing -TimeoutSec 10 - return $response.tag_name - } catch { - Write-ColorOutput "WARNING: Failed to fetch latest release" $ColorWarning - Write-ColorOutput "Reason: $($_.Exception.Message)" $ColorWarning - Write-ColorOutput "Falling back to 'main' branch" $ColorInfo - return $null - } -} - -function Download-Repository { - param( - [string]$TempDir, - [string]$Version = "stable", - [string]$Branch = "main", - [string]$Tag = "" - ) - - $repoUrl = "https://github.com/catlog22/Claude-Code-Workflow" - - # Determine download URL based on version type - if ($Version -eq "stable") { - # Download latest stable release - if ([string]::IsNullOrEmpty($Tag)) { - $latestTag = Get-LatestRelease - if ($latestTag) { - $Tag = $latestTag - } else { - # Fallback to main branch if API fails - $zipUrl = "$repoUrl/archive/refs/heads/main.zip" - $downloadType = "main branch (fallback)" - } - } - - if (-not [string]::IsNullOrEmpty($Tag)) { - $zipUrl = "$repoUrl/archive/refs/tags/$Tag.zip" - $downloadType = "stable release $Tag" - } - } elseif ($Version -eq "latest") { - # Download latest main branch - $zipUrl = "$repoUrl/archive/refs/heads/main.zip" - $downloadType = "latest main branch" - } else { - # Download specific branch - $zipUrl = "$repoUrl/archive/refs/heads/$Branch.zip" - $downloadType = "branch $Branch" - } - - $zipPath = Join-Path $TempDir "repo.zip" - - Write-ColorOutput "Downloading from GitHub..." $ColorInfo - Write-ColorOutput "Source: $repoUrl" $ColorInfo - Write-ColorOutput "Type: $downloadType" $ColorInfo - - try { - # Download with progress - $progressPreference = $ProgressPreference - $ProgressPreference = 'SilentlyContinue' - - Invoke-WebRequest -Uri $zipUrl -OutFile $zipPath -UseBasicParsing -TimeoutSec 300 - - $ProgressPreference = $progressPreference - - if (Test-Path $zipPath) { - $fileSize = (Get-Item $zipPath).Length - if ($fileSize -eq 0) { - throw "Downloaded file is empty (0 bytes)" - } - Write-ColorOutput "Download complete ($([math]::Round($fileSize/1024/1024, 2)) MB)" $ColorSuccess - return $zipPath - } else { - throw "Downloaded file does not exist" - } - } catch { - Write-Host "" - Write-ColorOutput "ERROR: Download failed" $ColorError - Write-Host "" - Write-ColorOutput "Common causes:" $ColorInfo - Write-Host " • Network connection interrupted during download" - Write-Host " • GitHub API rate limit exceeded" - Write-Host " • Invalid version tag or branch name" - Write-Host " • Temporary GitHub service issues" - Write-Host "" - Write-ColorOutput "Troubleshooting steps:" $ColorInfo - Write-Host " 1. Check your internet connection stability" - Write-Host " 2. Wait a few minutes and try again (rate limit resets)" - Write-Host " 3. Verify the version tag or branch name is correct" - Write-Host " 4. Try a different version (stable/latest)" - Write-Host " 5. Check GitHub status at https://www.githubstatus.com" - Write-Host "" - Write-ColorOutput "Download URL: $zipUrl" $ColorInfo - Write-ColorOutput "Error details: $($_.Exception.Message)" $ColorError - Write-Host "" - return $null - } -} - -function Extract-Repository { - param( - [string]$ZipPath, - [string]$TempDir - ) - - Write-ColorOutput "Extracting files..." $ColorInfo - - try { - # Verify zip file exists and is not empty - if (-not (Test-Path $ZipPath)) { - throw "ZIP file not found: $ZipPath" - } - - $zipSize = (Get-Item $ZipPath).Length - if ($zipSize -eq 0) { - throw "ZIP file is empty (0 bytes)" - } - - # Use .NET to extract zip - Add-Type -AssemblyName System.IO.Compression.FileSystem - [System.IO.Compression.ZipFile]::ExtractToDirectory($ZipPath, $TempDir) - - # Find the extracted directory (usually repo-name-branch) - $extractedDirs = Get-ChildItem -Path $TempDir -Directory - $repoDir = $extractedDirs | Where-Object { $_.Name -like "Claude-Code-Workflow-*" } | Select-Object -First 1 - - if ($repoDir) { - Write-ColorOutput "Extraction complete: $($repoDir.FullName)" $ColorSuccess - return $repoDir.FullName - } else { - throw "Could not find extracted repository directory" - } - } catch { - Write-Host "" - Write-ColorOutput "ERROR: Extraction failed" $ColorError - Write-Host "" - Write-ColorOutput "Common causes:" $ColorInfo - Write-Host " • Downloaded file is corrupted or incomplete" - Write-Host " • ZIP file format is invalid" - Write-Host " • Insufficient disk space" - Write-Host " • Permission issues on temporary directory" - Write-Host "" - Write-ColorOutput "Troubleshooting steps:" $ColorInfo - Write-Host " 1. Try downloading again (network may have interrupted)" - Write-Host " 2. Check available disk space" - Write-Host " 3. Verify temporary directory permissions" - Write-Host " 4. Try running as administrator" - Write-Host "" - Write-ColorOutput "ZIP file: $ZipPath" $ColorInfo - if (Test-Path $ZipPath) { - Write-ColorOutput "ZIP size: $([math]::Round($zipSize/1024/1024, 2)) MB" $ColorInfo - } - Write-ColorOutput "Error details: $($_.Exception.Message)" $ColorError - Write-Host "" - return $null - } -} - -function Invoke-LocalInstaller { - param( - [string]$RepoDir, - [string]$VersionInfo = "", - [string]$BranchInfo = "", - [string]$CommitSha = "" - ) - - $installerPath = Join-Path $RepoDir "Install-Claude.ps1" - - if (-not (Test-Path $installerPath)) { - Write-ColorOutput "ERROR: Install-Claude.ps1 not found" $ColorError - return $false - } - - Write-ColorOutput "Running local installer..." $ColorInfo - Write-Host "" - - # Build parameters for local installer - $params = @{} - if ($Global) { $params["InstallMode"] = "Global" } - if ($Directory) { - $params["InstallMode"] = "Custom" - $params["TargetPath"] = $Directory - } - if ($Force) { $params["Force"] = $Force } - if ($NoBackup) { $params["NoBackup"] = $NoBackup } - if ($NonInteractive) { $params["NonInteractive"] = $NonInteractive } - if ($BackupAll) { $params["BackupAll"] = $BackupAll } - - # Pass version, branch, and commit information - if ($VersionInfo) { $params["SourceVersion"] = $VersionInfo } - if ($BranchInfo) { $params["SourceBranch"] = $BranchInfo } - if ($CommitSha) { $params["SourceCommit"] = $CommitSha } - - try { - # Change to repo directory and run installer - Push-Location $RepoDir - - if ($params.Count -gt 0) { - $paramList = ($params.GetEnumerator() | ForEach-Object { "-$($_.Key) $($_.Value)" }) -join " " - Write-ColorOutput "Executing: & `"$installerPath`" $paramList" $ColorInfo - & $installerPath @params - } else { - Write-ColorOutput "Executing: & `"$installerPath`"" $ColorInfo - & $installerPath - } - - Pop-Location - return $true - } catch { - Pop-Location - Write-ColorOutput "Installation script failed: $($_.Exception.Message)" $ColorError - return $false - } -} - -function Cleanup-TempFiles { - param( - [string]$TempDir - ) - - if (Test-Path $TempDir) { - try { - Remove-Item -Path $TempDir -Recurse -Force - Write-ColorOutput "Temporary files cleaned up" $ColorInfo - } catch { - Write-ColorOutput "WARNING: Failed to clean temporary files: $($_.Exception.Message)" $ColorWarning - } - } -} - -function Wait-ForUserConfirmation { - param( - [string]$Message = "Press any key to continue...", - [switch]$ExitAfter - ) - - if (-not $NonInteractive) { - Write-Host "" - Write-ColorOutput $Message $ColorInfo - $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") - Write-Host "" - } - - if ($ExitAfter) { - exit 0 - } -} - -function Show-VersionMenu { - param( - [string]$LatestStableVersion = "Detecting...", - [string]$LatestStableDate = "", - [string]$LatestCommitId = "", - [string]$LatestCommitDate = "" - ) - - Write-Host "" - Write-ColorOutput "====================================================" $ColorInfo - Write-ColorOutput " Version Selection Menu" $ColorInfo - Write-ColorOutput "====================================================" $ColorInfo - Write-Host "" - - # Option 1: Latest Stable - Write-ColorOutput "1) Latest Stable Release (Recommended)" $ColorSuccess - if ($LatestStableVersion -ne "Detecting..." -and $LatestStableVersion -ne "Unknown") { - Write-Host " |-- Version: $LatestStableVersion" - if ($LatestStableDate) { - Write-Host " |-- Released: $LatestStableDate" - } - Write-Host " \-- Production-ready" - } else { - Write-Host " |-- Version: Auto-detected from GitHub" - Write-Host " \-- Production-ready" - } - Write-Host "" - - # Option 2: Latest Development - Write-ColorOutput "2) Latest Development Version" $ColorWarning - Write-Host " |-- Branch: main" - if ($LatestCommitId -and $LatestCommitDate) { - Write-Host " |-- Commit: $LatestCommitId" - Write-Host " |-- Updated: $LatestCommitDate" - } - Write-Host " |-- Cutting-edge features" - Write-Host " \-- May contain experimental changes" - Write-Host "" - - # Option 3: Specific Version - Write-ColorOutput "3) Specific Release Version" $ColorInfo - Write-Host " |-- Install a specific tagged release" - Write-Host " \-- Recent: v3.2.0, v3.1.0, v3.0.1" - Write-Host "" - - Write-ColorOutput "====================================================" $ColorInfo - Write-Host "" -} - -function Get-UserVersionChoice { - if ($NonInteractive -or $Force) { - # Non-interactive mode: use default stable version - return @{ - Type = "stable" - Tag = $Tag - Branch = $Branch - } - } - - # Detect latest stable version and commit info - Write-ColorOutput "Detecting latest release and commits..." $ColorInfo - $latestVersion = "Unknown" - $latestStableDate = "" - $latestCommitId = "" - $latestCommitDate = "" - - try { - # Get latest release info - $apiUrl = "https://api.github.com/repos/catlog22/Claude-Code-Workflow/releases/latest" - $response = Invoke-RestMethod -Uri $apiUrl -UseBasicParsing -TimeoutSec 5 - $latestVersion = $response.tag_name - - # Parse and format release date to local time - if ($response.published_at) { - $publishDate = [DateTime]::Parse($response.published_at).ToLocalTime() - $latestStableDate = $publishDate.ToString("yyyy-MM-dd HH:mm") - } - - Write-ColorOutput "Latest stable: $latestVersion ($latestStableDate)" $ColorSuccess - } catch { - Write-ColorOutput "Could not detect latest release" $ColorWarning - } - - try { - # Get latest commit info from main branch - $commitUrl = "https://api.github.com/repos/catlog22/Claude-Code-Workflow/commits/main" - $commitResponse = Invoke-RestMethod -Uri $commitUrl -UseBasicParsing -TimeoutSec 5 - $latestCommitId = $commitResponse.sha.Substring(0, 7) - - # Parse and format commit date to local time - if ($commitResponse.commit.committer.date) { - $commitDate = [DateTime]::Parse($commitResponse.commit.committer.date).ToLocalTime() - $latestCommitDate = $commitDate.ToString("yyyy-MM-dd HH:mm") - } - - Write-ColorOutput "Latest commit: $latestCommitId ($latestCommitDate)" $ColorSuccess - } catch { - Write-ColorOutput "Could not detect latest commit" $ColorWarning - } - - Show-VersionMenu -LatestStableVersion $latestVersion -LatestStableDate $latestStableDate -LatestCommitId $latestCommitId -LatestCommitDate $latestCommitDate - - $choice = Read-Host "Select version to install (1-3, default: 1)" - - switch ($choice) { - "2" { - Write-Host "" - Write-ColorOutput "✓ Selected: Latest Development Version (main branch)" $ColorSuccess - return @{ - Type = "latest" - Tag = "" - Branch = "main" - } - } - "3" { - Write-Host "" - Write-ColorOutput "Available recent releases:" $ColorInfo - Write-Host " v3.2.0, v3.1.0, v3.0.1, v3.0.0" - Write-Host "" - $tagInput = Read-Host "Enter version tag (e.g., v3.2.0)" - - if ([string]::IsNullOrWhiteSpace($tagInput)) { - Write-ColorOutput "No tag specified, using latest stable" $ColorWarning - return @{ - Type = "stable" - Tag = "" - Branch = "main" - } - } - - Write-ColorOutput "✓ Selected: Specific Version $tagInput" $ColorSuccess - return @{ - Type = "stable" - Tag = $tagInput - Branch = "main" - } - } - default { - Write-Host "" - if ($latestVersion -ne "Unknown") { - Write-ColorOutput "✓ Selected: Latest Stable Release ($latestVersion)" $ColorSuccess - } else { - Write-ColorOutput "✓ Selected: Latest Stable Release (auto-detect)" $ColorSuccess - } - return @{ - Type = "stable" - Tag = "" - Branch = "main" - } - } - } -} - -function Main { - Show-Header - - Write-ColorOutput "This will download and install Claude Code Workflow System from GitHub." $ColorInfo - Write-Host "" - - # Test prerequisites - Write-ColorOutput "Checking system requirements..." $ColorInfo - if (-not (Test-Prerequisites)) { - Wait-ForUserConfirmation "System check failed! Press any key to exit..." -ExitAfter - } - - # Get version choice from user (interactive menu) - $versionChoice = Get-UserVersionChoice - $script:Version = $versionChoice.Type - $script:Tag = $versionChoice.Tag - $script:Branch = $versionChoice.Branch - - # Determine version information for display - $versionInfo = switch ($Version) { - "stable" { - if ($Tag) { "Stable release: $Tag" } - else { "Latest stable release (auto-detected)" } - } - "latest" { "Latest main branch (development)" } - "branch" { "Custom branch: $Branch" } - } - - # Confirm installation - if (-not $NonInteractive -and -not $Force) { - Write-Host "" - Write-ColorOutput "INSTALLATION DETAILS:" $ColorInfo - Write-Host "- Repository: https://github.com/catlog22/Claude-Code-Workflow" - Write-Host "- Version: $versionInfo" - Write-Host "- Features: Intelligent workflow orchestration with multi-agent coordination" - Write-Host "" - Write-ColorOutput "SECURITY NOTE:" $ColorWarning - Write-Host "- This script will download and execute code from GitHub" - Write-Host "- Please ensure you trust this source" - Write-Host "" - - $choice = Read-Host "Continue with installation? (y/N)" - if ($choice -notmatch '^[Yy]') { - Write-ColorOutput "Installation cancelled" $ColorWarning - Wait-ForUserConfirmation -ExitAfter - } - } - - # Create temp directory - $tempDir = Get-TempDirectory - Write-ColorOutput "Temporary directory: $tempDir" $ColorInfo - - try { - # Download repository - $zipPath = Download-Repository -TempDir $tempDir -Version $Version -Branch $Branch -Tag $Tag - if (-not $zipPath) { - throw "Download failed" - } - - # Extract repository - $repoDir = Extract-Repository $zipPath $tempDir - if (-not $repoDir) { - throw "Extraction failed" - } - - # Get commit SHA from the downloaded repository first - $commitSha = "" - Write-ColorOutput "Detecting version information..." $ColorInfo - - # Try to get from git repository if git is available - $gitAvailable = $false - try { - $null = git --version 2>$null - if ($LASTEXITCODE -eq 0) { - $gitAvailable = $true - } - } catch { - # Git not available - } - - if ($gitAvailable) { - try { - Push-Location $repoDir - $commitSha = (git rev-parse --short HEAD 2>$null) - Pop-Location - - if ($commitSha) { - Write-ColorOutput "✓ Version detected from git: $commitSha" $ColorSuccess - } - } catch { - Pop-Location - # Continue to fallback - } - } - - # Fallback: try to get from GitHub API - if (-not $commitSha) { - try { - Write-ColorOutput "Fetching version from GitHub API..." $ColorInfo - $commitUrl = "https://api.github.com/repos/catlog22/Claude-Code-Workflow/commits/$Branch" - $commitResponse = Invoke-RestMethod -Uri $commitUrl -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop - - if ($commitResponse.sha) { - $commitSha = $commitResponse.sha.Substring(0, 7) - Write-ColorOutput "✓ Version detected from API: $commitSha" $ColorSuccess - } - } catch { - Write-ColorOutput "WARNING: Could not detect version, using 'unknown'" $ColorWarning - $commitSha = "unknown" - } - } - - if (-not $commitSha) { - $commitSha = "unknown" - } - - # Determine version and branch information to pass - $versionToPass = "" - - if ($Tag) { - # Specific tag version - $versionToPass = $Tag -replace '^v', '' # Remove 'v' prefix - } elseif ($Version -eq "stable") { - # Auto-detected latest stable - $latestTag = Get-LatestRelease - if ($latestTag) { - $versionToPass = $latestTag -replace '^v', '' - } else { - # Fallback: use commit SHA as version - $versionToPass = "dev-$commitSha" - } - } else { - # Latest development or branch - use commit SHA as version - $versionToPass = "dev-$commitSha" - } - - $branchToPass = if ($Version -eq "branch") { $Branch } elseif ($Version -eq "latest") { "main" } elseif ($Tag) { $Tag } else { "main" } - - Write-ColorOutput "Version info: $versionToPass (branch: $branchToPass, commit: $commitSha)" $ColorInfo - - # Run local installer with version information - $success = Invoke-LocalInstaller -RepoDir $repoDir -VersionInfo $versionToPass -BranchInfo $branchToPass -CommitSha $commitSha - if (-not $success) { - throw "Installation script failed" - } - - Write-Host "" - Write-ColorOutput "Remote installation completed successfully!" $ColorSuccess - - } catch { - Write-Host "" - Write-ColorOutput "Remote installation failed: $($_.Exception.Message)" $ColorError - Wait-ForUserConfirmation "Installation failed! Press any key to exit..." -ExitAfter - } finally { - # Cleanup - Cleanup-TempFiles $tempDir - } - - Write-Host "" - Write-ColorOutput "Next steps:" $ColorInfo - Write-Host "1. Review CLAUDE.md for project-specific guidelines" - Write-Host "2. Try /workflow commands for Agent coordination" - Write-Host "3. Use /update-memory to manage distributed documentation" - - Wait-ForUserConfirmation "Remote installation done! Press any key to exit..." -ExitAfter -} - -# Run main function -try { - Main -} catch { - Write-ColorOutput "CRITICAL ERROR: $($_.Exception.Message)" $ColorError - Write-Host "" - Wait-ForUserConfirmation "A critical error occurred! Press any key to exit..." -ExitAfter -} \ No newline at end of file diff --git a/install-remote.sh b/install-remote.sh deleted file mode 100644 index de710971..00000000 --- a/install-remote.sh +++ /dev/null @@ -1,886 +0,0 @@ -#!/usr/bin/env bash -# Claude Code Workflow (CCW) - Remote Installation Script -# One-liner remote installation for Claude Code Workflow system - -set -e # Exit on error - -# Script metadata -SCRIPT_NAME="Claude Code Workflow (CCW) Remote Installer" -INSTALLER_VERSION="2.2.0" -BRANCH="${BRANCH:-main}" - -# Version control -VERSION_TYPE="${VERSION_TYPE:-stable}" # stable, latest, branch -TAG_VERSION="" - -# Colors for output -COLOR_RESET='\033[0m' -COLOR_SUCCESS='\033[0;32m' -COLOR_INFO='\033[0;36m' -COLOR_WARNING='\033[0;33m' -COLOR_ERROR='\033[0;31m' - -# Variables -INSTALL_GLOBAL=false -INSTALL_DIR="" -FORCE=false -NO_BACKUP=false -NON_INTERACTIVE=false -BACKUP_ALL=false - -# Functions -function write_color() { - local message="$1" - local color="${2:-$COLOR_RESET}" - echo -e "${color}${message}${COLOR_RESET}" -} - -function show_header() { - write_color "==== $SCRIPT_NAME v$INSTALLER_VERSION ====" "$COLOR_INFO" - write_color "========================================================" "$COLOR_INFO" - echo "" -} - -function test_prerequisites() { - # Test bash version - if [ "${BASH_VERSINFO[0]}" -lt 2 ]; then - write_color "ERROR: Bash 2.0 or higher is required" "$COLOR_ERROR" - write_color "Current version: ${BASH_VERSION}" "$COLOR_ERROR" - return 1 - fi - - # Test required commands - for cmd in curl unzip; do - if ! command -v "$cmd" &> /dev/null; then - write_color "ERROR: Required command '$cmd' not found" "$COLOR_ERROR" - write_color "Please install: $cmd" "$COLOR_ERROR" - return 1 - fi - done - - # Check for optional but recommended commands - if ! command -v git &> /dev/null; then - write_color "WARNING: 'git' not found - version detection may be limited" "$COLOR_WARNING" - write_color "Hint: Install git for better version tracking" "$COLOR_INFO" - write_color " On Ubuntu/Debian: sudo apt-get install git" "$COLOR_INFO" - write_color " On WSL: sudo apt-get update && sudo apt-get install git" "$COLOR_INFO" - echo "" - else - write_color "✓ Git available" "$COLOR_SUCCESS" - fi - - # Test internet connectivity - if curl -sSf --connect-timeout 10 "https://github.com" &> /dev/null; then - write_color "✓ Network connection OK" "$COLOR_SUCCESS" - else - write_color "ERROR: Cannot connect to GitHub" "$COLOR_ERROR" - write_color "Please check your network connection and try again." "$COLOR_ERROR" - echo "" - write_color "Common causes:" "$COLOR_INFO" - echo " • Internet connection is down or unstable" - echo " • Firewall or proxy is blocking GitHub access" - echo " • DNS resolution issues" - echo " • GitHub is temporarily unavailable" - echo "" - write_color "Troubleshooting steps:" "$COLOR_INFO" - echo " 1. Check your internet connection" - echo " 2. Try accessing https://github.com in your browser" - echo " 3. If using a proxy, configure it properly" - echo " 4. Check firewall settings" - echo " 5. Wait a few minutes and try again" - echo "" - return 1 - fi - - return 0 -} - -function get_temp_directory() { - local temp_dir - temp_dir=$(mktemp -d -t claude-code-workflow-install.XXXXXX) - echo "$temp_dir" -} - -function get_latest_release() { - local api_url="https://api.github.com/repos/catlog22/Claude-Code-Workflow/releases/latest" - - if command -v jq &> /dev/null; then - # Use jq if available - local tag - tag=$(curl -fsSL "$api_url" 2>/dev/null | jq -r '.tag_name' 2>/dev/null) - if [ -n "$tag" ] && [ "$tag" != "null" ]; then - echo "$tag" - return 0 - fi - else - # Fallback: parse JSON with grep/sed - local tag - tag=$(curl -fsSL "$api_url" 2>/dev/null | grep -o '"tag_name":\s*"[^"]*"' | sed 's/"tag_name":\s*"\([^"]*\)"/\1/') - if [ -n "$tag" ]; then - echo "$tag" - return 0 - fi - fi - - write_color "WARNING: Failed to fetch latest release" "$COLOR_WARNING" >&2 - write_color "Falling back to 'main' branch" "$COLOR_INFO" >&2 - return 1 -} - -function download_repository() { - local temp_dir="$1" - local version_type="${2:-stable}" - local branch="${3:-main}" - local tag="${4:-}" - local repo_url="https://github.com/catlog22/Claude-Code-Workflow" - local zip_url="" - local download_type="" - - # Determine download URL based on version type - case "$version_type" in - stable) - # Download latest stable release - if [ -z "$tag" ]; then - tag=$(get_latest_release) - if [ -z "$tag" ]; then - # Fallback to main branch if API fails - zip_url="${repo_url}/archive/refs/heads/main.zip" - download_type="main branch (fallback)" - else - zip_url="${repo_url}/archive/refs/tags/${tag}.zip" - download_type="stable release $tag" - fi - else - zip_url="${repo_url}/archive/refs/tags/${tag}.zip" - download_type="stable release $tag" - fi - ;; - latest) - # Download latest main branch - zip_url="${repo_url}/archive/refs/heads/main.zip" - download_type="latest main branch" - ;; - branch) - # Download specific branch - zip_url="${repo_url}/archive/refs/heads/${branch}.zip" - download_type="branch $branch" - ;; - *) - write_color "ERROR: Invalid version type: $version_type" "$COLOR_ERROR" >&2 - return 1 - ;; - esac - - local zip_path="${temp_dir}/repo.zip" - - write_color "Downloading from GitHub..." "$COLOR_INFO" >&2 - write_color "Source: $repo_url" "$COLOR_INFO" >&2 - write_color "Type: $download_type" "$COLOR_INFO" >&2 - - # Download with curl - local download_error="" - if download_error=$(curl -fsSL -o "$zip_path" "$zip_url" 2>&1); then - # Verify the download - if [ -f "$zip_path" ]; then - local file_size - file_size=$(du -h "$zip_path" 2>/dev/null | cut -f1) - - # Check if file is empty - if [ ! -s "$zip_path" ]; then - echo "" >&2 - write_color "ERROR: Downloaded file is empty (0 bytes)" "$COLOR_ERROR" >&2 - echo "" >&2 - write_color "Common causes:" "$COLOR_INFO" >&2 - echo " • Network connection was interrupted" >&2 - echo " • Invalid version tag or branch name" >&2 - echo " • GitHub API or server issues" >&2 - echo "" >&2 - write_color "Troubleshooting steps:" "$COLOR_INFO" >&2 - echo " 1. Verify the version tag or branch name is correct" >&2 - echo " 2. Wait a few minutes and try again" >&2 - echo " 3. Try a different version (stable/latest)" >&2 - echo " 4. Check GitHub status at https://www.githubstatus.com" >&2 - echo "" >&2 - write_color "Download URL: $zip_url" "$COLOR_INFO" >&2 - echo "" >&2 - return 1 - fi - - write_color "✓ Download complete ($file_size)" "$COLOR_SUCCESS" >&2 - - # Output path to stdout for capture - echo "$zip_path" - return 0 - else - write_color "ERROR: Downloaded file does not exist" "$COLOR_ERROR" >&2 - return 1 - fi - else - echo "" >&2 - write_color "ERROR: Download failed" "$COLOR_ERROR" >&2 - echo "" >&2 - write_color "Common causes:" "$COLOR_INFO" >&2 - echo " • Network connection interrupted during download" >&2 - echo " • GitHub API rate limit exceeded" >&2 - echo " • Invalid version tag or branch name" >&2 - echo " • Temporary GitHub service issues" >&2 - echo " • Firewall or proxy blocking the download" >&2 - echo "" >&2 - write_color "Troubleshooting steps:" "$COLOR_INFO" >&2 - echo " 1. Check your internet connection stability" >&2 - echo " 2. Wait a few minutes and try again (rate limit resets)" >&2 - echo " 3. Verify the version tag or branch name is correct" >&2 - echo " 4. Try a different version (stable/latest)" >&2 - echo " 5. Check GitHub status at https://www.githubstatus.com" >&2 - echo " 6. If using a proxy, verify it's configured correctly" >&2 - echo "" >&2 - write_color "Download URL: $zip_url" "$COLOR_INFO" >&2 - if [ -n "$download_error" ]; then - write_color "Error details: $download_error" "$COLOR_ERROR" >&2 - fi - echo "" >&2 - return 1 - fi -} - -function extract_repository() { - local zip_path="$1" - local temp_dir="$2" - - write_color "Extracting files..." "$COLOR_INFO" >&2 - - # Verify zip file exists - if [ ! -f "$zip_path" ]; then - write_color "ERROR: ZIP file not found: $zip_path" "$COLOR_ERROR" >&2 - return 1 - fi - - # Verify zip file is not empty - if [ ! -s "$zip_path" ]; then - echo "" >&2 - write_color "ERROR: ZIP file is empty (0 bytes)" "$COLOR_ERROR" >&2 - echo "" >&2 - write_color "Common causes:" "$COLOR_INFO" >&2 - echo " • Download was interrupted" >&2 - echo " • Network connection issues during download" >&2 - echo " • Server-side issues" >&2 - echo "" >&2 - write_color "Troubleshooting steps:" "$COLOR_INFO" >&2 - echo " 1. Try downloading again" >&2 - echo " 2. Check your network connection" >&2 - echo " 3. Wait a few minutes and retry" >&2 - echo "" >&2 - return 1 - fi - - # Extract with unzip - local extract_error="" - if extract_error=$(unzip -q "$zip_path" -d "$temp_dir" 2>&1); then - # Find the extracted directory - local repo_dir - repo_dir=$(find "$temp_dir" -maxdepth 1 -type d -name "Claude-Code-Workflow-*" 2>/dev/null | head -n 1) - - if [ -n "$repo_dir" ] && [ -d "$repo_dir" ]; then - write_color "✓ Extraction complete: $repo_dir" "$COLOR_SUCCESS" >&2 - # Output path to stdout for capture - echo "$repo_dir" - return 0 - else - echo "" >&2 - write_color "ERROR: Could not find extracted repository directory" "$COLOR_ERROR" >&2 - write_color "Temp directory contents:" "$COLOR_INFO" >&2 - ls -la "$temp_dir" >&2 - echo "" >&2 - return 1 - fi - else - echo "" >&2 - write_color "ERROR: Extraction failed" "$COLOR_ERROR" >&2 - echo "" >&2 - write_color "Common causes:" "$COLOR_INFO" >&2 - echo " • Downloaded file is corrupted or incomplete" >&2 - echo " • ZIP file format is invalid" >&2 - echo " • Insufficient disk space" >&2 - echo " • Permission issues on temporary directory" >&2 - echo "" >&2 - write_color "Troubleshooting steps:" "$COLOR_INFO" >&2 - echo " 1. Try downloading again (network may have interrupted)" >&2 - echo " 2. Check available disk space: df -h" >&2 - echo " 3. Verify temporary directory permissions" >&2 - echo " 4. Check if 'unzip' command is working: unzip -v" >&2 - echo "" >&2 - write_color "ZIP file: $zip_path" "$COLOR_INFO" >&2 - if [ -f "$zip_path" ]; then - local zip_size - zip_size=$(du -h "$zip_path" 2>/dev/null | cut -f1) - write_color "ZIP size: $zip_size" "$COLOR_INFO" >&2 - fi - if [ -n "$extract_error" ]; then - write_color "Error details: $extract_error" "$COLOR_ERROR" >&2 - fi - echo "" >&2 - return 1 - fi -} - -function invoke_local_installer() { - local repo_dir="$1" - local version_info="$2" - local branch_info="$3" - local commit_sha="$4" - local installer_path="${repo_dir}/Install-Claude.sh" - - # Make installer executable - if [ -f "$installer_path" ]; then - chmod +x "$installer_path" - else - write_color "ERROR: Install-Claude.sh not found" "$COLOR_ERROR" - return 1 - fi - - write_color "Running local installer..." "$COLOR_INFO" - echo "" - - # Build parameters for local installer - local params=() - - if [ "$INSTALL_GLOBAL" = true ]; then - params+=("-InstallMode" "Global") - fi - - if [ -n "$INSTALL_DIR" ]; then - params+=("-InstallMode" "Path" "-TargetPath" "$INSTALL_DIR") - fi - - if [ "$FORCE" = true ]; then - params+=("-Force") - fi - - if [ "$NO_BACKUP" = true ]; then - params+=("-NoBackup") - fi - - if [ "$NON_INTERACTIVE" = true ]; then - params+=("-NonInteractive") - fi - - if [ "$BACKUP_ALL" = true ]; then - params+=("-BackupAll") - fi - - # Pass version, branch, and commit information - if [ -n "$version_info" ]; then - params+=("-SourceVersion" "$version_info") - fi - - if [ -n "$branch_info" ]; then - params+=("-SourceBranch" "$branch_info") - fi - - if [ -n "$commit_sha" ]; then - params+=("-SourceCommit" "$commit_sha") - fi - - # Execute installer - if (cd "$repo_dir" && "$installer_path" "${params[@]}"); then - return 0 - else - write_color "Installation script failed" "$COLOR_ERROR" - return 1 - fi -} - -function cleanup_temp_files() { - local temp_dir="$1" - - if [ -d "$temp_dir" ]; then - if rm -rf "$temp_dir"; then - write_color "✓ Temporary files cleaned up" "$COLOR_INFO" - else - write_color "WARNING: Failed to clean temporary files" "$COLOR_WARNING" - fi - fi -} - -function wait_for_user() { - local message="${1:-Press Enter to continue...}" - - if [ "$NON_INTERACTIVE" != true ]; then - echo "" - write_color "$message" "$COLOR_INFO" - read -r - echo "" - fi -} - -function parse_arguments() { - while [[ $# -gt 0 ]]; do - case "$1" in - --version) - VERSION_TYPE="$2" - if [[ ! "$VERSION_TYPE" =~ ^(stable|latest|branch)$ ]]; then - write_color "ERROR: Invalid version type: $VERSION_TYPE" "$COLOR_ERROR" - write_color "Valid options: stable, latest, branch" "$COLOR_ERROR" - exit 1 - fi - shift 2 - ;; - --tag) - TAG_VERSION="$2" - shift 2 - ;; - --branch) - BRANCH="$2" - shift 2 - ;; - --global) - INSTALL_GLOBAL=true - shift - ;; - --directory) - INSTALL_DIR="$2" - shift 2 - ;; - --force) - FORCE=true - shift - ;; - --no-backup) - NO_BACKUP=true - shift - ;; - --non-interactive) - NON_INTERACTIVE=true - shift - ;; - --backup-all) - BACKUP_ALL=true - shift - ;; - --help) - show_help - exit 0 - ;; - *) - write_color "Unknown option: $1" "$COLOR_ERROR" - show_help - exit 1 - ;; - esac - done -} - -function show_help() { - cat << EOF -$SCRIPT_NAME v$INSTALLER_VERSION - -Usage: $0 [OPTIONS] - -Version Options: - --version TYPE Version type: stable (default), latest, or branch - --tag TAG Specific release tag (e.g., v3.2.0) - for stable version - --branch BRANCH Branch name (default: main) - for branch version - -Installation Options: - --global Install to global user directory (~/.claude) - --directory DIR Install to custom directory - --force Force installation without prompts - --no-backup Skip backup creation - --non-interactive Non-interactive mode (no prompts) - --backup-all Backup all files before installation - --help Show this help message - -Examples: - # Install latest stable release (recommended) - $0 - - # Install specific stable version - $0 --version stable --tag v3.2.0 - - # Install latest development version - $0 --version latest - - # Install from specific branch - $0 --version branch --branch feature/new-feature - - # Global installation without prompts - $0 --global --non-interactive - - # Custom directory installation - $0 --directory /opt/claude-code-workflow - -Repository: https://github.com/catlog22/Claude-Code-Workflow - -EOF -} - -function show_version_menu() { - local latest_version="$1" - local latest_date="$2" - local commit_id="$3" - local commit_date="$4" - - echo "" - write_color "====================================================" "$COLOR_INFO" - write_color " Version Selection Menu" "$COLOR_INFO" - write_color "====================================================" "$COLOR_INFO" - echo "" - - # Option 1: Latest Stable - write_color "1) Latest Stable Release (Recommended)" "$COLOR_SUCCESS" - if [ -n "$latest_version" ] && [ "$latest_version" != "Unknown" ]; then - echo " |-- Version: $latest_version" - if [ -n "$latest_date" ]; then - echo " |-- Released: $latest_date" - fi - echo " \-- Production-ready" - else - echo " |-- Version: Auto-detected from GitHub" - echo " \-- Production-ready" - fi - echo "" - - # Option 2: Latest Development - write_color "2) Latest Development Version" "$COLOR_WARNING" - echo " |-- Branch: main" - if [ -n "$commit_id" ] && [ -n "$commit_date" ]; then - echo " |-- Commit: $commit_id" - echo " |-- Updated: $commit_date" - fi - echo " |-- Cutting-edge features" - echo " \-- May contain experimental changes" - echo "" - - # Option 3: Specific Version - write_color "3) Specific Release Version" "$COLOR_INFO" - echo " |-- Install a specific tagged release" - echo " \-- Recent: v3.2.0, v3.1.0, v3.0.1" - echo "" - - write_color "====================================================" "$COLOR_INFO" - echo "" -} - -function get_user_version_choice() { - if [ "$NON_INTERACTIVE" = true ] || [ "$FORCE" = true ]; then - # Non-interactive mode: use default stable version - echo "stable" - return 0 - fi - - # Detect latest stable version and commit info - write_color "Detecting latest release and commits..." "$COLOR_INFO" - local latest_version="Unknown" - local latest_date="" - local commit_id="" - local commit_date="" - - # Get latest release info - local release_data - release_data=$(curl -fsSL --connect-timeout 5 "https://api.github.com/repos/catlog22/Claude-Code-Workflow/releases/latest" 2>/dev/null) - - if [ -n "$release_data" ]; then - if command -v jq &> /dev/null; then - latest_version=$(echo "$release_data" | jq -r '.tag_name' 2>/dev/null) - local published_at=$(echo "$release_data" | jq -r '.published_at' 2>/dev/null) - if [ -n "$published_at" ] && [ "$published_at" != "null" ]; then - # Convert UTC to local time - if command -v date &> /dev/null; then - latest_date=$(date -d "$published_at" '+%Y-%m-%d %H:%M' 2>/dev/null || date -jf '%Y-%m-%dT%H:%M:%SZ' "$published_at" '+%Y-%m-%d %H:%M' 2>/dev/null) - fi - fi - else - latest_version=$(echo "$release_data" | grep -o '"tag_name":\s*"[^"]*"' | sed 's/"tag_name":\s*"\([^"]*\)"/\1/') - local published_at=$(echo "$release_data" | grep -o '"published_at":\s*"[^"]*"' | sed 's/"published_at":\s*"\([^"]*\)"/\1/') - if [ -n "$published_at" ]; then - # Convert UTC to local time - if command -v date &> /dev/null; then - latest_date=$(date -d "$published_at" '+%Y-%m-%d %H:%M' 2>/dev/null || date -jf '%Y-%m-%dT%H:%M:%SZ' "$published_at" '+%Y-%m-%d %H:%M' 2>/dev/null) - fi - fi - fi - fi - - if [ -n "$latest_version" ] && [ "$latest_version" != "null" ] && [ "$latest_version" != "Unknown" ]; then - write_color "Latest stable: $latest_version ($latest_date)" "$COLOR_SUCCESS" - else - latest_version="Unknown" - write_color "Could not detect latest release" "$COLOR_WARNING" - fi - - # Get latest commit info - local commit_data - commit_data=$(curl -fsSL --connect-timeout 5 "https://api.github.com/repos/catlog22/Claude-Code-Workflow/commits/main" 2>/dev/null) - - if [ -n "$commit_data" ]; then - if command -v jq &> /dev/null; then - commit_id=$(echo "$commit_data" | jq -r '.sha' 2>/dev/null | cut -c1-7) - local committer_date=$(echo "$commit_data" | jq -r '.commit.committer.date' 2>/dev/null) - if [ -n "$committer_date" ] && [ "$committer_date" != "null" ]; then - # Convert UTC to local time - if command -v date &> /dev/null; then - commit_date=$(date -d "$committer_date" '+%Y-%m-%d %H:%M' 2>/dev/null || date -jf '%Y-%m-%dT%H:%M:%SZ' "$committer_date" '+%Y-%m-%d %H:%M' 2>/dev/null) - fi - fi - else - commit_id=$(echo "$commit_data" | grep -o '"sha":\s*"[^"]*"' | head -1 | sed 's/"sha":\s*"\([^"]*\)"/\1/' | cut -c1-7) - local committer_date=$(echo "$commit_data" | grep -o '"date":\s*"[^"]*"' | head -1 | sed 's/"date":\s*"\([^"]*\)"/\1/') - if [ -n "$committer_date" ]; then - # Convert UTC to local time - if command -v date &> /dev/null; then - commit_date=$(date -d "$committer_date" '+%Y-%m-%d %H:%M' 2>/dev/null || date -jf '%Y-%m-%dT%H:%M:%SZ' "$committer_date" '+%Y-%m-%d %H:%M' 2>/dev/null) - fi - fi - fi - fi - - if [ -n "$commit_id" ] && [ -n "$commit_date" ]; then - write_color "Latest commit: $commit_id ($commit_date)" "$COLOR_SUCCESS" - else - write_color "Could not detect latest commit" "$COLOR_WARNING" - fi - - show_version_menu "$latest_version" "$latest_date" "$commit_id" "$commit_date" - - read -p "Select version to install (1-3, default: 1): " choice - - case "$choice" in - 2) - echo "" - write_color "✓ Selected: Latest Development Version (main branch)" "$COLOR_SUCCESS" - VERSION_TYPE="latest" - TAG_VERSION="" - BRANCH="main" - ;; - 3) - echo "" - write_color "Available recent releases:" "$COLOR_INFO" - echo " v3.2.0, v3.1.0, v3.0.1, v3.0.0" - echo "" - read -p "Enter version tag (e.g., v3.2.0): " tag_input - - if [ -z "$tag_input" ]; then - write_color "⚠ No tag specified, using latest stable" "$COLOR_WARNING" - VERSION_TYPE="stable" - TAG_VERSION="" - else - echo "" - write_color "✓ Selected: Specific Version $tag_input" "$COLOR_SUCCESS" - VERSION_TYPE="stable" - TAG_VERSION="$tag_input" - fi - BRANCH="main" - ;; - *) - echo "" - if [ "$latest_version" != "Unknown" ]; then - write_color "✓ Selected: Latest Stable Release ($latest_version)" "$COLOR_SUCCESS" - else - write_color "✓ Selected: Latest Stable Release (auto-detect)" "$COLOR_SUCCESS" - fi - VERSION_TYPE="stable" - TAG_VERSION="" - BRANCH="main" - ;; - esac -} - -function main() { - show_header - - write_color "This will download and install Claude Code Workflow System from GitHub." "$COLOR_INFO" - echo "" - - # Test prerequisites - write_color "Checking system requirements..." "$COLOR_INFO" - if ! test_prerequisites; then - wait_for_user "System check failed! Press Enter to exit..." - exit 1 - fi - - # Get version choice from user (interactive menu) - get_user_version_choice - - # Determine version information for display - local version_info="" - case "$VERSION_TYPE" in - stable) - if [ -n "$TAG_VERSION" ]; then - version_info="Stable release: $TAG_VERSION" - else - version_info="Latest stable release (auto-detected)" - fi - ;; - latest) - version_info="Latest main branch (development)" - ;; - branch) - version_info="Custom branch: $BRANCH" - ;; - esac - - # Confirm installation - if [ "$NON_INTERACTIVE" != true ] && [ "$FORCE" != true ]; then - echo "" - write_color "INSTALLATION DETAILS:" "$COLOR_INFO" - echo "- Repository: https://github.com/catlog22/Claude-Code-Workflow" - echo "- Version: $version_info" - echo "- Features: Intelligent workflow orchestration with multi-agent coordination" - echo "" - write_color "SECURITY NOTE:" "$COLOR_WARNING" - echo "- This script will download and execute code from GitHub" - echo "- Please ensure you trust this source" - echo "" - - read -p "Continue with installation? (y/N) " -r choice - if [[ ! $choice =~ ^[Yy]$ ]]; then - write_color "Installation cancelled" "$COLOR_WARNING" - exit 0 - fi - fi - - # Create temp directory - local temp_dir - temp_dir=$(get_temp_directory) - write_color "Temporary directory: $temp_dir" "$COLOR_INFO" - - local success=false - - # Download repository - local zip_path - write_color "Starting download process..." "$COLOR_INFO" - zip_path=$(download_repository "$temp_dir" "$VERSION_TYPE" "$BRANCH" "$TAG_VERSION") - local download_status=$? - - if [ $download_status -eq 0 ] && [ -n "$zip_path" ] && [ -f "$zip_path" ]; then - write_color "Download successful: $zip_path" "$COLOR_SUCCESS" - - # Extract repository - local repo_dir - write_color "Starting extraction process..." "$COLOR_INFO" - repo_dir=$(extract_repository "$zip_path" "$temp_dir") - local extract_status=$? - - if [ $extract_status -eq 0 ] && [ -n "$repo_dir" ] && [ -d "$repo_dir" ]; then - write_color "Extraction successful: $repo_dir" "$COLOR_SUCCESS" - - # Get commit SHA from the downloaded repository first - local commit_sha="" - write_color "Detecting version information..." "$COLOR_INFO" - - if command -v git &> /dev/null && [ -d "$repo_dir/.git" ]; then - # Try to get from git repository - commit_sha=$(cd "$repo_dir" && git rev-parse --short HEAD 2>/dev/null || echo "") - if [ -n "$commit_sha" ]; then - write_color "✓ Version detected from git: $commit_sha" "$COLOR_SUCCESS" - fi - fi - - if [ -z "$commit_sha" ]; then - # Fallback: try to get from GitHub API - write_color "Fetching version from GitHub API..." "$COLOR_INFO" - local temp_branch="main" - [ "$VERSION_TYPE" = "branch" ] && temp_branch="$BRANCH" - - local commit_data - commit_data=$(curl -fsSL --connect-timeout 10 "https://api.github.com/repos/catlog22/Claude-Code-Workflow/commits/$temp_branch" 2>/dev/null) - - if [ -n "$commit_data" ]; then - if command -v jq &> /dev/null; then - commit_sha=$(echo "$commit_data" | jq -r '.sha' 2>/dev/null | cut -c1-7) - else - commit_sha=$(echo "$commit_data" | grep -o '"sha": *"[^"]*"' | head -1 | cut -d'"' -f4 | cut -c1-7) - fi - fi - - if [ -n "$commit_sha" ] && [ "$commit_sha" != "null" ]; then - write_color "✓ Version detected from API: $commit_sha" "$COLOR_SUCCESS" - else - write_color "WARNING: Could not detect version, using 'unknown'" "$COLOR_WARNING" - commit_sha="unknown" - fi - fi - - # Determine version and branch information to pass - local version_to_pass="" - local branch_to_pass="" - - if [ -n "$TAG_VERSION" ]; then - # Specific tag version - remove 'v' prefix - version_to_pass="${TAG_VERSION#v}" - elif [ "$VERSION_TYPE" = "stable" ]; then - # Auto-detected latest stable - local latest_tag - latest_tag=$(get_latest_release) - if [ -n "$latest_tag" ]; then - version_to_pass="${latest_tag#v}" - else - # Fallback: use commit SHA as version - version_to_pass="dev-$commit_sha" - fi - else - # Latest development or branch - use commit SHA as version - version_to_pass="dev-$commit_sha" - fi - - if [ "$VERSION_TYPE" = "branch" ]; then - branch_to_pass="$BRANCH" - elif [ "$VERSION_TYPE" = "latest" ]; then - branch_to_pass="main" - elif [ -n "$TAG_VERSION" ]; then - branch_to_pass="$TAG_VERSION" - else - branch_to_pass="main" - fi - - write_color "Version info: $version_to_pass (branch: $branch_to_pass, commit: $commit_sha)" "$COLOR_INFO" - - # Run local installer with version information - if invoke_local_installer "$repo_dir" "$version_to_pass" "$branch_to_pass" "$commit_sha"; then - success=true - echo "" - write_color "✓ Remote installation completed successfully!" "$COLOR_SUCCESS" - else - write_color "ERROR: Installation script failed" "$COLOR_ERROR" - fi - else - write_color "ERROR: Extraction failed (status: $extract_status)" "$COLOR_ERROR" - if [ ! -f "$zip_path" ]; then - write_color "ZIP file does not exist: $zip_path" "$COLOR_ERROR" - elif [ ! -s "$zip_path" ]; then - write_color "ZIP file is empty: $zip_path" "$COLOR_ERROR" - fi - fi - else - write_color "ERROR: Download failed (status: $download_status)" "$COLOR_ERROR" - if [ -z "$zip_path" ]; then - write_color "Download did not return a file path" "$COLOR_ERROR" - elif [ ! -f "$zip_path" ]; then - write_color "Downloaded file does not exist: $zip_path" "$COLOR_ERROR" - fi - fi - - # Cleanup - cleanup_temp_files "$temp_dir" - - if [ "$success" = true ]; then - echo "" - write_color "Next steps:" "$COLOR_INFO" - echo "1. Review CLAUDE.md for project-specific guidelines" - echo "2. Try /workflow commands for Agent coordination" - echo "3. Use /update-memory to manage distributed documentation" - - wait_for_user "Remote installation done! Press Enter to exit..." - exit 0 - else - echo "" - wait_for_user "Installation failed! Press Enter to exit..." - exit 1 - fi -} - -# Parse command line arguments -parse_arguments "$@" - -# Run main function -main