Add uninstall option for vt command in settings (#407)

This commit is contained in:
Peter Steinberger 2025-07-18 08:32:29 +02:00 committed by GitHub
parent af569666b7
commit f9869293a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 194 additions and 19 deletions

View file

@ -40,37 +40,67 @@ struct CLIInstallationSection: View {
HStack {
ProgressView()
.scaleEffect(0.7)
Text("Installing...")
Text(cliInstaller.isUninstalling ? "Uninstalling..." : "Installing...")
.font(.caption)
}
} else {
if cliInstaller.isInstalled {
// Updated status
if cliInstaller.isOutdated {
Button("Update 'vt' Command") {
Task {
await cliInstaller.install()
HStack(spacing: 8) {
Button("Update 'vt' Command") {
Task {
await cliInstaller.install()
}
}
}
.buttonStyle(.bordered)
.disabled(cliInstaller.isInstalling)
} else {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
Text("VT installed")
.foregroundColor(.secondary)
// Show reinstall button in debug mode
if debugMode {
.buttonStyle(.bordered)
.disabled(cliInstaller.isInstalling)
Button(action: {
cliInstaller.installCLITool()
Task {
await cliInstaller.uninstall()
}
}, label: {
Image(systemName: "arrow.clockwise.circle")
Image(systemName: "trash")
.font(.system(size: 14))
})
.buttonStyle(.plain)
.foregroundColor(.accentColor)
.help("Reinstall CLI tool")
.foregroundColor(.red)
.disabled(cliInstaller.isInstalling)
.help("Uninstall CLI tool")
}
} else {
HStack(spacing: 8) {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
Text("VT installed")
.foregroundColor(.secondary)
// Show reinstall button in debug mode
if debugMode {
Button(action: {
cliInstaller.installCLITool()
}, label: {
Image(systemName: "arrow.clockwise.circle")
.font(.system(size: 14))
})
.buttonStyle(.plain)
.foregroundColor(.accentColor)
.help("Reinstall CLI tool")
}
Button(action: {
Task {
await cliInstaller.uninstall()
}
}, label: {
Image(systemName: "trash")
.font(.system(size: 14))
})
.buttonStyle(.plain)
.foregroundColor(.red)
.disabled(cliInstaller.isInstalling)
.help("Uninstall CLI tool")
}
}
} else {

View file

@ -38,6 +38,7 @@ final class CLIInstaller {
var isInstalling = false
var lastError: String?
var isOutdated = false
var isUninstalling = false
// MARK: - Initialization
@ -142,6 +143,43 @@ final class CLIInstaller {
performInstallation()
}
/// Uninstalls the vt CLI tool (async version)
func uninstall() async {
await MainActor.run {
uninstallCLITool()
}
}
/// Uninstalls the vt CLI tool from /usr/local/bin and /opt/homebrew/bin
func uninstallCLITool() {
logger.info("CLIInstaller: Starting CLI tool uninstallation...")
isInstalling = true
isUninstalling = true
lastError = nil
// Show confirmation dialog
let confirmAlert = NSAlert()
confirmAlert.messageText = "Uninstall VT Command Line Tool"
confirmAlert
.informativeText =
"This will remove the 'vt' command from your system. Administrator privileges are required."
confirmAlert.addButton(withTitle: "Uninstall")
confirmAlert.addButton(withTitle: "Cancel")
confirmAlert.alertStyle = .informational
confirmAlert.icon = NSApp.applicationIconImage
let response = confirmAlert.runModal()
if response != .alertFirstButtonReturn {
logger.info("CLIInstaller: User cancelled uninstallation")
isInstalling = false
isUninstalling = false
return
}
// Perform the uninstallation
performUninstallation()
}
// MARK: - Private Implementation
/// Performs the actual installation with sudo privileges
@ -250,6 +288,102 @@ final class CLIInstaller {
}
}
/// Performs the actual uninstallation with sudo privileges
private func performUninstallation() {
logger.info("CLIInstaller: Uninstalling vt script")
// Create the uninstallation script
let script = """
#!/bin/bash
set -e
# Remove vt script from /usr/local/bin
if [ -L "/usr/local/bin/vt" ] || [ -f "/usr/local/bin/vt" ]; then
rm -f "/usr/local/bin/vt"
echo "Removed vt from /usr/local/bin"
fi
# Remove vt script from /opt/homebrew/bin (Apple Silicon Homebrew path)
if [ -L "/opt/homebrew/bin/vt" ] || [ -f "/opt/homebrew/bin/vt" ]; then
rm -f "/opt/homebrew/bin/vt"
echo "Removed vt from /opt/homebrew/bin"
fi
# Clean up old vibetunnel binary if it exists
if [ -f "/usr/local/bin/vibetunnel" ]; then
rm -f "/usr/local/bin/vibetunnel"
echo "Removed old vibetunnel binary"
fi
"""
// Write the script to a temporary file
let tempDir = FileManager.default.temporaryDirectory
let scriptURL = tempDir.appendingPathComponent("uninstall_vt_cli.sh")
do {
try script.write(to: scriptURL, atomically: true, encoding: .utf8)
// Make the script executable
let attributes: [FileAttributeKey: Any] = [.posixPermissions: 0o755]
try FileManager.default.setAttributes(attributes, ofItemAtPath: scriptURL.path)
logger.info("CLIInstaller: Created uninstallation script at \(scriptURL.path)")
// Execute with osascript to get sudo dialog
let appleScript = """
do shell script "bash '\(scriptURL.path)'" with administrator privileges
"""
let task = Process()
task.launchPath = "/usr/bin/osascript"
task.arguments = ["-e", appleScript]
let pipe = Pipe()
let errorPipe = Pipe()
task.standardOutput = pipe
task.standardError = errorPipe
try task.run()
task.waitUntilExit()
// Clean up the temporary script
try? FileManager.default.removeItem(at: scriptURL)
if task.terminationStatus == 0 {
logger.info("CLIInstaller: Uninstallation completed successfully")
isInstalled = false
isInstalling = false
isUninstalling = false
showUninstallSuccess()
// Refresh installation status
checkInstallationStatus()
} else {
let errorString: String
do {
if let errorData = try errorPipe.fileHandleForReading.readToEnd() {
errorString = String(data: errorData, encoding: .utf8) ?? "Unknown error"
} else {
errorString = "Unknown error"
}
} catch {
logger.debug("Could not read error output: \(error.localizedDescription)")
errorString = "Unknown error (could not read stderr)"
}
logger.error("CLIInstaller: Uninstallation failed with status \(task.terminationStatus): \(errorString)")
lastError = "Uninstallation failed: \(errorString)"
isInstalling = false
isUninstalling = false
showError("Uninstallation failed: \(errorString)")
}
} catch {
logger.error("CLIInstaller: Uninstallation failed with error: \(error)")
lastError = "Uninstallation failed: \(error.localizedDescription)"
isInstalling = false
isUninstalling = false
showError("Uninstallation failed: \(error.localizedDescription)")
}
}
/// Shows success message after installation
private func showSuccess() {
let alert = NSAlert()
@ -263,6 +397,17 @@ final class CLIInstaller {
alert.runModal()
}
/// Shows success message after uninstallation
private func showUninstallSuccess() {
let alert = NSAlert()
alert.messageText = "CLI Tools Uninstalled Successfully"
alert.informativeText = "The 'vt' command has been removed from your system."
alert.addButton(withTitle: "OK")
alert.alertStyle = .informational
alert.icon = NSApp.applicationIconImage
alert.runModal()
}
/// Shows error message for installation failures
private func showError(_ message: String) {
let alert = NSAlert()