mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-10 02:24:35 +08:00
feat: Add version tracking and upgrade check system
- 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 <noreply@anthropic.com>
This commit is contained in:
212
.claude/commands/version.md
Normal file
212
.claude/commands/version.md
Normal file
@@ -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
|
||||||
@@ -59,7 +59,11 @@ param(
|
|||||||
|
|
||||||
[switch]$BackupAll,
|
[switch]$BackupAll,
|
||||||
|
|
||||||
[switch]$NoBackup
|
[switch]$NoBackup,
|
||||||
|
|
||||||
|
[string]$SourceVersion = "",
|
||||||
|
|
||||||
|
[string]$SourceBranch = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set encoding for proper Unicode support
|
# Set encoding for proper Unicode support
|
||||||
@@ -622,6 +626,37 @@ function Merge-DirectoryContents {
|
|||||||
return $true
|
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 {
|
function Install-Global {
|
||||||
Write-ColorOutput "Installing Claude Code Workflow System globally..." $ColorInfo
|
Write-ColorOutput "Installing Claude Code Workflow System globally..." $ColorInfo
|
||||||
|
|
||||||
@@ -682,6 +717,10 @@ function Install-Global {
|
|||||||
Write-ColorOutput "Merging .gemini directory contents..." $ColorInfo
|
Write-ColorOutput "Merging .gemini directory contents..." $ColorInfo
|
||||||
$geminiMerged = Merge-DirectoryContents -Source $sourceGeminiDir -Destination $globalGeminiDir -Description ".gemini directory contents" -BackupFolder $backupFolder
|
$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)) {
|
if ($backupFolder -and (Test-Path $backupFolder)) {
|
||||||
$backupFiles = Get-ChildItem $backupFolder -Recurse -File -ErrorAction SilentlyContinue
|
$backupFiles = Get-ChildItem $backupFolder -Recurse -File -ErrorAction SilentlyContinue
|
||||||
if (-not $backupFiles -or ($backupFiles | Measure-Object).Count -eq 0) {
|
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
|
Write-ColorOutput "Merging .gemini directory contents to local location..." $ColorInfo
|
||||||
$geminiMerged = Merge-DirectoryContents -Source $sourceGeminiDir -Destination $localGeminiDir -Description ".gemini directory contents" -BackupFolder $backupFolder
|
$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)) {
|
if ($backupFolder -and (Test-Path $backupFolder)) {
|
||||||
$backupFiles = Get-ChildItem $backupFolder -Recurse -File -ErrorAction SilentlyContinue
|
$backupFiles = Get-ChildItem $backupFolder -Recurse -File -ErrorAction SilentlyContinue
|
||||||
if (-not $backupFiles -or ($backupFiles | Measure-Object).Count -eq 0) {
|
if (-not $backupFiles -or ($backupFiles | Measure-Object).Count -eq 0) {
|
||||||
|
|||||||
@@ -258,23 +258,25 @@ function Extract-Repository {
|
|||||||
|
|
||||||
function Invoke-LocalInstaller {
|
function Invoke-LocalInstaller {
|
||||||
param(
|
param(
|
||||||
[string]$RepoDir
|
[string]$RepoDir,
|
||||||
|
[string]$VersionInfo = "",
|
||||||
|
[string]$BranchInfo = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
$installerPath = Join-Path $RepoDir "Install-Claude.ps1"
|
$installerPath = Join-Path $RepoDir "Install-Claude.ps1"
|
||||||
|
|
||||||
if (-not (Test-Path $installerPath)) {
|
if (-not (Test-Path $installerPath)) {
|
||||||
Write-ColorOutput "ERROR: Install-Claude.ps1 not found" $ColorError
|
Write-ColorOutput "ERROR: Install-Claude.ps1 not found" $ColorError
|
||||||
return $false
|
return $false
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-ColorOutput "Running local installer..." $ColorInfo
|
Write-ColorOutput "Running local installer..." $ColorInfo
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
|
|
||||||
# Build parameters for local installer
|
# Build parameters for local installer
|
||||||
$params = @{}
|
$params = @{}
|
||||||
if ($Global) { $params["InstallMode"] = "Global" }
|
if ($Global) { $params["InstallMode"] = "Global" }
|
||||||
if ($Directory) {
|
if ($Directory) {
|
||||||
$params["InstallMode"] = "Custom"
|
$params["InstallMode"] = "Custom"
|
||||||
$params["TargetPath"] = $Directory
|
$params["TargetPath"] = $Directory
|
||||||
}
|
}
|
||||||
@@ -282,11 +284,15 @@ function Invoke-LocalInstaller {
|
|||||||
if ($NoBackup) { $params["NoBackup"] = $NoBackup }
|
if ($NoBackup) { $params["NoBackup"] = $NoBackup }
|
||||||
if ($NonInteractive) { $params["NonInteractive"] = $NonInteractive }
|
if ($NonInteractive) { $params["NonInteractive"] = $NonInteractive }
|
||||||
if ($BackupAll) { $params["BackupAll"] = $BackupAll }
|
if ($BackupAll) { $params["BackupAll"] = $BackupAll }
|
||||||
|
|
||||||
|
# Pass version and branch information
|
||||||
|
if ($VersionInfo) { $params["SourceVersion"] = $VersionInfo }
|
||||||
|
if ($BranchInfo) { $params["SourceBranch"] = $BranchInfo }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
# Change to repo directory and run installer
|
# Change to repo directory and run installer
|
||||||
Push-Location $RepoDir
|
Push-Location $RepoDir
|
||||||
|
|
||||||
if ($params.Count -gt 0) {
|
if ($params.Count -gt 0) {
|
||||||
$paramList = ($params.GetEnumerator() | ForEach-Object { "-$($_.Key) $($_.Value)" }) -join " "
|
$paramList = ($params.GetEnumerator() | ForEach-Object { "-$($_.Key) $($_.Value)" }) -join " "
|
||||||
Write-ColorOutput "Executing: & `"$installerPath`" $paramList" $ColorInfo
|
Write-ColorOutput "Executing: & `"$installerPath`" $paramList" $ColorInfo
|
||||||
@@ -295,7 +301,7 @@ function Invoke-LocalInstaller {
|
|||||||
Write-ColorOutput "Executing: & `"$installerPath`"" $ColorInfo
|
Write-ColorOutput "Executing: & `"$installerPath`"" $ColorInfo
|
||||||
& $installerPath
|
& $installerPath
|
||||||
}
|
}
|
||||||
|
|
||||||
Pop-Location
|
Pop-Location
|
||||||
return $true
|
return $true
|
||||||
} catch {
|
} catch {
|
||||||
@@ -542,26 +548,32 @@ function Main {
|
|||||||
# Create temp directory
|
# Create temp directory
|
||||||
$tempDir = Get-TempDirectory
|
$tempDir = Get-TempDirectory
|
||||||
Write-ColorOutput "Temporary directory: $tempDir" $ColorInfo
|
Write-ColorOutput "Temporary directory: $tempDir" $ColorInfo
|
||||||
|
|
||||||
try {
|
try {
|
||||||
# Download repository
|
# Download repository
|
||||||
$zipPath = Download-Repository -TempDir $tempDir -Version $Version -Branch $Branch -Tag $Tag
|
$zipPath = Download-Repository -TempDir $tempDir -Version $Version -Branch $Branch -Tag $Tag
|
||||||
if (-not $zipPath) {
|
if (-not $zipPath) {
|
||||||
throw "Download failed"
|
throw "Download failed"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Extract repository
|
# Extract repository
|
||||||
$repoDir = Extract-Repository $zipPath $tempDir
|
$repoDir = Extract-Repository $zipPath $tempDir
|
||||||
if (-not $repoDir) {
|
if (-not $repoDir) {
|
||||||
throw "Extraction failed"
|
throw "Extraction failed"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Run local installer
|
# Determine version and branch information to pass
|
||||||
$success = Invoke-LocalInstaller $repoDir
|
$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) {
|
if (-not $success) {
|
||||||
throw "Installation script failed"
|
throw "Installation script failed"
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-ColorOutput "Remote installation completed successfully!" $ColorSuccess
|
Write-ColorOutput "Remote installation completed successfully!" $ColorSuccess
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user