mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-06 01:54:11 +08:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51a0cb3a3c | ||
|
|
436c7909b0 |
240
.claude/commands/memory/load.md
Normal file
240
.claude/commands/memory/load.md
Normal file
@@ -0,0 +1,240 @@
|
||||
---
|
||||
name: load
|
||||
description: Load project memory by delegating to agent, returns structured core content package for subsequent operations
|
||||
argument-hint: "[--tool gemini|qwen] \"task context description\""
|
||||
allowed-tools: Task(*), Bash(*)
|
||||
examples:
|
||||
- /memory:load "在当前前端基础上开发用户认证功能"
|
||||
- /memory:load --tool qwen "重构支付模块API"
|
||||
---
|
||||
|
||||
# Memory Load Command (/memory:load)
|
||||
|
||||
## 1. Overview
|
||||
|
||||
The `memory:load` command **delegates to a general-purpose agent** to analyze the project and return a structured "Core Content Pack". This pack is loaded into the main thread's memory, providing essential context for subsequent agent operations while minimizing token consumption.
|
||||
|
||||
**Core Philosophy**:
|
||||
- **Agent-Driven**: Fully delegates execution to general-purpose agent
|
||||
- **Read-Only Analysis**: Does not modify code, only extracts context
|
||||
- **Structured Output**: Returns standardized JSON content package
|
||||
- **Memory Optimization**: Package loaded directly into main thread memory
|
||||
- **Token Efficiency**: CLI analysis executed within agent to save tokens
|
||||
|
||||
## 2. Parameters
|
||||
|
||||
- `"task context description"` (Required): Task description to guide context extraction
|
||||
- Example: "在当前前端基础上开发用户认证功能"
|
||||
- Example: "重构支付模块API"
|
||||
- Example: "修复数据库查询性能问题"
|
||||
|
||||
- `--tool <gemini|qwen>` (Optional): Specify CLI tool for agent to use (default: gemini)
|
||||
- gemini: Large context window, suitable for complex project analysis
|
||||
- qwen: Alternative to Gemini with similar capabilities
|
||||
|
||||
## 3. Agent-Driven Execution Flow
|
||||
|
||||
The command fully delegates to **general-purpose agent**, which autonomously:
|
||||
|
||||
1. **Analyzes Project Structure**: Executes `get_modules_by_depth.sh` to understand architecture
|
||||
2. **Loads Documentation**: Reads CLAUDE.md, README.md and other key docs
|
||||
3. **Extracts Keywords**: Derives core keywords from task description
|
||||
4. **Discovers Files**: Uses MCP code-index or rg/find to locate relevant files
|
||||
5. **CLI Deep Analysis**: Executes Gemini/Qwen CLI for deep context analysis
|
||||
6. **Generates Content Package**: Returns structured JSON core content package
|
||||
|
||||
## 4. Core Content Package Structure
|
||||
|
||||
**Output Format** - Loaded into main thread memory for subsequent use:
|
||||
|
||||
```json
|
||||
{
|
||||
"task_context": "在当前前端基础上开发用户认证功能",
|
||||
"keywords": ["前端", "用户", "认证", "auth", "login"],
|
||||
"project_summary": {
|
||||
"architecture": "TypeScript + React frontend with Vite build system",
|
||||
"tech_stack": ["React", "TypeScript", "Vite", "TailwindCSS"],
|
||||
"key_patterns": [
|
||||
"State management via Context API",
|
||||
"Functional components with Hooks pattern",
|
||||
"API calls encapsulated in custom hooks"
|
||||
]
|
||||
},
|
||||
"relevant_files": [
|
||||
{
|
||||
"path": "src/components/Auth/LoginForm.tsx",
|
||||
"relevance": "Existing login form component",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"path": "src/contexts/AuthContext.tsx",
|
||||
"relevance": "Authentication state management context",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"path": "CLAUDE.md",
|
||||
"relevance": "Project development standards",
|
||||
"priority": "high"
|
||||
}
|
||||
],
|
||||
"integration_points": [
|
||||
"Must integrate with existing AuthContext",
|
||||
"Follow component organization pattern: src/components/[Feature]/",
|
||||
"API calls should use src/hooks/useApi.ts wrapper"
|
||||
],
|
||||
"constraints": [
|
||||
"Maintain backward compatibility",
|
||||
"Follow TypeScript strict mode",
|
||||
"Use existing UI component library"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Agent Invocation
|
||||
|
||||
```javascript
|
||||
Task(
|
||||
subagent_type="general-purpose",
|
||||
description="Load project memory: ${task_description}",
|
||||
prompt=`
|
||||
## Mission: Load Project Memory Context
|
||||
|
||||
**Task Context**: "${task_description}"
|
||||
**Mode**: Read-only analysis
|
||||
**Tool**: ${tool || 'gemini'}
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 1: Foundation Analysis
|
||||
|
||||
1. **Project Structure**
|
||||
\`\`\`bash
|
||||
bash(~/.claude/scripts/get_modules_by_depth.sh)
|
||||
\`\`\`
|
||||
|
||||
2. **Core Documentation**
|
||||
\`\`\`javascript
|
||||
Read(CLAUDE.md)
|
||||
Read(README.md)
|
||||
\`\`\`
|
||||
|
||||
### Step 2: Keyword Extraction & File Discovery
|
||||
|
||||
1. Extract core keywords from task description
|
||||
2. Discover relevant files using MCP code-index or rg:
|
||||
\`\`\`javascript
|
||||
// Prefer MCP tools
|
||||
mcp__code-index__find_files(pattern="*{keyword}*")
|
||||
mcp__code-index__search_code_advanced(pattern="{keyword}", context_lines=2)
|
||||
|
||||
// Fallback: use rg
|
||||
bash(rg -l "{keyword}" --type ts --type md)
|
||||
\`\`\`
|
||||
|
||||
### Step 3: Deep Analysis via CLI
|
||||
|
||||
Execute Gemini/Qwen CLI for deep analysis (saves main thread tokens):
|
||||
|
||||
\`\`\`bash
|
||||
cd . && ~/.claude/scripts/${tool}-wrapper -p "
|
||||
PURPOSE: Extract project core context for task: ${task_description}
|
||||
TASK: Analyze project architecture, tech stack, key patterns, relevant files
|
||||
MODE: analysis
|
||||
CONTEXT: @{CLAUDE.md,README.md,${discovered_files}}
|
||||
EXPECTED: Structured project summary and integration point analysis
|
||||
RULES:
|
||||
- Focus on task-relevant core information
|
||||
- Identify key architecture patterns and technical constraints
|
||||
- Extract integration points and development standards
|
||||
- Output concise, structured format
|
||||
"
|
||||
\`\`\`
|
||||
|
||||
### Step 4: Generate Core Content Package
|
||||
|
||||
Generate structured JSON content package (format shown above)
|
||||
|
||||
**Required Fields**:
|
||||
- task_context: Original task description
|
||||
- keywords: Extracted keyword array
|
||||
- project_summary: Architecture, tech stack, key patterns
|
||||
- relevant_files: File list with path, relevance, priority
|
||||
- integration_points: Integration guidance
|
||||
- constraints: Development constraints
|
||||
|
||||
### Step 5: Return Content Package
|
||||
|
||||
Return JSON content package as final output for main thread to load into memory.
|
||||
|
||||
## Quality Checklist
|
||||
|
||||
Before returning:
|
||||
- [ ] Valid JSON format
|
||||
- [ ] All required fields complete
|
||||
- [ ] relevant_files contains 3-10 files minimum
|
||||
- [ ] project_summary accurately reflects architecture
|
||||
- [ ] integration_points clearly specify integration paths
|
||||
- [ ] keywords accurately extracted (3-8 keywords)
|
||||
- [ ] Content concise, avoiding redundancy (< 5KB total)
|
||||
|
||||
`
|
||||
)
|
||||
```
|
||||
|
||||
## 6. Usage Examples
|
||||
|
||||
### Example 1: Load Context for New Feature
|
||||
|
||||
```bash
|
||||
/memory:load "在当前前端基础上开发用户认证功能"
|
||||
```
|
||||
|
||||
**Agent Execution**:
|
||||
1. Analyzes project structure (`get_modules_by_depth.sh`)
|
||||
2. Reads CLAUDE.md, README.md
|
||||
3. Extracts keywords: ["前端", "用户", "认证", "auth"]
|
||||
4. Uses MCP to search relevant files
|
||||
5. Executes Gemini CLI for deep analysis
|
||||
6. Returns core content package
|
||||
|
||||
**Returned Package** (loaded into memory):
|
||||
```json
|
||||
{
|
||||
"task_context": "在当前前端基础上开发用户认证功能",
|
||||
"keywords": ["前端", "认证", "auth", "login"],
|
||||
"project_summary": { ... },
|
||||
"relevant_files": [ ... ],
|
||||
"integration_points": [ ... ],
|
||||
"constraints": [ ... ]
|
||||
}
|
||||
```
|
||||
|
||||
### Example 2: Using Qwen Tool
|
||||
|
||||
```bash
|
||||
/memory:load --tool qwen "重构支付模块API"
|
||||
```
|
||||
|
||||
Agent uses Qwen CLI for analysis, returns same structured package.
|
||||
|
||||
### Example 3: Bug Fix Context
|
||||
|
||||
```bash
|
||||
/memory:load "修复登录验证错误"
|
||||
```
|
||||
|
||||
Returns core context related to login validation, including test files and validation logic.
|
||||
|
||||
### Memory Persistence
|
||||
|
||||
- **Session-Scoped**: Content package valid for current session
|
||||
- **Subsequent Reference**: All subsequent agents/commands can access
|
||||
- **Reload Required**: New sessions need to re-execute /memory:load
|
||||
|
||||
## 8. Notes
|
||||
|
||||
- **Read-Only**: Does not modify any code, pure analysis
|
||||
- **Token Optimization**: CLI analysis executed within agent, saves main thread tokens
|
||||
- **Memory Loading**: Returned JSON loaded directly into main thread memory
|
||||
- **Subsequent Use**: Other commands/agents can reference this package for development
|
||||
- **Session-Level**: Content package valid for current session
|
||||
27
CHANGELOG.md
27
CHANGELOG.md
@@ -5,6 +5,33 @@ All notable changes to Claude Code Workflow (CCW) will be documented in this fil
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
n## [4.6.2] - 2025-10-20
|
||||
|
||||
### 📝 Documentation Optimization
|
||||
|
||||
#### Improved
|
||||
|
||||
**`/memory:load` Command Documentation**: Optimized command specification from 273 to 240 lines (12% reduction)
|
||||
- Merged redundant sections for better information flow
|
||||
- Removed unnecessary internal implementation details
|
||||
- Simplified usage examples while preserving clarity
|
||||
- Maintained all critical information (parameters, workflow, JSON structure)
|
||||
- Improved user-centric documentation structure
|
||||
|
||||
#### Updated
|
||||
|
||||
**COMMAND_SPEC.md**: Updated `/memory:load` specification to match actual implementation
|
||||
- Corrected syntax: `[--tool gemini|qwen]` instead of outdated `[--agent] [--json]` flags
|
||||
- Added agent-driven execution details
|
||||
- Clarified core philosophy and token-efficiency benefits
|
||||
|
||||
**GETTING_STARTED.md**: Added "Quick Context Loading for Specific Tasks" section
|
||||
- Positioned between "Full Project Index Rebuild" and "Incremental Related Module Updates"
|
||||
- Includes practical examples and use case guidance
|
||||
- Explains how `/memory:load` works and when to use it
|
||||
|
||||
---
|
||||
|
||||
## [4.6.0] - 2025-10-18
|
||||
|
||||
### 🎯 Concept Clarification & Agent-Driven Analysis
|
||||
|
||||
@@ -127,6 +127,7 @@ Commands for managing individual tasks within a workflow session.
|
||||
| Command | Description |
|
||||
|---|---|
|
||||
| `/memory:update-full` | Complete project-wide CLAUDE.md documentation update. |
|
||||
| `/memory:load` | Quickly load key project context into memory based on a task description. |
|
||||
| `/memory:update-related` | Context-aware CLAUDE.md documentation updates based on recent changes. |
|
||||
| `/version` | Display version information and check for updates. |
|
||||
| `/enhance-prompt` | Context-aware prompt enhancement using session memory and codebase analysis. |
|
||||
|
||||
@@ -299,6 +299,27 @@ Commands for managing individual tasks within a workflow session.
|
||||
/memory:update-full
|
||||
```
|
||||
|
||||
### **/memory:load**
|
||||
- **Syntax**: `/memory:load [--tool gemini|qwen] "task context description"`
|
||||
- **Parameters**:
|
||||
- `"task context description"` (Required, String): Task description to guide context extraction.
|
||||
- `--tool <gemini|qwen>` (Optional): Specify CLI tool for agent to use (default: gemini).
|
||||
- **Responsibilities**: Delegates to `@general-purpose` agent to analyze the project and return a structured "Core Content Pack". This pack is loaded into the main thread's memory, providing essential context for subsequent operations.
|
||||
- **Agent-Driven Execution**: Fully delegates to general-purpose agent which autonomously:
|
||||
1. Analyzes project structure and documentation
|
||||
2. Extracts keywords from task description
|
||||
3. Discovers relevant files using MCP code-index or search tools
|
||||
4. Executes Gemini/Qwen CLI for deep analysis
|
||||
5. Generates structured JSON content package
|
||||
- **Core Philosophy**: Read-only analysis, token-efficient (CLI analysis in agent), structured output
|
||||
- **Agent Calls**: `@general-purpose` agent.
|
||||
- **Integration**: Provides quick, task-relevant context for subsequent agent operations while minimizing token consumption.
|
||||
- **Example**:
|
||||
```bash
|
||||
/memory:load "在当前前端基础上开发用户认证功能"
|
||||
/memory:load --tool qwen "重构支付模块API"
|
||||
```
|
||||
|
||||
### **/memory:update-related**
|
||||
- **Syntax**: `/memory:update-related [--tool gemini|qwen|codex]`
|
||||
- **Responsibilities**: Performs a context-aware update of `CLAUDE.md` files for modules affected by recent git changes.
|
||||
|
||||
@@ -226,6 +226,31 @@ Suitable for large-scale refactoring, architectural changes, or first-time CCW u
|
||||
- Weekly routine maintenance
|
||||
- When AI output drift is detected
|
||||
|
||||
#### Quick Context Loading for Specific Tasks
|
||||
|
||||
When you need immediate, task-specific context without updating documentation:
|
||||
|
||||
```bash
|
||||
# Load context for a specific task into memory
|
||||
/memory:load "在当前前端基础上开发用户认证功能"
|
||||
|
||||
# Use alternative CLI tool for analysis
|
||||
/memory:load --tool qwen "重构支付模块API"
|
||||
```
|
||||
|
||||
**How It Works**:
|
||||
- Delegates to an AI agent for autonomous project analysis
|
||||
- Discovers relevant files and extracts task-specific keywords
|
||||
- Uses CLI tools (Gemini/Qwen) for deep analysis to save tokens
|
||||
- Returns a structured "Core Content Pack" loaded into memory
|
||||
- Provides context for subsequent agent operations
|
||||
|
||||
**When to Use**:
|
||||
- Before starting a new feature or task
|
||||
- When you need quick context without full documentation rebuild
|
||||
- For task-specific architectural or pattern discovery
|
||||
- As preparation for agent-based development workflows
|
||||
|
||||
#### Incremental Related Module Updates
|
||||
|
||||
Suitable for daily development, updating only modules affected by changes:
|
||||
|
||||
@@ -26,6 +26,9 @@
|
||||
.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
|
||||
@@ -45,6 +48,14 @@
|
||||
.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
|
||||
#>
|
||||
|
||||
param(
|
||||
@@ -61,6 +72,8 @@ param(
|
||||
|
||||
[switch]$NoBackup,
|
||||
|
||||
[switch]$Uninstall,
|
||||
|
||||
[string]$SourceVersion = "",
|
||||
|
||||
[string]$SourceBranch = "",
|
||||
@@ -98,6 +111,9 @@ $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,
|
||||
@@ -704,6 +720,427 @@ function Merge-DirectoryContents {
|
||||
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
|
||||
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
||||
$manifestId = "install-$InstallationMode-$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 Save-InstallManifest {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Save the installation manifest to disk
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[hashtable]$Manifest
|
||||
)
|
||||
|
||||
try {
|
||||
# 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
|
||||
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
||||
$mode = if ($legacy.installation_mode) { $legacy.installation_mode } else { "Global" }
|
||||
$manifestId = "install-$mode-$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 Get-AllInstallManifests {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get all installation manifests
|
||||
#>
|
||||
|
||||
# Migrate legacy manifest if exists
|
||||
Migrate-LegacyManifest
|
||||
|
||||
if (-not (Test-Path $script:ManifestDir)) {
|
||||
return @()
|
||||
}
|
||||
|
||||
try {
|
||||
$manifestFiles = Get-ChildItem -Path $script:ManifestDir -Filter "install-*.json" -File | Sort-Object LastWriteTime -Descending
|
||||
$manifests = [System.Collections.ArrayList]::new()
|
||||
|
||||
foreach ($file in $manifestFiles) {
|
||||
try {
|
||||
$manifestJson = Get-Content -Path $file.FullName -Raw -Encoding utf8
|
||||
$manifest = $manifestJson | ConvertFrom-Json
|
||||
|
||||
# Convert to hashtable for easier manipulation
|
||||
# Handle both old and new manifest formats
|
||||
|
||||
# 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
|
||||
}
|
||||
}
|
||||
|
||||
$manifestHash = @{
|
||||
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
|
||||
}
|
||||
|
||||
$null = $manifests.Add($manifestHash)
|
||||
} catch {
|
||||
Write-ColorOutput "WARNING: Failed to load manifest $($file.Name): $($_.Exception.Message)" $ColorWarning
|
||||
}
|
||||
}
|
||||
|
||||
return ,$manifests.ToArray()
|
||||
} 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]
|
||||
Write-ColorOutput "Only one installation found, will uninstall:" $ColorInfo
|
||||
} else {
|
||||
# Multiple manifests - let user choose
|
||||
$options = @()
|
||||
for ($i = 0; $i -lt $manifests.Count; $i++) {
|
||||
$m = $manifests[$i]
|
||||
|
||||
# Safely extract date string
|
||||
$dateStr = "unknown date"
|
||||
if ($m.installation_date) {
|
||||
try {
|
||||
if ($m.installation_date.Length -ge 10) {
|
||||
$dateStr = $m.installation_date.Substring(0, 10)
|
||||
} else {
|
||||
$dateStr = $m.installation_date
|
||||
}
|
||||
} catch {
|
||||
$dateStr = "unknown date"
|
||||
}
|
||||
}
|
||||
|
||||
# Build option string with safe counts
|
||||
$filesCount = if ($m.files_count) { $m.files_count } else { 0 }
|
||||
$dirsCount = if ($m.directories_count) { $m.directories_count } else { 0 }
|
||||
$pathInfo = if ($m.installation_path) { " ($($m.installation_path))" } else { "" }
|
||||
$option = "$($i + 1). [$($m.installation_mode)] $dateStr - $filesCount files, $dirsCount dirs$pathInfo"
|
||||
$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
|
||||
Write-Host ""
|
||||
Write-ColorOutput "Installation Information:" $ColorInfo
|
||||
Write-Host " Manifest ID: $($selectedManifest.manifest_id)"
|
||||
Write-Host " Mode: $($selectedManifest.installation_mode)"
|
||||
Write-Host " Path: $($selectedManifest.installation_path)"
|
||||
Write-Host " Date: $($selectedManifest.installation_date)"
|
||||
Write-Host " Installer Version: $($selectedManifest.installer_version)"
|
||||
|
||||
# Use pre-calculated counts
|
||||
$filesCount = if ($selectedManifest.files_count) { $selectedManifest.files_count } else { 0 }
|
||||
$dirsCount = if ($selectedManifest.directories_count) { $selectedManifest.directories_count } else { 0 }
|
||||
Write-Host " Files tracked: $filesCount"
|
||||
Write-Host " Directories tracked: $dirsCount"
|
||||
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
|
||||
$removedDirs = 0
|
||||
$failedItems = @()
|
||||
|
||||
# Remove files first
|
||||
Write-ColorOutput "Removing installed files..." $ColorInfo
|
||||
foreach ($fileEntry in $manifest.files) {
|
||||
$filePath = $fileEntry.path
|
||||
|
||||
if (Test-Path $filePath) {
|
||||
try {
|
||||
Remove-Item -Path $filePath -Force -ErrorAction Stop
|
||||
Write-ColorOutput " Removed file: $filePath" $ColorSuccess
|
||||
$removedFiles++
|
||||
} catch {
|
||||
Write-ColorOutput " WARNING: Failed to remove file: $filePath" $ColorWarning
|
||||
$failedItems += $filePath
|
||||
}
|
||||
} else {
|
||||
Write-ColorOutput " File not found (already removed): $filePath" $ColorInfo
|
||||
}
|
||||
}
|
||||
|
||||
# Remove directories (in reverse order to handle nested directories)
|
||||
Write-ColorOutput "Removing installed directories..." $ColorInfo
|
||||
$sortedDirs = $manifest.directories | Sort-Object { $_.path.Length } -Descending
|
||||
|
||||
foreach ($dirEntry in $sortedDirs) {
|
||||
$dirPath = $dirEntry.path
|
||||
|
||||
if (Test-Path $dirPath) {
|
||||
try {
|
||||
# Check if directory is empty or only contains files we installed
|
||||
$dirContents = Get-ChildItem -Path $dirPath -Recurse -Force -ErrorAction SilentlyContinue
|
||||
|
||||
if (-not $dirContents -or ($dirContents | Measure-Object).Count -eq 0) {
|
||||
Remove-Item -Path $dirPath -Recurse -Force -ErrorAction Stop
|
||||
Write-ColorOutput " Removed directory: $dirPath" $ColorSuccess
|
||||
$removedDirs++
|
||||
} else {
|
||||
Write-ColorOutput " Directory not empty (preserved): $dirPath" $ColorWarning
|
||||
}
|
||||
} catch {
|
||||
Write-ColorOutput " WARNING: Failed to remove directory: $dirPath" $ColorWarning
|
||||
$failedItems += $dirPath
|
||||
}
|
||||
} else {
|
||||
Write-ColorOutput " Directory not found (already removed): $dirPath" $ColorInfo
|
||||
}
|
||||
}
|
||||
|
||||
# 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 ""
|
||||
Write-ColorOutput "========================================" $ColorInfo
|
||||
Write-ColorOutput "Uninstallation Summary:" $ColorInfo
|
||||
Write-Host " Files removed: $removedFiles"
|
||||
Write-Host " Directories removed: $removedDirs"
|
||||
|
||||
if ($failedItems.Count -gt 0) {
|
||||
Write-Host ""
|
||||
Write-ColorOutput "Failed to remove the following items:" $ColorWarning
|
||||
foreach ($item in $failedItems) {
|
||||
Write-Host " - $item"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
if ($failedItems.Count -eq 0) {
|
||||
Write-ColorOutput "Claude Code Workflow has been successfully uninstalled!" $ColorSuccess
|
||||
} else {
|
||||
Write-ColorOutput "Uninstallation completed with warnings." $ColorWarning
|
||||
Write-ColorOutput "Please manually remove the failed items listed above." $ColorInfo
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
function Create-VersionJson {
|
||||
param(
|
||||
[string]$TargetClaudeDir,
|
||||
@@ -751,6 +1188,9 @@ function Install-Global {
|
||||
|
||||
Write-ColorOutput "Global installation path: $userProfile" $ColorInfo
|
||||
|
||||
# Initialize manifest
|
||||
$manifest = New-InstallManifest -InstallationMode "Global" -InstallationPath $userProfile
|
||||
|
||||
# Source paths
|
||||
$sourceDir = $PSScriptRoot
|
||||
$sourceClaudeDir = Join-Path $sourceDir ".claude"
|
||||
@@ -791,22 +1231,73 @@ function Install-Global {
|
||||
Write-ColorOutput "Installing .claude directory..." $ColorInfo
|
||||
$claudeInstalled = Backup-AndReplaceDirectory -Source $sourceClaudeDir -Destination $globalClaudeDir -Description ".claude directory" -BackupFolder $backupFolder
|
||||
|
||||
# Track .claude directory in manifest
|
||||
if ($claudeInstalled) {
|
||||
Add-ManifestEntry -Manifest $manifest -Path $globalClaudeDir -Type "Directory"
|
||||
|
||||
# Track files from SOURCE directory, not destination
|
||||
Get-ChildItem -Path $sourceClaudeDir -Recurse -File | ForEach-Object {
|
||||
# Calculate target path where this file will be installed
|
||||
$relativePath = $_.FullName.Substring($sourceClaudeDir.Length)
|
||||
$targetPath = $globalClaudeDir + $relativePath
|
||||
Add-ManifestEntry -Manifest $manifest -Path $targetPath -Type "File"
|
||||
}
|
||||
}
|
||||
|
||||
# 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"
|
||||
}
|
||||
|
||||
# Replace .codex directory (backup → clear → copy entire folder)
|
||||
Write-ColorOutput "Installing .codex directory..." $ColorInfo
|
||||
$codexInstalled = Backup-AndReplaceDirectory -Source $sourceCodexDir -Destination $globalCodexDir -Description ".codex directory" -BackupFolder $backupFolder
|
||||
|
||||
# Track .codex directory in manifest
|
||||
if ($codexInstalled) {
|
||||
Add-ManifestEntry -Manifest $manifest -Path $globalCodexDir -Type "Directory"
|
||||
# Track files from SOURCE directory
|
||||
Get-ChildItem -Path $sourceCodexDir -Recurse -File | ForEach-Object {
|
||||
$relativePath = $_.FullName.Substring($sourceCodexDir.Length)
|
||||
$targetPath = $globalCodexDir + $relativePath
|
||||
Add-ManifestEntry -Manifest $manifest -Path $targetPath -Type "File"
|
||||
}
|
||||
}
|
||||
|
||||
# Replace .gemini directory (backup → clear → copy entire folder)
|
||||
Write-ColorOutput "Installing .gemini directory..." $ColorInfo
|
||||
$geminiInstalled = Backup-AndReplaceDirectory -Source $sourceGeminiDir -Destination $globalGeminiDir -Description ".gemini directory" -BackupFolder $backupFolder
|
||||
|
||||
# Track .gemini directory in manifest
|
||||
if ($geminiInstalled) {
|
||||
Add-ManifestEntry -Manifest $manifest -Path $globalGeminiDir -Type "Directory"
|
||||
# Track files from SOURCE directory
|
||||
Get-ChildItem -Path $sourceGeminiDir -Recurse -File | ForEach-Object {
|
||||
$relativePath = $_.FullName.Substring($sourceGeminiDir.Length)
|
||||
$targetPath = $globalGeminiDir + $relativePath
|
||||
Add-ManifestEntry -Manifest $manifest -Path $targetPath -Type "File"
|
||||
}
|
||||
}
|
||||
|
||||
# Replace .qwen directory (backup → clear → copy entire folder)
|
||||
Write-ColorOutput "Installing .qwen directory..." $ColorInfo
|
||||
$qwenInstalled = Backup-AndReplaceDirectory -Source $sourceQwenDir -Destination $globalQwenDir -Description ".qwen directory" -BackupFolder $backupFolder
|
||||
|
||||
# Track .qwen directory in manifest
|
||||
if ($qwenInstalled) {
|
||||
Add-ManifestEntry -Manifest $manifest -Path $globalQwenDir -Type "Directory"
|
||||
# Track files from SOURCE directory
|
||||
Get-ChildItem -Path $sourceQwenDir -Recurse -File | ForEach-Object {
|
||||
$relativePath = $_.FullName.Substring($sourceQwenDir.Length)
|
||||
$targetPath = $globalQwenDir + $relativePath
|
||||
Add-ManifestEntry -Manifest $manifest -Path $targetPath -Type "File"
|
||||
}
|
||||
}
|
||||
|
||||
# Create version.json in global .claude directory
|
||||
Write-ColorOutput "Creating version.json..." $ColorInfo
|
||||
Create-VersionJson -TargetClaudeDir $globalClaudeDir -InstallationMode "Global"
|
||||
@@ -820,6 +1311,9 @@ function Install-Global {
|
||||
}
|
||||
}
|
||||
|
||||
# Save installation manifest
|
||||
Save-InstallManifest -Manifest $manifest
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
@@ -837,6 +1331,9 @@ function Install-Path {
|
||||
|
||||
Write-ColorOutput "Global path: $userProfile" $ColorInfo
|
||||
|
||||
# Initialize manifest
|
||||
$manifest = New-InstallManifest -InstallationMode "Path" -InstallationPath $TargetDirectory
|
||||
|
||||
# Source paths
|
||||
$sourceDir = $PSScriptRoot
|
||||
$sourceClaudeDir = Join-Path $sourceDir ".claude"
|
||||
@@ -877,8 +1374,19 @@ function Install-Path {
|
||||
if (Test-Path $sourceFolderPath) {
|
||||
# Use new backup and replace logic for local folders
|
||||
Write-ColorOutput "Installing local folder: $folder..." $ColorInfo
|
||||
Backup-AndReplaceDirectory -Source $sourceFolderPath -Destination $destFolderPath -Description "$folder folder" -BackupFolder $backupFolder
|
||||
$folderInstalled = Backup-AndReplaceDirectory -Source $sourceFolderPath -Destination $destFolderPath -Description "$folder folder" -BackupFolder $backupFolder
|
||||
Write-ColorOutput "Installed local folder: $folder" $ColorSuccess
|
||||
|
||||
# Track local folder in manifest
|
||||
if ($folderInstalled) {
|
||||
Add-ManifestEntry -Manifest $manifest -Path $destFolderPath -Type "Directory"
|
||||
# Track files from SOURCE directory
|
||||
Get-ChildItem -Path $sourceFolderPath -Recurse -File | ForEach-Object {
|
||||
$relativePath = $_.FullName.Substring($sourceFolderPath.Length)
|
||||
$targetPath = $destFolderPath + $relativePath
|
||||
Add-ManifestEntry -Manifest $manifest -Path $targetPath -Type "File"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Write-ColorOutput "WARNING: Source folder not found: $folder" $ColorWarning
|
||||
}
|
||||
@@ -933,23 +1441,71 @@ function Install-Path {
|
||||
|
||||
Write-ColorOutput "Merged $mergedCount files to global location" $ColorSuccess
|
||||
|
||||
# Track global files in manifest
|
||||
$globalClaudeFiles = Get-ChildItem -Path $globalClaudeDir -Recurse -File | Where-Object {
|
||||
$relativePath = $_.FullName.Substring($globalClaudeDir.Length + 1)
|
||||
$topFolder = $relativePath.Split([System.IO.Path]::DirectorySeparatorChar)[0]
|
||||
$topFolder -notin $localFolders
|
||||
}
|
||||
foreach ($file in $globalClaudeFiles) {
|
||||
Add-ManifestEntry -Manifest $manifest -Path $file.FullName -Type "File"
|
||||
}
|
||||
|
||||
# Handle CLAUDE.md file in global .claude directory
|
||||
$globalClaudeMd = Join-Path $globalClaudeDir "CLAUDE.md"
|
||||
Write-ColorOutput "Installing CLAUDE.md to global .claude directory..." $ColorInfo
|
||||
Copy-FileToDestination -Source $sourceClaudeMd -Destination $globalClaudeMd -Description "CLAUDE.md" -BackupFolder $backupFolder
|
||||
$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"
|
||||
}
|
||||
|
||||
# Replace .codex directory to local location (backup → clear → copy entire folder)
|
||||
Write-ColorOutput "Installing .codex directory to local location..." $ColorInfo
|
||||
$codexInstalled = Backup-AndReplaceDirectory -Source $sourceCodexDir -Destination $localCodexDir -Description ".codex directory" -BackupFolder $backupFolder
|
||||
|
||||
# Track .codex directory in manifest
|
||||
if ($codexInstalled) {
|
||||
Add-ManifestEntry -Manifest $manifest -Path $localCodexDir -Type "Directory"
|
||||
# Track files from SOURCE directory
|
||||
Get-ChildItem -Path $sourceCodexDir -Recurse -File | ForEach-Object {
|
||||
$relativePath = $_.FullName.Substring($sourceCodexDir.Length)
|
||||
$targetPath = $localCodexDir + $relativePath
|
||||
Add-ManifestEntry -Manifest $manifest -Path $targetPath -Type "File"
|
||||
}
|
||||
}
|
||||
|
||||
# Replace .gemini directory to local location (backup → clear → copy entire folder)
|
||||
Write-ColorOutput "Installing .gemini directory to local location..." $ColorInfo
|
||||
$geminiInstalled = Backup-AndReplaceDirectory -Source $sourceGeminiDir -Destination $localGeminiDir -Description ".gemini directory" -BackupFolder $backupFolder
|
||||
|
||||
# Track .gemini directory in manifest
|
||||
if ($geminiInstalled) {
|
||||
Add-ManifestEntry -Manifest $manifest -Path $localGeminiDir -Type "Directory"
|
||||
# Track files from SOURCE directory
|
||||
Get-ChildItem -Path $sourceGeminiDir -Recurse -File | ForEach-Object {
|
||||
$relativePath = $_.FullName.Substring($sourceGeminiDir.Length)
|
||||
$targetPath = $localGeminiDir + $relativePath
|
||||
Add-ManifestEntry -Manifest $manifest -Path $targetPath -Type "File"
|
||||
}
|
||||
}
|
||||
|
||||
# Replace .qwen directory to local location (backup → clear → copy entire folder)
|
||||
Write-ColorOutput "Installing .qwen directory to local location..." $ColorInfo
|
||||
$qwenInstalled = Backup-AndReplaceDirectory -Source $sourceQwenDir -Destination $localQwenDir -Description ".qwen directory" -BackupFolder $backupFolder
|
||||
|
||||
# Track .qwen directory in manifest
|
||||
if ($qwenInstalled) {
|
||||
Add-ManifestEntry -Manifest $manifest -Path $localQwenDir -Type "Directory"
|
||||
# Track files from SOURCE directory
|
||||
Get-ChildItem -Path $sourceQwenDir -Recurse -File | ForEach-Object {
|
||||
$relativePath = $_.FullName.Substring($sourceQwenDir.Length)
|
||||
$targetPath = $localQwenDir + $relativePath
|
||||
Add-ManifestEntry -Manifest $manifest -Path $targetPath -Type "File"
|
||||
}
|
||||
}
|
||||
|
||||
# Create version.json in local .claude directory
|
||||
Write-ColorOutput "Creating version.json in local directory..." $ColorInfo
|
||||
Create-VersionJson -TargetClaudeDir $localClaudeDir -InstallationMode "Path"
|
||||
@@ -966,6 +1522,9 @@ function Install-Path {
|
||||
}
|
||||
}
|
||||
|
||||
# Save installation manifest
|
||||
Save-InstallManifest -Manifest $manifest
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
@@ -1098,6 +1657,42 @@ 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
|
||||
|
||||
@@ -24,10 +24,14 @@ FORCE=false
|
||||
NON_INTERACTIVE=false
|
||||
BACKUP_ALL=true # Enabled by default
|
||||
NO_BACKUP=false
|
||||
UNINSTALL=false # Uninstall mode
|
||||
SOURCE_VERSION="" # Version from remote installer
|
||||
SOURCE_BRANCH="" # Branch from remote installer
|
||||
SOURCE_COMMIT="" # Commit SHA from remote installer
|
||||
|
||||
# Global manifest directory location
|
||||
MANIFEST_DIR="${HOME}/.claude-manifests"
|
||||
|
||||
# Functions
|
||||
function write_color() {
|
||||
local message="$1"
|
||||
@@ -474,6 +478,9 @@ function install_global() {
|
||||
|
||||
write_color "Global installation path: $user_home" "$COLOR_INFO"
|
||||
|
||||
# Initialize manifest
|
||||
local manifest_file=$(new_install_manifest "Global" "$user_home")
|
||||
|
||||
# Source paths
|
||||
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
local source_claude_dir="${script_dir}/.claude"
|
||||
@@ -507,23 +514,66 @@ function install_global() {
|
||||
|
||||
# Replace .claude directory (backup → clear conflicting → copy)
|
||||
write_color "Installing .claude directory..." "$COLOR_INFO"
|
||||
backup_and_replace_directory "$source_claude_dir" "$global_claude_dir" ".claude directory" "$backup_folder"
|
||||
if backup_and_replace_directory "$source_claude_dir" "$global_claude_dir" ".claude directory" "$backup_folder"; then
|
||||
# Track .claude directory in manifest
|
||||
add_manifest_entry "$manifest_file" "$global_claude_dir" "Directory"
|
||||
|
||||
# Track files from SOURCE directory, not destination
|
||||
while IFS= read -r -d '' source_file; do
|
||||
local relative_path="${source_file#$source_claude_dir}"
|
||||
local target_path="${global_claude_dir}${relative_path}"
|
||||
add_manifest_entry "$manifest_file" "$target_path" "File"
|
||||
done < <(find "$source_claude_dir" -type f -print0)
|
||||
fi
|
||||
|
||||
# Handle CLAUDE.md file
|
||||
write_color "Installing CLAUDE.md to global .claude directory..." "$COLOR_INFO"
|
||||
copy_file_to_destination "$source_claude_md" "$global_claude_md" "CLAUDE.md" "$backup_folder"
|
||||
if copy_file_to_destination "$source_claude_md" "$global_claude_md" "CLAUDE.md" "$backup_folder"; then
|
||||
# Track CLAUDE.md in manifest
|
||||
add_manifest_entry "$manifest_file" "$global_claude_md" "File"
|
||||
fi
|
||||
|
||||
# Replace .codex directory (backup → clear conflicting → copy)
|
||||
write_color "Installing .codex directory..." "$COLOR_INFO"
|
||||
backup_and_replace_directory "$source_codex_dir" "$global_codex_dir" ".codex directory" "$backup_folder"
|
||||
if backup_and_replace_directory "$source_codex_dir" "$global_codex_dir" ".codex directory" "$backup_folder"; then
|
||||
# Track .codex directory in manifest
|
||||
add_manifest_entry "$manifest_file" "$global_codex_dir" "Directory"
|
||||
|
||||
# Track files from SOURCE directory
|
||||
while IFS= read -r -d '' source_file; do
|
||||
local relative_path="${source_file#$source_codex_dir}"
|
||||
local target_path="${global_codex_dir}${relative_path}"
|
||||
add_manifest_entry "$manifest_file" "$target_path" "File"
|
||||
done < <(find "$source_codex_dir" -type f -print0)
|
||||
fi
|
||||
|
||||
# Replace .gemini directory (backup → clear conflicting → copy)
|
||||
write_color "Installing .gemini directory..." "$COLOR_INFO"
|
||||
backup_and_replace_directory "$source_gemini_dir" "$global_gemini_dir" ".gemini directory" "$backup_folder"
|
||||
if backup_and_replace_directory "$source_gemini_dir" "$global_gemini_dir" ".gemini directory" "$backup_folder"; then
|
||||
# Track .gemini directory in manifest
|
||||
add_manifest_entry "$manifest_file" "$global_gemini_dir" "Directory"
|
||||
|
||||
# Track files from SOURCE directory
|
||||
while IFS= read -r -d '' source_file; do
|
||||
local relative_path="${source_file#$source_gemini_dir}"
|
||||
local target_path="${global_gemini_dir}${relative_path}"
|
||||
add_manifest_entry "$manifest_file" "$target_path" "File"
|
||||
done < <(find "$source_gemini_dir" -type f -print0)
|
||||
fi
|
||||
|
||||
# Replace .qwen directory (backup → clear conflicting → copy)
|
||||
write_color "Installing .qwen directory..." "$COLOR_INFO"
|
||||
backup_and_replace_directory "$source_qwen_dir" "$global_qwen_dir" ".qwen directory" "$backup_folder"
|
||||
if backup_and_replace_directory "$source_qwen_dir" "$global_qwen_dir" ".qwen directory" "$backup_folder"; then
|
||||
# Track .qwen directory in manifest
|
||||
add_manifest_entry "$manifest_file" "$global_qwen_dir" "Directory"
|
||||
|
||||
# Track files from SOURCE directory
|
||||
while IFS= read -r -d '' source_file; do
|
||||
local relative_path="${source_file#$source_qwen_dir}"
|
||||
local target_path="${global_qwen_dir}${relative_path}"
|
||||
add_manifest_entry "$manifest_file" "$target_path" "File"
|
||||
done < <(find "$source_qwen_dir" -type f -print0)
|
||||
fi
|
||||
|
||||
# Remove empty backup folder
|
||||
if [ -n "$backup_folder" ] && [ -d "$backup_folder" ]; then
|
||||
@@ -537,6 +587,9 @@ function install_global() {
|
||||
write_color "Creating version.json..." "$COLOR_INFO"
|
||||
create_version_json "$global_claude_dir" "Global"
|
||||
|
||||
# Save installation manifest
|
||||
save_install_manifest "$manifest_file"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -550,6 +603,9 @@ function install_path() {
|
||||
local global_claude_dir="${user_home}/.claude"
|
||||
write_color "Global path: $user_home" "$COLOR_INFO"
|
||||
|
||||
# Initialize manifest
|
||||
local manifest_file=$(new_install_manifest "Path" "$target_dir")
|
||||
|
||||
# Source paths
|
||||
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
local source_claude_dir="${script_dir}/.claude"
|
||||
@@ -588,7 +644,17 @@ function install_path() {
|
||||
if [ -d "$source_folder" ]; then
|
||||
# Use new backup and replace logic for local folders
|
||||
write_color "Installing local folder: $folder..." "$COLOR_INFO"
|
||||
backup_and_replace_directory "$source_folder" "$dest_folder" "$folder folder" "$backup_folder"
|
||||
if backup_and_replace_directory "$source_folder" "$dest_folder" "$folder folder" "$backup_folder"; then
|
||||
# Track local folder in manifest
|
||||
add_manifest_entry "$manifest_file" "$dest_folder" "Directory"
|
||||
|
||||
# Track files from SOURCE directory
|
||||
while IFS= read -r -d '' source_file; do
|
||||
local relative_path="${source_file#$source_folder}"
|
||||
local target_path="${dest_folder}${relative_path}"
|
||||
add_manifest_entry "$manifest_file" "$target_path" "File"
|
||||
done < <(find "$source_folder" -type f -print0)
|
||||
fi
|
||||
write_color "✓ Installed local folder: $folder" "$COLOR_SUCCESS"
|
||||
else
|
||||
write_color "WARNING: Source folder not found: $folder" "$COLOR_WARNING"
|
||||
@@ -644,19 +710,52 @@ function install_path() {
|
||||
# Handle CLAUDE.md file in global .claude directory
|
||||
local global_claude_md="${global_claude_dir}/CLAUDE.md"
|
||||
write_color "Installing CLAUDE.md to global .claude directory..." "$COLOR_INFO"
|
||||
copy_file_to_destination "$source_claude_md" "$global_claude_md" "CLAUDE.md" "$backup_folder"
|
||||
if copy_file_to_destination "$source_claude_md" "$global_claude_md" "CLAUDE.md" "$backup_folder"; then
|
||||
# Track CLAUDE.md in manifest
|
||||
add_manifest_entry "$manifest_file" "$global_claude_md" "File"
|
||||
fi
|
||||
|
||||
# Replace .codex directory to local location (backup → clear conflicting → copy)
|
||||
write_color "Installing .codex directory to local location..." "$COLOR_INFO"
|
||||
backup_and_replace_directory "$source_codex_dir" "$local_codex_dir" ".codex directory" "$backup_folder"
|
||||
if backup_and_replace_directory "$source_codex_dir" "$local_codex_dir" ".codex directory" "$backup_folder"; then
|
||||
# Track .codex directory in manifest
|
||||
add_manifest_entry "$manifest_file" "$local_codex_dir" "Directory"
|
||||
|
||||
# Track files from SOURCE directory
|
||||
while IFS= read -r -d '' source_file; do
|
||||
local relative_path="${source_file#$source_codex_dir}"
|
||||
local target_path="${local_codex_dir}${relative_path}"
|
||||
add_manifest_entry "$manifest_file" "$target_path" "File"
|
||||
done < <(find "$source_codex_dir" -type f -print0)
|
||||
fi
|
||||
|
||||
# Replace .gemini directory to local location (backup → clear conflicting → copy)
|
||||
write_color "Installing .gemini directory to local location..." "$COLOR_INFO"
|
||||
backup_and_replace_directory "$source_gemini_dir" "$local_gemini_dir" ".gemini directory" "$backup_folder"
|
||||
if backup_and_replace_directory "$source_gemini_dir" "$local_gemini_dir" ".gemini directory" "$backup_folder"; then
|
||||
# Track .gemini directory in manifest
|
||||
add_manifest_entry "$manifest_file" "$local_gemini_dir" "Directory"
|
||||
|
||||
# Track files from SOURCE directory
|
||||
while IFS= read -r -d '' source_file; do
|
||||
local relative_path="${source_file#$source_gemini_dir}"
|
||||
local target_path="${local_gemini_dir}${relative_path}"
|
||||
add_manifest_entry "$manifest_file" "$target_path" "File"
|
||||
done < <(find "$source_gemini_dir" -type f -print0)
|
||||
fi
|
||||
|
||||
# Replace .qwen directory to local location (backup → clear conflicting → copy)
|
||||
write_color "Installing .qwen directory to local location..." "$COLOR_INFO"
|
||||
backup_and_replace_directory "$source_qwen_dir" "$local_qwen_dir" ".qwen directory" "$backup_folder"
|
||||
if backup_and_replace_directory "$source_qwen_dir" "$local_qwen_dir" ".qwen directory" "$backup_folder"; then
|
||||
# Track .qwen directory in manifest
|
||||
add_manifest_entry "$manifest_file" "$local_qwen_dir" "Directory"
|
||||
|
||||
# Track files from SOURCE directory
|
||||
while IFS= read -r -d '' source_file; do
|
||||
local relative_path="${source_file#$source_qwen_dir}"
|
||||
local target_path="${local_qwen_dir}${relative_path}"
|
||||
add_manifest_entry "$manifest_file" "$target_path" "File"
|
||||
done < <(find "$source_qwen_dir" -type f -print0)
|
||||
fi
|
||||
|
||||
# Remove empty backup folder
|
||||
if [ -n "$backup_folder" ] && [ -d "$backup_folder" ]; then
|
||||
@@ -674,6 +773,9 @@ function install_path() {
|
||||
write_color "Creating version.json in global directory..." "$COLOR_INFO"
|
||||
create_version_json "$global_claude_dir" "Global"
|
||||
|
||||
# Save installation manifest
|
||||
save_install_manifest "$manifest_file"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -749,6 +851,357 @@ function get_installation_path() {
|
||||
done
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# INSTALLATION MANIFEST MANAGEMENT
|
||||
# ============================================================================
|
||||
|
||||
function new_install_manifest() {
|
||||
local installation_mode="$1"
|
||||
local installation_path="$2"
|
||||
|
||||
# Create manifest directory if it doesn't exist
|
||||
mkdir -p "$MANIFEST_DIR"
|
||||
|
||||
# Generate unique manifest ID based on timestamp and mode
|
||||
local timestamp=$(date +"%Y%m%d-%H%M%S")
|
||||
local manifest_id="install-${installation_mode}-${timestamp}"
|
||||
|
||||
# Create manifest file path
|
||||
local manifest_file="${MANIFEST_DIR}/${manifest_id}.json"
|
||||
|
||||
# Get current UTC timestamp
|
||||
local installation_date_utc=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
# Create manifest JSON
|
||||
cat > "$manifest_file" << EOF
|
||||
{
|
||||
"manifest_id": "$manifest_id",
|
||||
"version": "1.0",
|
||||
"installation_mode": "$installation_mode",
|
||||
"installation_path": "$installation_path",
|
||||
"installation_date": "$installation_date_utc",
|
||||
"installer_version": "$VERSION",
|
||||
"files": [],
|
||||
"directories": []
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "$manifest_file"
|
||||
}
|
||||
|
||||
function add_manifest_entry() {
|
||||
local manifest_file="$1"
|
||||
local entry_path="$2"
|
||||
local entry_type="$3"
|
||||
|
||||
if [ ! -f "$manifest_file" ]; then
|
||||
write_color "WARNING: Manifest file not found: $manifest_file" "$COLOR_WARNING"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
# Escape path for JSON
|
||||
local escaped_path=$(echo "$entry_path" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g')
|
||||
|
||||
# Create entry JSON
|
||||
local entry_json=$(cat << EOF
|
||||
{
|
||||
"path": "$escaped_path",
|
||||
"type": "$entry_type",
|
||||
"timestamp": "$timestamp"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
# Read manifest, add entry, write back
|
||||
local temp_file="${manifest_file}.tmp"
|
||||
|
||||
if [ "$entry_type" = "File" ]; then
|
||||
jq --argjson entry "$entry_json" '.files += [$entry]' "$manifest_file" > "$temp_file"
|
||||
else
|
||||
jq --argjson entry "$entry_json" '.directories += [$entry]' "$manifest_file" > "$temp_file"
|
||||
fi
|
||||
|
||||
mv "$temp_file" "$manifest_file"
|
||||
}
|
||||
|
||||
function save_install_manifest() {
|
||||
local manifest_file="$1"
|
||||
|
||||
if [ -f "$manifest_file" ]; then
|
||||
write_color "Installation manifest saved: $manifest_file" "$COLOR_SUCCESS"
|
||||
return 0
|
||||
else
|
||||
write_color "WARNING: Failed to save installation manifest" "$COLOR_WARNING"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
function migrate_legacy_manifest() {
|
||||
local legacy_manifest="${HOME}/.claude-install-manifest.json"
|
||||
|
||||
if [ ! -f "$legacy_manifest" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
write_color "Found legacy manifest file, migrating to new system..." "$COLOR_INFO"
|
||||
|
||||
# Create manifest directory if it doesn't exist
|
||||
mkdir -p "$MANIFEST_DIR"
|
||||
|
||||
# Read legacy manifest
|
||||
local mode=$(jq -r '.installation_mode // "Global"' "$legacy_manifest")
|
||||
local timestamp=$(date +"%Y%m%d-%H%M%S")
|
||||
local manifest_id="install-${mode}-${timestamp}-migrated"
|
||||
|
||||
# Create new manifest file
|
||||
local new_manifest="${MANIFEST_DIR}/${manifest_id}.json"
|
||||
|
||||
# Copy with new manifest_id field
|
||||
jq --arg id "$manifest_id" '. + {manifest_id: $id}' "$legacy_manifest" > "$new_manifest"
|
||||
|
||||
# Rename old manifest (don't delete, keep as backup)
|
||||
mv "$legacy_manifest" "${legacy_manifest}.migrated"
|
||||
|
||||
write_color "Legacy manifest migrated successfully" "$COLOR_SUCCESS"
|
||||
write_color "Old manifest backed up to: ${legacy_manifest}.migrated" "$COLOR_INFO"
|
||||
}
|
||||
|
||||
function get_all_install_manifests() {
|
||||
# Migrate legacy manifest if exists
|
||||
migrate_legacy_manifest
|
||||
|
||||
if [ ! -d "$MANIFEST_DIR" ]; then
|
||||
echo "[]"
|
||||
return
|
||||
fi
|
||||
|
||||
# Check if any manifest files exist
|
||||
local manifest_count=$(find "$MANIFEST_DIR" -name "install-*.json" -type f 2>/dev/null | wc -l)
|
||||
|
||||
if [ "$manifest_count" -eq 0 ]; then
|
||||
echo "[]"
|
||||
return
|
||||
fi
|
||||
|
||||
# Collect all manifests into JSON array
|
||||
local manifests="["
|
||||
local first=true
|
||||
|
||||
while IFS= read -r -d '' file; do
|
||||
if [ "$first" = true ]; then
|
||||
first=false
|
||||
else
|
||||
manifests+=","
|
||||
fi
|
||||
|
||||
# Add manifest_file field
|
||||
local manifest_content=$(jq --arg file "$file" '. + {manifest_file: $file}' "$file")
|
||||
|
||||
# Count files and directories safely
|
||||
local files_count=$(echo "$manifest_content" | jq '.files | length')
|
||||
local dirs_count=$(echo "$manifest_content" | jq '.directories | length')
|
||||
|
||||
# Add counts to manifest
|
||||
manifest_content=$(echo "$manifest_content" | jq --argjson fc "$files_count" --argjson dc "$dirs_count" '. + {files_count: $fc, directories_count: $dc}')
|
||||
|
||||
manifests+="$manifest_content"
|
||||
done < <(find "$MANIFEST_DIR" -name "install-*.json" -type f -print0 | sort -z)
|
||||
|
||||
manifests+="]"
|
||||
|
||||
echo "$manifests"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# UNINSTALLATION FUNCTIONS
|
||||
# ============================================================================
|
||||
|
||||
function uninstall_claude_workflow() {
|
||||
write_color "Claude Code Workflow System Uninstaller" "$COLOR_INFO"
|
||||
write_color "========================================" "$COLOR_INFO"
|
||||
echo ""
|
||||
|
||||
# Load all manifests
|
||||
local manifests_json=$(get_all_install_manifests)
|
||||
local manifests_count=$(echo "$manifests_json" | jq 'length')
|
||||
|
||||
if [ "$manifests_count" -eq 0 ]; then
|
||||
write_color "ERROR: No installation manifests found in: $MANIFEST_DIR" "$COLOR_ERROR"
|
||||
write_color "Cannot proceed with uninstallation without manifest." "$COLOR_ERROR"
|
||||
echo ""
|
||||
write_color "Manual uninstallation instructions:" "$COLOR_INFO"
|
||||
echo "For Global installation, remove these directories:"
|
||||
echo " - ~/.claude/agents"
|
||||
echo " - ~/.claude/commands"
|
||||
echo " - ~/.claude/output-styles"
|
||||
echo " - ~/.claude/workflows"
|
||||
echo " - ~/.claude/scripts"
|
||||
echo " - ~/.claude/prompt-templates"
|
||||
echo " - ~/.claude/python_script"
|
||||
echo " - ~/.claude/skills"
|
||||
echo " - ~/.claude/version.json"
|
||||
echo " - ~/.claude/CLAUDE.md"
|
||||
echo " - ~/.codex"
|
||||
echo " - ~/.gemini"
|
||||
echo " - ~/.qwen"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Display available installations
|
||||
write_color "Found $manifests_count installation(s):" "$COLOR_INFO"
|
||||
echo ""
|
||||
|
||||
# If only one manifest, use it directly
|
||||
local selected_index=0
|
||||
local selected_manifest=""
|
||||
|
||||
if [ "$manifests_count" -eq 1 ]; then
|
||||
selected_manifest=$(echo "$manifests_json" | jq '.[0]')
|
||||
write_color "Only one installation found, will uninstall:" "$COLOR_INFO"
|
||||
else
|
||||
# Multiple manifests - let user choose
|
||||
local options=()
|
||||
|
||||
for i in $(seq 0 $((manifests_count - 1))); do
|
||||
local m=$(echo "$manifests_json" | jq ".[$i]")
|
||||
|
||||
# Safely extract date string
|
||||
local date_str=$(echo "$m" | jq -r '.installation_date // "unknown date"' | cut -c1-10)
|
||||
local mode=$(echo "$m" | jq -r '.installation_mode // "Unknown"')
|
||||
local files_count=$(echo "$m" | jq -r '.files_count // 0')
|
||||
local dirs_count=$(echo "$m" | jq -r '.directories_count // 0')
|
||||
local path_info=$(echo "$m" | jq -r '.installation_path // ""')
|
||||
|
||||
if [ -n "$path_info" ]; then
|
||||
path_info=" ($path_info)"
|
||||
fi
|
||||
|
||||
options+=("$((i + 1)). [$mode] $date_str - $files_count files, $dirs_count dirs$path_info")
|
||||
done
|
||||
|
||||
options+=("Cancel - Don't uninstall anything")
|
||||
|
||||
echo ""
|
||||
local selection=$(get_user_choice "Select installation to uninstall:" "${options[@]}")
|
||||
|
||||
if [[ "$selection" == Cancel* ]]; then
|
||||
write_color "Uninstallation cancelled." "$COLOR_WARNING"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Parse selection to get index
|
||||
selected_index=$((${selection%%.*} - 1))
|
||||
selected_manifest=$(echo "$manifests_json" | jq ".[$selected_index]")
|
||||
fi
|
||||
|
||||
# Display selected installation info
|
||||
echo ""
|
||||
write_color "Installation Information:" "$COLOR_INFO"
|
||||
echo " Manifest ID: $(echo "$selected_manifest" | jq -r '.manifest_id')"
|
||||
echo " Mode: $(echo "$selected_manifest" | jq -r '.installation_mode')"
|
||||
echo " Path: $(echo "$selected_manifest" | jq -r '.installation_path')"
|
||||
echo " Date: $(echo "$selected_manifest" | jq -r '.installation_date')"
|
||||
echo " Installer Version: $(echo "$selected_manifest" | jq -r '.installer_version')"
|
||||
echo " Files tracked: $(echo "$selected_manifest" | jq -r '.files_count')"
|
||||
echo " Directories tracked: $(echo "$selected_manifest" | jq -r '.directories_count')"
|
||||
echo ""
|
||||
|
||||
# Confirm uninstallation
|
||||
if ! confirm_action "Do you want to uninstall this installation?" false; then
|
||||
write_color "Uninstallation cancelled." "$COLOR_WARNING"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local removed_files=0
|
||||
local removed_dirs=0
|
||||
local failed_items=()
|
||||
|
||||
# Remove files first
|
||||
write_color "Removing installed files..." "$COLOR_INFO"
|
||||
|
||||
local files_array=$(echo "$selected_manifest" | jq -c '.files[]')
|
||||
|
||||
while IFS= read -r file_entry; do
|
||||
local file_path=$(echo "$file_entry" | jq -r '.path')
|
||||
|
||||
if [ -f "$file_path" ]; then
|
||||
if rm -f "$file_path" 2>/dev/null; then
|
||||
write_color " Removed file: $file_path" "$COLOR_SUCCESS"
|
||||
((removed_files++))
|
||||
else
|
||||
write_color " WARNING: Failed to remove file: $file_path" "$COLOR_WARNING"
|
||||
failed_items+=("$file_path")
|
||||
fi
|
||||
else
|
||||
write_color " File not found (already removed): $file_path" "$COLOR_INFO"
|
||||
fi
|
||||
done <<< "$files_array"
|
||||
|
||||
# Remove directories (in reverse order by path length)
|
||||
write_color "Removing installed directories..." "$COLOR_INFO"
|
||||
|
||||
local dirs_array=$(echo "$selected_manifest" | jq -c '.directories[] | {path: .path, length: (.path | length)}' | sort -t: -k2 -rn | jq -c '.path')
|
||||
|
||||
while IFS= read -r dir_path_json; do
|
||||
local dir_path=$(echo "$dir_path_json" | jq -r '.')
|
||||
|
||||
if [ -d "$dir_path" ]; then
|
||||
# Check if directory is empty
|
||||
if [ -z "$(ls -A "$dir_path" 2>/dev/null)" ]; then
|
||||
if rmdir "$dir_path" 2>/dev/null; then
|
||||
write_color " Removed directory: $dir_path" "$COLOR_SUCCESS"
|
||||
((removed_dirs++))
|
||||
else
|
||||
write_color " WARNING: Failed to remove directory: $dir_path" "$COLOR_WARNING"
|
||||
failed_items+=("$dir_path")
|
||||
fi
|
||||
else
|
||||
write_color " Directory not empty (preserved): $dir_path" "$COLOR_WARNING"
|
||||
fi
|
||||
else
|
||||
write_color " Directory not found (already removed): $dir_path" "$COLOR_INFO"
|
||||
fi
|
||||
done <<< "$dirs_array"
|
||||
|
||||
# Remove manifest file
|
||||
local manifest_file=$(echo "$selected_manifest" | jq -r '.manifest_file')
|
||||
|
||||
if [ -f "$manifest_file" ]; then
|
||||
if rm -f "$manifest_file" 2>/dev/null; then
|
||||
write_color "Removed installation manifest: $(basename "$manifest_file")" "$COLOR_SUCCESS"
|
||||
else
|
||||
write_color "WARNING: Failed to remove manifest file" "$COLOR_WARNING"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Show summary
|
||||
echo ""
|
||||
write_color "========================================" "$COLOR_INFO"
|
||||
write_color "Uninstallation Summary:" "$COLOR_INFO"
|
||||
echo " Files removed: $removed_files"
|
||||
echo " Directories removed: $removed_dirs"
|
||||
|
||||
if [ ${#failed_items[@]} -gt 0 ]; then
|
||||
echo ""
|
||||
write_color "Failed to remove the following items:" "$COLOR_WARNING"
|
||||
for item in "${failed_items[@]}"; do
|
||||
echo " - $item"
|
||||
done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [ ${#failed_items[@]} -eq 0 ]; then
|
||||
write_color "✓ Claude Code Workflow has been successfully uninstalled!" "$COLOR_SUCCESS"
|
||||
else
|
||||
write_color "Uninstallation completed with warnings." "$COLOR_WARNING"
|
||||
write_color "Please manually remove the failed items listed above." "$COLOR_INFO"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
function create_version_json() {
|
||||
local target_claude_dir="$1"
|
||||
local installation_mode="$2"
|
||||
@@ -863,6 +1316,10 @@ function parse_arguments() {
|
||||
BACKUP_ALL=false
|
||||
shift
|
||||
;;
|
||||
-Uninstall)
|
||||
UNINSTALL=true
|
||||
shift
|
||||
;;
|
||||
-SourceVersion)
|
||||
SOURCE_VERSION="$2"
|
||||
shift 2
|
||||
@@ -901,6 +1358,7 @@ Options:
|
||||
-NonInteractive Run in non-interactive mode with default options
|
||||
-BackupAll Automatically backup all existing files (default)
|
||||
-NoBackup Disable automatic backup functionality
|
||||
-Uninstall Uninstall Claude Code Workflow System based on installation manifest
|
||||
-SourceVersion <ver> Source version (passed from install-remote.sh)
|
||||
-SourceBranch <name> Source branch (passed from install-remote.sh)
|
||||
-SourceCommit <sha> Source commit SHA (passed from install-remote.sh)
|
||||
@@ -919,6 +1377,12 @@ Examples:
|
||||
# Installation without backup
|
||||
$0 -NoBackup
|
||||
|
||||
# Uninstall Claude Code Workflow System
|
||||
$0 -Uninstall
|
||||
|
||||
# Uninstall without confirmation prompts
|
||||
$0 -Uninstall -Force
|
||||
|
||||
# With version info (typically called by install-remote.sh)
|
||||
$0 -InstallMode Global -Force -SourceVersion "3.4.2" -SourceBranch "main" -SourceCommit "abc1234"
|
||||
|
||||
@@ -926,6 +1390,46 @@ EOF
|
||||
}
|
||||
|
||||
function main() {
|
||||
# Show banner first
|
||||
show_banner
|
||||
|
||||
# Check for uninstall mode from parameter or ask user interactively
|
||||
local operation_mode="Install"
|
||||
|
||||
if [ "$UNINSTALL" = true ]; then
|
||||
operation_mode="Uninstall"
|
||||
elif [ "$NON_INTERACTIVE" != true ] && [ -z "$INSTALL_MODE" ]; then
|
||||
# Interactive mode selection
|
||||
echo ""
|
||||
local operations=(
|
||||
"Install - Install Claude Code Workflow System"
|
||||
"Uninstall - Remove Claude Code Workflow System"
|
||||
)
|
||||
local selection=$(get_user_choice "Choose operation:" "${operations[@]}")
|
||||
|
||||
if [[ "$selection" == Uninstall* ]]; then
|
||||
operation_mode="Uninstall"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Handle uninstall mode
|
||||
if [ "$operation_mode" = "Uninstall" ]; then
|
||||
if uninstall_claude_workflow; then
|
||||
local result=0
|
||||
else
|
||||
local result=1
|
||||
fi
|
||||
|
||||
if [ "$NON_INTERACTIVE" != true ]; then
|
||||
echo ""
|
||||
write_color "Press Enter to exit..." "$COLOR_PROMPT"
|
||||
read -r
|
||||
fi
|
||||
|
||||
return $result
|
||||
fi
|
||||
|
||||
# Continue with installation
|
||||
show_header
|
||||
|
||||
# Test prerequisites
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](https://github.com/catlog22/Claude-Code-Workflow/releases)
|
||||
[](https://github.com/catlog22/Claude-Code-Workflow/releases)
|
||||
[](LICENSE)
|
||||
[]()
|
||||
[](https://github.com/modelcontextprotocol)
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
**Claude Code Workflow (CCW)** transforms AI development from simple prompt chaining into a robust, context-first orchestration system. It solves execution uncertainty and error accumulation through structured planning, deterministic execution, and intelligent multi-model orchestration.
|
||||
|
||||
> **🎉 Latest: v4.6.0** - Concept Clarification & Agent-Driven Analysis. See [CHANGELOG.md](CHANGELOG.md) for details.
|
||||
> **🎉 Latest: v4.6.2** - Documentation Optimization & `/memory:load` Command Refinement. See [CHANGELOG.md](CHANGELOG.md) for details.
|
||||
|
||||
> 📚 **New to CCW?** Check out the [**Getting Started Guide**](GETTING_STARTED.md) for a beginner-friendly 5-minute tutorial!
|
||||
|
||||
|
||||
Reference in New Issue
Block a user