From a03415bfeb9ef1a7febefd89060e44a7d2ee213d Mon Sep 17 00:00:00 2001 From: catlog22 Date: Fri, 3 Oct 2025 12:46:44 +0800 Subject: [PATCH] feat: Add version tracking and upgrade check system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add /version command to check installed and latest versions - Create version.json metadata file during installation - Pass version and branch info from install-remote.ps1 to Install-Claude.ps1 - Support version comparison and upgrade recommendations - Track installation mode (Global/Path) and source branch 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .claude/commands/version.md | 212 ++++++++++++++++++++++++++++++++++++ Install-Claude.ps1 | 49 ++++++++- install-remote.ps1 | 42 ++++--- 3 files changed, 287 insertions(+), 16 deletions(-) create mode 100644 .claude/commands/version.md diff --git a/.claude/commands/version.md b/.claude/commands/version.md new file mode 100644 index 00000000..307a3adc --- /dev/null +++ b/.claude/commands/version.md @@ -0,0 +1,212 @@ +--- +name: version +description: Display version information and check for updates +usage: /version +examples: + - /version +allowed-tools: Bash(*) +--- + +# Version Command (/version) + +## Purpose +Display local and global installation versions, check for the latest updates from GitHub, and provide upgrade recommendations. + +## Execution Flow +1. **Local Version Check**: Read version information from `./.claude/version.json` if it exists. +2. **Global Version Check**: Read version information from `~/.claude/version.json` if it exists. +3. **Fetch Remote Versions**: Use GitHub API to get the latest stable release tag and the latest commit hash from the main branch. +4. **Compare & Suggest**: Compare installed versions with the latest remote versions and provide upgrade suggestions if applicable. + +## Step 1: Check Local Version + +### Check if local version.json exists +```bash +bash(test -f ./.claude/version.json && echo "found" || echo "not_found") +``` + +### Read local version (if exists) +```bash +bash(cat ./.claude/version.json) +``` + +### Extract version with jq (preferred) +```bash +bash(cat ./.claude/version.json | grep -o '"version": *"[^"]*"' | cut -d'"' -f4) +``` + +### Extract installation date +```bash +bash(cat ./.claude/version.json | grep -o '"installation_date_utc": *"[^"]*"' | cut -d'"' -f4) +``` + +**Output Format**: +``` +Local Version: 3.2.1 +Installed: 2025-10-03T12:00:00Z +``` + +## Step 2: Check Global Version + +### Check if global version.json exists +```bash +bash(test -f ~/.claude/version.json && echo "found" || echo "not_found") +``` + +### Read global version +```bash +bash(cat ~/.claude/version.json) +``` + +### Extract version +```bash +bash(cat ~/.claude/version.json | grep -o '"version": *"[^"]*"' | cut -d'"' -f4) +``` + +### Extract installation date +```bash +bash(cat ~/.claude/version.json | grep -o '"installation_date_utc": *"[^"]*"' | cut -d'"' -f4) +``` + +**Output Format**: +``` +Global Version: 3.2.1 +Installed: 2025-10-03T12:00:00Z +``` + +## Step 3: Fetch Latest Stable Release + +### Call GitHub API for latest release +```bash +bash(curl -fsSL "https://api.github.com/repos/catlog22/Claude-Code-Workflow/releases/latest" 2>/dev/null) +``` + +### Extract tag name (version) +```bash +bash(curl -fsSL "https://api.github.com/repos/catlog22/Claude-Code-Workflow/releases/latest" 2>/dev/null | grep -o '"tag_name": *"[^"]*"' | head -1 | cut -d'"' -f4) +``` + +**Output Format**: +``` +Latest Stable: v3.2.2 +``` + +## Step 4: Fetch Latest Main Branch + +### Call GitHub API for main branch +```bash +bash(curl -fsSL "https://api.github.com/repos/catlog22/Claude-Code-Workflow/branches/main" 2>/dev/null) +``` + +### Extract commit SHA (short) +```bash +bash(curl -fsSL "https://api.github.com/repos/catlog22/Claude-Code-Workflow/branches/main" 2>/dev/null | grep -o '"sha": *"[^"]*"' | head -1 | cut -d'"' -f4 | cut -c1-7) +``` + +**Output Format**: +``` +Latest Dev: a1b2c3d +``` + +## Step 5: Compare Versions and Suggest Upgrade + +### Normalize versions (remove 'v' prefix) +```bash +bash(echo "v3.2.1" | sed 's/^v//') +``` + +### Compare two versions +```bash +bash(printf "%s\n%s" "3.2.1" "3.2.2" | sort -V | tail -n 1) +``` + +### Check if versions are equal +```bash +# If equal: Up to date +# If remote newer: Upgrade available +# If local newer: Development version +``` + +**Output Scenarios**: + +**Scenario 1: Up to date** +``` +✅ You are on the latest stable version (3.2.1) +``` + +**Scenario 2: Upgrade available** +``` +⬆️ A newer stable version is available: v3.2.2 +Your version: 3.2.1 + +To upgrade: +PowerShell: iex (iwr -useb https://raw.githubusercontent.com/catlog22/Claude-Code-Workflow/main/install-remote.ps1) +Bash: bash <(curl -fsSL https://raw.githubusercontent.com/catlog22/Claude-Code-Workflow/main/install-remote.sh) +``` + +**Scenario 3: Development version** +``` +✨ You are running a development version (3.3.0-dev) +This is newer than the latest stable release (v3.2.2) +``` + +## Simple Bash Commands + +### Basic Operations +```bash +# Check local version file +bash(test -f ./.claude/version.json && cat ./.claude/version.json) + +# Check global version file +bash(test -f ~/.claude/version.json && cat ~/.claude/version.json) + +# Extract version from JSON +bash(cat version.json | grep -o '"version": *"[^"]*"' | cut -d'"' -f4) + +# Extract date from JSON +bash(cat version.json | grep -o '"installation_date_utc": *"[^"]*"' | cut -d'"' -f4) + +# Fetch latest release +bash(curl -fsSL "https://api.github.com/repos/catlog22/Claude-Code-Workflow/releases/latest") + +# Extract tag name +bash(curl -s https://api.github.com/repos/catlog22/Claude-Code-Workflow/releases/latest | grep -o '"tag_name": *"[^"]*"' | cut -d'"' -f4) + +# Compare versions +bash(printf "%s\n%s" "3.2.1" "3.2.2" | sort -V | tail -n 1) + +# Remove 'v' prefix +bash(echo "v3.2.1" | sed 's/^v//') +``` + +## Error Handling + +### No installation found +``` +WARNING: Claude Code Workflow not installed +Install using: +PowerShell: iex (iwr -useb https://raw.githubusercontent.com/catlog22/Claude-Code-Workflow/main/install-remote.ps1) +``` + +### Network error +``` +ERROR: Could not fetch latest version from GitHub +Check your network connection +``` + +### Invalid version.json +``` +ERROR: version.json is invalid or corrupted +``` + +## Design Notes + +- Uses simple, direct bash commands instead of complex functions +- Each step is independent and can be executed separately +- Fallback to grep/sed for JSON parsing (no jq dependency required) +- Network calls use curl with error suppression +- Version comparison uses `sort -V` for accurate semantic versioning + +## Related Commands +- `/cli:cli-init` - Initialize CLI configurations +- `/workflow:session:list` - List workflow sessions diff --git a/Install-Claude.ps1 b/Install-Claude.ps1 index 7d33e2bb..d99680dc 100644 --- a/Install-Claude.ps1 +++ b/Install-Claude.ps1 @@ -59,7 +59,11 @@ param( [switch]$BackupAll, - [switch]$NoBackup + [switch]$NoBackup, + + [string]$SourceVersion = "", + + [string]$SourceBranch = "" ) # Set encoding for proper Unicode support @@ -622,6 +626,37 @@ function Merge-DirectoryContents { return $true } +function Create-VersionJson { + param( + [string]$TargetClaudeDir, + [string]$InstallationMode + ) + + # Determine version from source or default + $versionNumber = if ($SourceVersion) { $SourceVersion } else { $Version } + $sourceBranch = if ($SourceBranch) { $SourceBranch } else { "unknown" } + + # Create version.json content + $versionInfo = @{ + version = $versionNumber + installation_mode = $InstallationMode + installation_path = $TargetClaudeDir + installation_date_utc = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") + source_branch = $sourceBranch + } + + $versionJsonPath = Join-Path $TargetClaudeDir "version.json" + + try { + $versionInfo | ConvertTo-Json | Out-File -FilePath $versionJsonPath -Encoding utf8 -Force + Write-ColorOutput "Created version.json: $versionNumber ($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 @@ -682,6 +717,10 @@ function Install-Global { Write-ColorOutput "Merging .gemini directory contents..." $ColorInfo $geminiMerged = Merge-DirectoryContents -Source $sourceGeminiDir -Destination $globalGeminiDir -Description ".gemini directory contents" -BackupFolder $backupFolder + # 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) { @@ -819,6 +858,14 @@ function Install-Path { Write-ColorOutput "Merging .gemini directory contents to local location..." $ColorInfo $geminiMerged = Merge-DirectoryContents -Source $sourceGeminiDir -Destination $localGeminiDir -Description ".gemini directory contents" -BackupFolder $backupFolder + # 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) { diff --git a/install-remote.ps1 b/install-remote.ps1 index e48571df..6c07484c 100644 --- a/install-remote.ps1 +++ b/install-remote.ps1 @@ -258,23 +258,25 @@ function Extract-Repository { function Invoke-LocalInstaller { param( - [string]$RepoDir + [string]$RepoDir, + [string]$VersionInfo = "", + [string]$BranchInfo = "" ) - + $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) { + if ($Directory) { $params["InstallMode"] = "Custom" $params["TargetPath"] = $Directory } @@ -282,11 +284,15 @@ function Invoke-LocalInstaller { if ($NoBackup) { $params["NoBackup"] = $NoBackup } if ($NonInteractive) { $params["NonInteractive"] = $NonInteractive } if ($BackupAll) { $params["BackupAll"] = $BackupAll } - + + # Pass version and branch information + if ($VersionInfo) { $params["SourceVersion"] = $VersionInfo } + if ($BranchInfo) { $params["SourceBranch"] = $BranchInfo } + 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 @@ -295,7 +301,7 @@ function Invoke-LocalInstaller { Write-ColorOutput "Executing: & `"$installerPath`"" $ColorInfo & $installerPath } - + Pop-Location return $true } catch { @@ -542,26 +548,32 @@ function Main { # 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" } - - # Run local installer - $success = Invoke-LocalInstaller $repoDir + + # Determine version and branch information to pass + $versionToPass = if ($Tag) { $Tag } else { "latest" } + $branchToPass = if ($Version -eq "branch") { $Branch } elseif ($Version -eq "latest") { "main" } elseif ($Tag) { $Tag } else { "main" } + + Write-ColorOutput "Version info: $versionToPass (branch: $branchToPass)" $ColorInfo + + # Run local installer with version information + $success = Invoke-LocalInstaller -RepoDir $repoDir -VersionInfo $versionToPass -BranchInfo $branchToPass if (-not $success) { throw "Installation script failed" } - + Write-Host "" Write-ColorOutput "Remote installation completed successfully!" $ColorSuccess