mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
Implement hash-based vt script version detection
- Add SHA256 hash comparison for vt script updates - Check vt script version on app startup - Show welcome dialog when vt script is outdated - Add update prompts in both Welcome and Settings views - Ensures users always have the latest vt features (like 'vt title') Fixes #245
This commit is contained in:
parent
d9a30e299d
commit
55a852881c
5 changed files with 154 additions and 25 deletions
|
|
@ -41,22 +41,37 @@ struct AdvancedSettingsView: View {
|
||||||
// Actual content
|
// Actual content
|
||||||
if cliInstaller.isInstalled {
|
if cliInstaller.isInstalled {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
Image(systemName: "checkmark.circle.fill")
|
if cliInstaller.isOutdated {
|
||||||
.foregroundColor(.green)
|
Image(systemName: "exclamationmark.triangle.fill")
|
||||||
Text("VT installed")
|
.foregroundColor(.orange)
|
||||||
.foregroundColor(.secondary)
|
Text("VT update available")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
|
||||||
// Show reinstall button in debug mode
|
Button("Update") {
|
||||||
if debugMode {
|
Task {
|
||||||
Button(action: {
|
await cliInstaller.install()
|
||||||
cliInstaller.installCLITool()
|
}
|
||||||
}, label: {
|
}
|
||||||
Image(systemName: "arrow.clockwise.circle")
|
.buttonStyle(.bordered)
|
||||||
.font(.system(size: 14))
|
.disabled(cliInstaller.isInstalling)
|
||||||
})
|
} else {
|
||||||
.buttonStyle(.plain)
|
Image(systemName: "checkmark.circle.fill")
|
||||||
.foregroundColor(.accentColor)
|
.foregroundColor(.green)
|
||||||
.help("Reinstall CLI tool")
|
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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -204,6 +204,14 @@ private struct PermissionsSection: View {
|
||||||
.padding(.horizontal, 10)
|
.padding(.horizontal, 10)
|
||||||
.padding(.vertical, 2)
|
.padding(.vertical, 2)
|
||||||
.frame(height: 22) // Match small button height
|
.frame(height: 22) // Match small button height
|
||||||
|
.contextMenu {
|
||||||
|
Button("Refresh Status") {
|
||||||
|
permissionManager.forcePermissionRecheck()
|
||||||
|
}
|
||||||
|
Button("Open System Settings...") {
|
||||||
|
permissionManager.requestPermission(.appleScript)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Button("Grant Permission") {
|
Button("Grant Permission") {
|
||||||
permissionManager.requestPermission(.appleScript)
|
permissionManager.requestPermission(.appleScript)
|
||||||
|
|
@ -236,6 +244,14 @@ private struct PermissionsSection: View {
|
||||||
.padding(.horizontal, 10)
|
.padding(.horizontal, 10)
|
||||||
.padding(.vertical, 2)
|
.padding(.vertical, 2)
|
||||||
.frame(height: 22) // Match small button height
|
.frame(height: 22) // Match small button height
|
||||||
|
.contextMenu {
|
||||||
|
Button("Refresh Status") {
|
||||||
|
permissionManager.forcePermissionRecheck()
|
||||||
|
}
|
||||||
|
Button("Open System Settings...") {
|
||||||
|
permissionManager.requestPermission(.accessibility)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Button("Grant Permission") {
|
Button("Grant Permission") {
|
||||||
permissionManager.requestPermission(.accessibility)
|
permissionManager.requestPermission(.accessibility)
|
||||||
|
|
@ -268,6 +284,14 @@ private struct PermissionsSection: View {
|
||||||
.padding(.horizontal, 10)
|
.padding(.horizontal, 10)
|
||||||
.padding(.vertical, 2)
|
.padding(.vertical, 2)
|
||||||
.frame(height: 22) // Match small button height
|
.frame(height: 22) // Match small button height
|
||||||
|
.contextMenu {
|
||||||
|
Button("Refresh Status") {
|
||||||
|
permissionManager.forcePermissionRecheck()
|
||||||
|
}
|
||||||
|
Button("Open System Settings...") {
|
||||||
|
permissionManager.requestPermission(.screenRecording)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Button("Grant Permission") {
|
Button("Grant Permission") {
|
||||||
permissionManager.requestPermission(.screenRecording)
|
permissionManager.requestPermission(.screenRecording)
|
||||||
|
|
|
||||||
|
|
@ -53,11 +53,28 @@ struct VTCommandPageView: View {
|
||||||
// Install VT Binary button
|
// Install VT Binary button
|
||||||
VStack(spacing: 12) {
|
VStack(spacing: 12) {
|
||||||
if cliInstaller.isInstalled {
|
if cliInstaller.isInstalled {
|
||||||
HStack {
|
if cliInstaller.isOutdated {
|
||||||
Image(systemName: "checkmark.circle.fill")
|
HStack {
|
||||||
.foregroundColor(.green)
|
Image(systemName: "exclamationmark.triangle.fill")
|
||||||
Text("CLI tool is installed")
|
.foregroundColor(.orange)
|
||||||
.foregroundColor(.secondary)
|
Text("CLI tool is outdated")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button("Update VT Command Line Tool") {
|
||||||
|
Task {
|
||||||
|
await cliInstaller.install()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.buttonStyle(.borderedProminent)
|
||||||
|
.disabled(cliInstaller.isInstalling)
|
||||||
|
} else {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "checkmark.circle.fill")
|
||||||
|
.foregroundColor(.green)
|
||||||
|
Text("CLI tool is installed")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Button("Install VT Command Line Tool") {
|
Button("Install VT Command Line Tool") {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import AppKit
|
import AppKit
|
||||||
|
import CryptoKit
|
||||||
import Foundation
|
import Foundation
|
||||||
import Observation
|
import Observation
|
||||||
import os.log
|
import os.log
|
||||||
|
|
@ -36,6 +37,7 @@ final class CLIInstaller {
|
||||||
var isInstalled = false
|
var isInstalled = false
|
||||||
var isInstalling = false
|
var isInstalling = false
|
||||||
var lastError: String?
|
var lastError: String?
|
||||||
|
var isOutdated = false
|
||||||
|
|
||||||
// MARK: - Initialization
|
// MARK: - Initialization
|
||||||
|
|
||||||
|
|
@ -79,6 +81,13 @@ final class CLIInstaller {
|
||||||
isInstalled = isCorrectlyInstalled
|
isInstalled = isCorrectlyInstalled
|
||||||
|
|
||||||
logger.info("CLIInstaller: vt script installed: \(self.isInstalled)")
|
logger.info("CLIInstaller: vt script installed: \(self.isInstalled)")
|
||||||
|
|
||||||
|
// If installed, check if it's outdated
|
||||||
|
if isInstalled {
|
||||||
|
checkScriptVersion()
|
||||||
|
} else {
|
||||||
|
isOutdated = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,4 +265,60 @@ final class CLIInstaller {
|
||||||
alert.alertStyle = .critical
|
alert.alertStyle = .critical
|
||||||
alert.runModal()
|
alert.runModal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Script Version Detection
|
||||||
|
|
||||||
|
/// Calculates SHA256 hash of a file
|
||||||
|
private func calculateHash(for filePath: String) -> String? {
|
||||||
|
guard let data = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let hash = SHA256.hash(data: data)
|
||||||
|
return hash.compactMap { String(format: "%02x", $0) }.joined()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the hash of the bundled vt script
|
||||||
|
private func getBundledScriptHash() -> String? {
|
||||||
|
guard let scriptPath = Bundle.main.path(forResource: "vt", ofType: nil) else {
|
||||||
|
logger.error("CLIInstaller: Bundled vt script not found")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return calculateHash(for: scriptPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the installed script is outdated compared to bundled version
|
||||||
|
func checkScriptVersion() {
|
||||||
|
Task { @MainActor in
|
||||||
|
guard let bundledHash = getBundledScriptHash() else {
|
||||||
|
logger.error("CLIInstaller: Failed to get bundled script hash")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check both possible installation paths
|
||||||
|
let pathsToCheck = [
|
||||||
|
vtTargetPath,
|
||||||
|
"/opt/homebrew/bin/vt"
|
||||||
|
]
|
||||||
|
|
||||||
|
var installedHash: String?
|
||||||
|
for path in pathsToCheck where FileManager.default.fileExists(atPath: path) {
|
||||||
|
if let hash = calculateHash(for: path) {
|
||||||
|
installedHash = hash
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update outdated status
|
||||||
|
if let installedHash = installedHash {
|
||||||
|
self.isOutdated = (installedHash != bundledHash)
|
||||||
|
logger.info("CLIInstaller: Script version check - outdated: \(self.isOutdated)")
|
||||||
|
logger.debug("CLIInstaller: Bundled hash: \(bundledHash), Installed hash: \(installedHash)")
|
||||||
|
} else {
|
||||||
|
// Script not installed
|
||||||
|
self.isOutdated = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -194,12 +194,20 @@ final class AppDelegate: NSObject, NSApplicationDelegate, @preconcurrency UNUser
|
||||||
// Initialize dock icon visibility through DockIconManager
|
// Initialize dock icon visibility through DockIconManager
|
||||||
DockIconManager.shared.updateDockVisibility()
|
DockIconManager.shared.updateDockVisibility()
|
||||||
|
|
||||||
// Show welcome screen when version changes
|
// Check CLI installation status
|
||||||
|
let cliInstaller = CLIInstaller()
|
||||||
|
cliInstaller.checkInstallationStatus()
|
||||||
|
|
||||||
|
// Show welcome screen when version changes OR when vt script is outdated
|
||||||
let storedWelcomeVersion = UserDefaults.standard.integer(forKey: AppConstants.UserDefaultsKeys.welcomeVersion)
|
let storedWelcomeVersion = UserDefaults.standard.integer(forKey: AppConstants.UserDefaultsKeys.welcomeVersion)
|
||||||
|
|
||||||
// Show welcome if version is different from current
|
// Small delay to allow CLI check to complete
|
||||||
if storedWelcomeVersion < AppConstants.currentWelcomeVersion && !isRunningInTests && !isRunningInPreview {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
|
||||||
showWelcomeScreen()
|
// Show welcome if version is different from current OR if vt script is outdated
|
||||||
|
if (storedWelcomeVersion < AppConstants.currentWelcomeVersion || cliInstaller.isOutdated)
|
||||||
|
&& !isRunningInTests && !isRunningInPreview {
|
||||||
|
self?.showWelcomeScreen()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip all service initialization during tests
|
// Skip all service initialization during tests
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue