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