mirror of
https://github.com/SuperClaude-Org/SuperClaude_Framework.git
synced 2025-12-18 02:06:36 +00:00
✨ Add automatic update checking for PyPI and NPM packages
- Check for updates on startup (once per 24h) - Show update banner when new version available - Support --no-update-check and --auto-update flags - Add SUPERCLAUDE_AUTO_UPDATE environment variable - Implement for both Python (PyPI) and Node.js (NPM)
This commit is contained in:
parent
06ee059c0b
commit
291b8a0c2b
@ -73,6 +73,10 @@ def create_global_parser() -> argparse.ArgumentParser:
|
|||||||
help="Force execution, skipping checks")
|
help="Force execution, skipping checks")
|
||||||
global_parser.add_argument("--yes", "-y", action="store_true",
|
global_parser.add_argument("--yes", "-y", action="store_true",
|
||||||
help="Automatically answer yes to all prompts")
|
help="Automatically answer yes to all prompts")
|
||||||
|
global_parser.add_argument("--no-update-check", action="store_true",
|
||||||
|
help="Skip checking for updates")
|
||||||
|
global_parser.add_argument("--auto-update", action="store_true",
|
||||||
|
help="Automatically install updates without prompting")
|
||||||
|
|
||||||
return global_parser
|
return global_parser
|
||||||
|
|
||||||
@ -199,6 +203,26 @@ def main() -> int:
|
|||||||
operations = register_operation_parsers(subparsers, global_parser)
|
operations = register_operation_parsers(subparsers, global_parser)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Check for updates unless disabled
|
||||||
|
if not args.quiet and not getattr(args, 'no_update_check', False):
|
||||||
|
try:
|
||||||
|
from setup.utils.updater import check_for_updates
|
||||||
|
# Check for updates in the background
|
||||||
|
updated = check_for_updates(
|
||||||
|
current_version="4.0.6",
|
||||||
|
auto_update=getattr(args, 'auto_update', False)
|
||||||
|
)
|
||||||
|
# If updated, suggest restart
|
||||||
|
if updated:
|
||||||
|
print("\n🔄 SuperClaude was updated. Please restart to use the new version.")
|
||||||
|
return 0
|
||||||
|
except ImportError:
|
||||||
|
# Updater module not available, skip silently
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
# Any other error, skip silently
|
||||||
|
pass
|
||||||
|
|
||||||
# No operation provided? Show help manually unless in quiet mode
|
# No operation provided? Show help manually unless in quiet mode
|
||||||
if not args.operation:
|
if not args.operation:
|
||||||
if not args.quiet:
|
if not args.quiet:
|
||||||
|
|||||||
276
bin/checkUpdate.js
Normal file
276
bin/checkUpdate.js
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Auto-update checker for SuperClaude NPM package
|
||||||
|
* Checks npm registry for newer versions and offers automatic updates
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { spawnSync } = require('child_process');
|
||||||
|
const https = require('https');
|
||||||
|
|
||||||
|
const CACHE_FILE = path.join(process.env.HOME || process.env.USERPROFILE, '.claude', '.npm_update_check');
|
||||||
|
const CHECK_INTERVAL = 86400000; // 24 hours in milliseconds
|
||||||
|
const TIMEOUT = 2000; // 2 seconds
|
||||||
|
const PACKAGE_NAME = '@bifrost_inc/superclaude';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current package version from package.json
|
||||||
|
*/
|
||||||
|
function getCurrentVersion() {
|
||||||
|
try {
|
||||||
|
const packagePath = path.join(__dirname, '..', 'package.json');
|
||||||
|
const packageData = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
||||||
|
return packageData.version;
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if we should perform an update check based on last check time
|
||||||
|
*/
|
||||||
|
function shouldCheckUpdate(force = false) {
|
||||||
|
if (force) return true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(CACHE_FILE)) return true;
|
||||||
|
|
||||||
|
const data = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8'));
|
||||||
|
const lastCheck = data.lastCheck || 0;
|
||||||
|
|
||||||
|
// Check if 24 hours have passed
|
||||||
|
return Date.now() - lastCheck > CHECK_INTERVAL;
|
||||||
|
} catch {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the current timestamp as last check time
|
||||||
|
*/
|
||||||
|
function saveCheckTimestamp() {
|
||||||
|
const cacheDir = path.dirname(CACHE_FILE);
|
||||||
|
|
||||||
|
// Create directory if it doesn't exist
|
||||||
|
if (!fs.existsSync(cacheDir)) {
|
||||||
|
fs.mkdirSync(cacheDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = {};
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(CACHE_FILE)) {
|
||||||
|
data = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8'));
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Ignore errors
|
||||||
|
}
|
||||||
|
|
||||||
|
data.lastCheck = Date.now();
|
||||||
|
fs.writeFileSync(CACHE_FILE, JSON.stringify(data, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query npm registry for the latest version
|
||||||
|
*/
|
||||||
|
function getLatestVersion() {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const options = {
|
||||||
|
hostname: 'registry.npmjs.org',
|
||||||
|
path: `/${PACKAGE_NAME}/latest`,
|
||||||
|
method: 'GET',
|
||||||
|
timeout: TIMEOUT,
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'SuperClaude-Updater'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const req = https.request(options, (res) => {
|
||||||
|
let data = '';
|
||||||
|
|
||||||
|
res.on('data', (chunk) => {
|
||||||
|
data += chunk;
|
||||||
|
});
|
||||||
|
|
||||||
|
res.on('end', () => {
|
||||||
|
try {
|
||||||
|
const packageData = JSON.parse(data);
|
||||||
|
resolve(packageData.version);
|
||||||
|
} catch {
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on('error', () => resolve(null));
|
||||||
|
req.on('timeout', () => {
|
||||||
|
req.destroy();
|
||||||
|
resolve(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
req.setTimeout(TIMEOUT);
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare version strings
|
||||||
|
*/
|
||||||
|
function isNewerVersion(current, latest) {
|
||||||
|
if (!current || !latest) return false;
|
||||||
|
|
||||||
|
const currentParts = current.split('.').map(Number);
|
||||||
|
const latestParts = latest.split('.').map(Number);
|
||||||
|
|
||||||
|
for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
|
||||||
|
const currentPart = currentParts[i] || 0;
|
||||||
|
const latestPart = latestParts[i] || 0;
|
||||||
|
|
||||||
|
if (latestPart > currentPart) return true;
|
||||||
|
if (latestPart < currentPart) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect if npm or yarn is being used globally
|
||||||
|
*/
|
||||||
|
function detectPackageManager() {
|
||||||
|
// Check if installed globally with npm
|
||||||
|
const npmResult = spawnSync('npm', ['list', '-g', PACKAGE_NAME], {
|
||||||
|
encoding: 'utf8',
|
||||||
|
shell: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (npmResult.status === 0 && npmResult.stdout.includes(PACKAGE_NAME)) {
|
||||||
|
return 'npm';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if installed globally with yarn
|
||||||
|
const yarnResult = spawnSync('yarn', ['global', 'list'], {
|
||||||
|
encoding: 'utf8',
|
||||||
|
shell: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (yarnResult.status === 0 && yarnResult.stdout.includes(PACKAGE_NAME)) {
|
||||||
|
return 'yarn';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'npm'; // Default to npm
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the appropriate update command
|
||||||
|
*/
|
||||||
|
function getUpdateCommand() {
|
||||||
|
const pm = detectPackageManager();
|
||||||
|
|
||||||
|
if (pm === 'yarn') {
|
||||||
|
return `yarn global upgrade ${PACKAGE_NAME}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `npm update -g ${PACKAGE_NAME}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show update banner
|
||||||
|
*/
|
||||||
|
function showUpdateBanner(currentVersion, latestVersion, autoUpdate = false) {
|
||||||
|
const updateCmd = getUpdateCommand();
|
||||||
|
|
||||||
|
console.log('\n\x1b[36m╔════════════════════════════════════════════════╗\x1b[0m');
|
||||||
|
console.log(`\x1b[36m║\x1b[33m 🚀 Update Available: ${currentVersion} → ${latestVersion} \x1b[36m║\x1b[0m`);
|
||||||
|
console.log(`\x1b[36m║\x1b[32m Run: ${updateCmd.padEnd(30)} \x1b[36m║\x1b[0m`);
|
||||||
|
console.log('\x1b[36m╚════════════════════════════════════════════════╝\x1b[0m\n');
|
||||||
|
|
||||||
|
return autoUpdate || process.env.SUPERCLAUDE_AUTO_UPDATE === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the update
|
||||||
|
*/
|
||||||
|
function performUpdate() {
|
||||||
|
const updateCmd = getUpdateCommand();
|
||||||
|
console.log('\x1b[36m🔄 Updating SuperClaude...\x1b[0m');
|
||||||
|
|
||||||
|
const cmdParts = updateCmd.split(' ');
|
||||||
|
const result = spawnSync(cmdParts[0], cmdParts.slice(1), {
|
||||||
|
stdio: 'inherit',
|
||||||
|
shell: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.status === 0) {
|
||||||
|
console.log('\x1b[32m✅ Update completed successfully!\x1b[0m');
|
||||||
|
console.log('\x1b[33mPlease restart SuperClaude to use the new version.\x1b[0m');
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
console.log('\x1b[33m⚠️ Update failed. Please run manually:\x1b[0m');
|
||||||
|
console.log(` ${updateCmd}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main function to check and notify for updates
|
||||||
|
*/
|
||||||
|
async function checkAndNotify(options = {}) {
|
||||||
|
const { force = false, autoUpdate = false, silent = false } = options;
|
||||||
|
|
||||||
|
// Check environment variables
|
||||||
|
if (process.env.SUPERCLAUDE_NO_UPDATE_CHECK === 'true') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if enough time has passed
|
||||||
|
if (!shouldCheckUpdate(force)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current version
|
||||||
|
const currentVersion = getCurrentVersion();
|
||||||
|
if (!currentVersion) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get latest version
|
||||||
|
const latestVersion = await getLatestVersion();
|
||||||
|
if (!latestVersion) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save timestamp
|
||||||
|
saveCheckTimestamp();
|
||||||
|
|
||||||
|
// Compare versions
|
||||||
|
if (!isNewerVersion(currentVersion, latestVersion)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show banner unless silent
|
||||||
|
if (!silent) {
|
||||||
|
const shouldUpdate = showUpdateBanner(currentVersion, latestVersion, autoUpdate);
|
||||||
|
|
||||||
|
if (shouldUpdate) {
|
||||||
|
return performUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export functions for use in other modules
|
||||||
|
module.exports = {
|
||||||
|
checkAndNotify,
|
||||||
|
getCurrentVersion,
|
||||||
|
getLatestVersion,
|
||||||
|
isNewerVersion
|
||||||
|
};
|
||||||
|
|
||||||
|
// If run directly, perform check
|
||||||
|
if (require.main === module) {
|
||||||
|
checkAndNotify({
|
||||||
|
force: process.argv.includes('--force'),
|
||||||
|
autoUpdate: process.argv.includes('--auto-update')
|
||||||
|
});
|
||||||
|
}
|
||||||
22
bin/cli.js
22
bin/cli.js
@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
const { spawnSync } = require("child_process");
|
const { spawnSync } = require("child_process");
|
||||||
const { detectPython, detectPip } = require("./checkEnv");
|
const { detectPython, detectPip } = require("./checkEnv");
|
||||||
|
const { checkAndNotify } = require("./checkUpdate");
|
||||||
|
|
||||||
let pythonCmd = detectPython();
|
let pythonCmd = detectPython();
|
||||||
if (!pythonCmd) {
|
if (!pythonCmd) {
|
||||||
@ -10,12 +11,33 @@ if (!pythonCmd) {
|
|||||||
|
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
// Parse command line arguments for update control
|
||||||
|
const noUpdateCheck = args.includes('--no-update-check');
|
||||||
|
const autoUpdate = args.includes('--auto-update');
|
||||||
|
const isQuiet = args.includes('--quiet') || args.includes('-q');
|
||||||
|
|
||||||
// Special case: update command
|
// Special case: update command
|
||||||
if (args[0] === "update") {
|
if (args[0] === "update") {
|
||||||
require("./update");
|
require("./update");
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for updates unless disabled
|
||||||
|
if (!noUpdateCheck && !isQuiet) {
|
||||||
|
// Run update check asynchronously to avoid blocking
|
||||||
|
checkAndNotify({
|
||||||
|
autoUpdate: autoUpdate,
|
||||||
|
silent: false
|
||||||
|
}).then(updated => {
|
||||||
|
if (updated) {
|
||||||
|
console.log("\n🔄 SuperClaude was updated. Please restart to use the new version.");
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
// Silently ignore update check errors
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Forward everything to Python SuperClaude
|
// Forward everything to Python SuperClaude
|
||||||
const result = spawnSync(pythonCmd, ["-m", "SuperClaude", ...args], { stdio: "inherit", shell: true });
|
const result = spawnSync(pythonCmd, ["-m", "SuperClaude", ...args], { stdio: "inherit", shell: true });
|
||||||
process.exit(result.status);
|
process.exit(result.status);
|
||||||
|
|||||||
310
setup/utils/updater.py
Normal file
310
setup/utils/updater.py
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
"""
|
||||||
|
Auto-update checker for SuperClaude Framework
|
||||||
|
Checks PyPI for newer versions and offers automatic updates
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional, Tuple
|
||||||
|
from packaging import version
|
||||||
|
import urllib.request
|
||||||
|
import urllib.error
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
from .ui import display_info, display_warning, display_success, Colors
|
||||||
|
from .logger import get_logger
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateChecker:
|
||||||
|
"""Handles automatic update checking for SuperClaude"""
|
||||||
|
|
||||||
|
PYPI_URL = "https://pypi.org/pypi/SuperClaude/json"
|
||||||
|
CACHE_FILE = Path.home() / ".claude" / ".update_check"
|
||||||
|
CHECK_INTERVAL = 86400 # 24 hours in seconds
|
||||||
|
TIMEOUT = 2 # seconds
|
||||||
|
|
||||||
|
def __init__(self, current_version: str):
|
||||||
|
"""
|
||||||
|
Initialize update checker
|
||||||
|
|
||||||
|
Args:
|
||||||
|
current_version: Current installed version
|
||||||
|
"""
|
||||||
|
self.current_version = current_version
|
||||||
|
self.logger = get_logger()
|
||||||
|
|
||||||
|
def should_check_update(self, force: bool = False) -> bool:
|
||||||
|
"""
|
||||||
|
Determine if we should check for updates based on last check time
|
||||||
|
|
||||||
|
Args:
|
||||||
|
force: Force check regardless of last check time
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if update check should be performed
|
||||||
|
"""
|
||||||
|
if force:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if not self.CACHE_FILE.exists():
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(self.CACHE_FILE, 'r') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
last_check = data.get('last_check', 0)
|
||||||
|
|
||||||
|
# Check if 24 hours have passed
|
||||||
|
if time.time() - last_check > self.CHECK_INTERVAL:
|
||||||
|
return True
|
||||||
|
|
||||||
|
except (json.JSONDecodeError, KeyError):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def save_check_timestamp(self):
|
||||||
|
"""Save the current timestamp as last check time"""
|
||||||
|
self.CACHE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
if self.CACHE_FILE.exists():
|
||||||
|
try:
|
||||||
|
with open(self.CACHE_FILE, 'r') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
data['last_check'] = time.time()
|
||||||
|
|
||||||
|
with open(self.CACHE_FILE, 'w') as f:
|
||||||
|
json.dump(data, f)
|
||||||
|
|
||||||
|
def get_latest_version(self) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
Query PyPI for the latest version of SuperClaude
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Latest version string or None if check fails
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Create request with timeout
|
||||||
|
req = urllib.request.Request(
|
||||||
|
self.PYPI_URL,
|
||||||
|
headers={'User-Agent': 'SuperClaude-Updater'}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set timeout for the request
|
||||||
|
with urllib.request.urlopen(req, timeout=self.TIMEOUT) as response:
|
||||||
|
data = json.loads(response.read().decode())
|
||||||
|
latest = data.get('info', {}).get('version')
|
||||||
|
|
||||||
|
if self.logger:
|
||||||
|
self.logger.debug(f"Latest PyPI version: {latest}")
|
||||||
|
|
||||||
|
return latest
|
||||||
|
|
||||||
|
except (urllib.error.URLError, urllib.error.HTTPError, json.JSONDecodeError) as e:
|
||||||
|
if self.logger:
|
||||||
|
self.logger.debug(f"Failed to check PyPI: {e}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
if self.logger:
|
||||||
|
self.logger.debug(f"Unexpected error checking updates: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def compare_versions(self, latest: str) -> bool:
|
||||||
|
"""
|
||||||
|
Compare current version with latest version
|
||||||
|
|
||||||
|
Args:
|
||||||
|
latest: Latest version string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if update is available
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return version.parse(latest) > version.parse(self.current_version)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def detect_installation_method(self) -> str:
|
||||||
|
"""
|
||||||
|
Detect how SuperClaude was installed (pip, pipx, etc.)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Installation method string
|
||||||
|
"""
|
||||||
|
# Check pipx first
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
['pipx', 'list'],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=2
|
||||||
|
)
|
||||||
|
if 'SuperClaude' in result.stdout or 'superclaude' in result.stdout:
|
||||||
|
return 'pipx'
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Check if pip installation exists
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
[sys.executable, '-m', 'pip', 'show', 'SuperClaude'],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=2
|
||||||
|
)
|
||||||
|
if result.returncode == 0:
|
||||||
|
# Check if it's a user installation
|
||||||
|
if '--user' in result.stdout or Path.home() in Path(result.stdout):
|
||||||
|
return 'pip-user'
|
||||||
|
return 'pip'
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return 'unknown'
|
||||||
|
|
||||||
|
def get_update_command(self) -> str:
|
||||||
|
"""
|
||||||
|
Get the appropriate update command based on installation method
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Update command string
|
||||||
|
"""
|
||||||
|
method = self.detect_installation_method()
|
||||||
|
|
||||||
|
commands = {
|
||||||
|
'pipx': 'pipx upgrade SuperClaude',
|
||||||
|
'pip-user': 'pip install --upgrade --user SuperClaude',
|
||||||
|
'pip': 'pip install --upgrade SuperClaude',
|
||||||
|
'unknown': 'pip install --upgrade SuperClaude'
|
||||||
|
}
|
||||||
|
|
||||||
|
return commands.get(method, commands['unknown'])
|
||||||
|
|
||||||
|
def show_update_banner(self, latest: str, auto_update: bool = False) -> bool:
|
||||||
|
"""
|
||||||
|
Display update available banner
|
||||||
|
|
||||||
|
Args:
|
||||||
|
latest: Latest version available
|
||||||
|
auto_update: Whether to auto-update without prompting
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if user wants to update
|
||||||
|
"""
|
||||||
|
update_cmd = self.get_update_command()
|
||||||
|
|
||||||
|
# Display banner
|
||||||
|
print(f"\n{Colors.CYAN}╔════════════════════════════════════════════════╗{Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}║{Colors.YELLOW} 🚀 Update Available: {self.current_version} → {latest} {Colors.CYAN}║{Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}║{Colors.GREEN} Run: {update_cmd:<30} {Colors.CYAN}║{Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}╚════════════════════════════════════════════════╝{Colors.RESET}\n")
|
||||||
|
|
||||||
|
if auto_update:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Check if running in non-interactive mode
|
||||||
|
if not sys.stdin.isatty():
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Prompt user
|
||||||
|
try:
|
||||||
|
response = input(f"{Colors.YELLOW}Would you like to update now? (y/N): {Colors.RESET}").strip().lower()
|
||||||
|
return response in ['y', 'yes']
|
||||||
|
except (EOFError, KeyboardInterrupt):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def perform_update(self) -> bool:
|
||||||
|
"""
|
||||||
|
Execute the update command
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if update succeeded
|
||||||
|
"""
|
||||||
|
update_cmd = self.get_update_command()
|
||||||
|
|
||||||
|
print(f"{Colors.CYAN}🔄 Updating SuperClaude...{Colors.RESET}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
update_cmd.split(),
|
||||||
|
capture_output=False,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
display_success("Update completed successfully!")
|
||||||
|
print(f"{Colors.YELLOW}Please restart SuperClaude to use the new version.{Colors.RESET}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
display_warning("Update failed. Please run manually:")
|
||||||
|
print(f" {update_cmd}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
display_warning(f"Could not auto-update: {e}")
|
||||||
|
print(f"Please run manually: {update_cmd}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def check_and_notify(self, force: bool = False, auto_update: bool = False) -> bool:
|
||||||
|
"""
|
||||||
|
Main method to check for updates and notify user
|
||||||
|
|
||||||
|
Args:
|
||||||
|
force: Force check regardless of last check time
|
||||||
|
auto_update: Automatically update if available
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if update was performed
|
||||||
|
"""
|
||||||
|
# Check if we should skip based on environment variable
|
||||||
|
if os.getenv('SUPERCLAUDE_NO_UPDATE_CHECK', '').lower() in ['true', '1', 'yes']:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check if auto-update is enabled via environment
|
||||||
|
if os.getenv('SUPERCLAUDE_AUTO_UPDATE', '').lower() in ['true', '1', 'yes']:
|
||||||
|
auto_update = True
|
||||||
|
|
||||||
|
# Check if enough time has passed
|
||||||
|
if not self.should_check_update(force):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Get latest version
|
||||||
|
latest = self.get_latest_version()
|
||||||
|
if not latest:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Save timestamp
|
||||||
|
self.save_check_timestamp()
|
||||||
|
|
||||||
|
# Compare versions
|
||||||
|
if not self.compare_versions(latest):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Show banner and potentially update
|
||||||
|
if self.show_update_banner(latest, auto_update):
|
||||||
|
return self.perform_update()
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def check_for_updates(current_version: str = "4.0.6", **kwargs) -> bool:
|
||||||
|
"""
|
||||||
|
Convenience function to check for updates
|
||||||
|
|
||||||
|
Args:
|
||||||
|
current_version: Current installed version
|
||||||
|
**kwargs: Additional arguments passed to check_and_notify
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if update was performed
|
||||||
|
"""
|
||||||
|
checker = UpdateChecker(current_version)
|
||||||
|
return checker.check_and_notify(**kwargs)
|
||||||
Loading…
x
Reference in New Issue
Block a user