diff --git a/VibeTunnel.xcodeproj/project.xcworkspace/xcuserdata/steipete.xcuserdatad/UserInterfaceState.xcuserstate b/VibeTunnel.xcodeproj/project.xcworkspace/xcuserdata/steipete.xcuserdatad/UserInterfaceState.xcuserstate
index e9d4f057..dce13329 100644
Binary files a/VibeTunnel.xcodeproj/project.xcworkspace/xcuserdata/steipete.xcuserdatad/UserInterfaceState.xcuserstate and b/VibeTunnel.xcodeproj/project.xcworkspace/xcuserdata/steipete.xcuserdatad/UserInterfaceState.xcuserstate differ
diff --git a/VibeTunnel/Core/Services/TTYForwardManager.swift b/VibeTunnel/Core/Services/TTYForwardManager.swift
index 19db96d0..f7642d39 100644
--- a/VibeTunnel/Core/Services/TTYForwardManager.swift
+++ b/VibeTunnel/Core/Services/TTYForwardManager.swift
@@ -10,7 +10,7 @@ final class TTYForwardManager {
/// Returns the URL to the bundled tty-fwd executable
var ttyForwardExecutableURL: URL? {
- return Bundle.main.url(forResource: "tty-fwd", withExtension: nil, subdirectory: "Resources")
+ return Bundle.main.url(forResource: "tty-fwd", withExtension: nil)
}
/// Executes the tty-fwd binary with the specified arguments
diff --git a/VibeTunnel/Core/Services/TunnelServer.swift b/VibeTunnel/Core/Services/TunnelServer.swift
index a93ccf3e..5bcb7a51 100644
--- a/VibeTunnel/Core/Services/TunnelServer.swift
+++ b/VibeTunnel/Core/Services/TunnelServer.swift
@@ -89,7 +89,7 @@ public final class TunnelServer {
VibeTunnel Server
- ✓ Server is running on port \(portNumber)
+ Server is running on port \(portNumber)
Available endpoints:
- /health - Health check
diff --git a/VibeTunnel/Presentation/Views/AboutView.swift b/VibeTunnel/Presentation/Views/AboutView.swift
index ede872f9..f2556260 100644
--- a/VibeTunnel/Presentation/Views/AboutView.swift
+++ b/VibeTunnel/Presentation/Views/AboutView.swift
@@ -64,12 +64,12 @@ struct AboutView: View {
title: "Report an Issue",
icon: "exclamationmark.bubble"
)
- HoverableLink(url: "https://x.com/steipete", title: "Follow @steipete on Twitter", icon: "bird")
+ HoverableLink(url: "https://x.com/VibeTunnel", title: "Follow @VibeTunnel", icon: "bird")
}
}
private var copyrightSection: some View {
- Text("© 2025 Amantus AI • MIT Licensed")
+ Text("© 2025 VibeTunnel Team • MIT Licensed")
.font(.footnote)
.foregroundStyle(.secondary)
.padding(.bottom, 32)
diff --git a/VibeTunnel/SettingsView.swift b/VibeTunnel/SettingsView.swift
index 6e1abf9a..72122318 100644
--- a/VibeTunnel/SettingsView.swift
+++ b/VibeTunnel/SettingsView.swift
@@ -32,6 +32,7 @@ extension Notification.Name {
struct SettingsView: View {
@State private var selectedTab: SettingsTab = .general
@State private var contentSize: CGSize = .zero
+ @AppStorage("debugMode") private var debugMode = false
// Define ideal sizes for each tab
private let tabSizes: [SettingsTab: CGSize] = [
@@ -55,11 +56,13 @@ struct SettingsView: View {
}
.tag(SettingsTab.advanced)
- DebugSettingsView()
- .tabItem {
- Label(SettingsTab.debug.displayName, systemImage: SettingsTab.debug.icon)
- }
- .tag(SettingsTab.debug)
+ if debugMode {
+ DebugSettingsView()
+ .tabItem {
+ Label(SettingsTab.debug.displayName, systemImage: SettingsTab.debug.icon)
+ }
+ .tag(SettingsTab.debug)
+ }
AboutView()
.tabItem {
@@ -68,7 +71,7 @@ struct SettingsView: View {
.tag(SettingsTab.about)
}
.frame(width: contentSize.width, height: contentSize.height)
- .animatedWindowResizing(size: contentSize)
+ .animatedWindowContainer(size: contentSize)
.onReceive(NotificationCenter.default.publisher(for: .openSettingsTab)) { notification in
if let tab = notification.object as? SettingsTab {
selectedTab = tab
@@ -80,6 +83,12 @@ struct SettingsView: View {
.onAppear {
contentSize = tabSizes[selectedTab] ?? CGSize(width: 500, height: 400)
}
+ .onChange(of: debugMode) { _, _ in
+ // If debug mode is disabled and we're on the debug tab, switch to general
+ if !debugMode && selectedTab == .debug {
+ selectedTab = .general
+ }
+ }
}
}
@@ -214,7 +223,6 @@ struct AdvancedSettingsView: View {
.buttonStyle(.bordered)
.disabled(isCheckingForUpdates)
}
- .padding(.top, 8)
} header: {
Text("Updates")
.font(.headline)
@@ -421,7 +429,7 @@ struct DebugSettingsView: View {
Section {
// API Endpoints with test functionality
VStack(alignment: .leading, spacing: 12) {
- ForEach(apiEndpoints, id: \.path) { endpoint in
+ ForEach(apiEndpoints) { endpoint in
VStack(alignment: .leading, spacing: 4) {
HStack {
Text(endpoint.method)
@@ -614,11 +622,20 @@ struct DebugSettingsView: View {
}
// API Endpoint data
-struct APIEndpoint {
+struct APIEndpoint: Identifiable {
+ let id: String
let method: String
let path: String
let description: String
let isTestable: Bool
+
+ init(method: String, path: String, description: String, isTestable: Bool) {
+ self.id = "\(method)_\(path)"
+ self.method = method
+ self.path = path
+ self.description = description
+ self.isTestable = isTestable
+ }
}
let apiEndpoints = [
diff --git a/VibeTunnel/Utilities/AnimatedSettingsWindow.swift b/VibeTunnel/Utilities/AnimatedSettingsWindow.swift
new file mode 100644
index 00000000..1c0cec48
--- /dev/null
+++ b/VibeTunnel/Utilities/AnimatedSettingsWindow.swift
@@ -0,0 +1,86 @@
+import SwiftUI
+import AppKit
+
+/// A view that enables animated window resizing for Settings windows
+struct AnimatedWindowContainer: NSViewRepresentable {
+ let content: Content
+ let targetSize: CGSize
+
+ class Coordinator: NSObject {
+ var lastSize: CGSize = .zero
+ weak var window: NSWindow?
+ var isAnimating = false
+
+ func animateWindowResize(to newSize: CGSize) {
+ guard let window = window,
+ newSize != lastSize,
+ !isAnimating else { return }
+
+ lastSize = newSize
+ isAnimating = true
+
+ // Calculate the new frame maintaining the window's top-left position
+ var newFrame = window.frame
+ let heightDifference = newSize.height - newFrame.height
+ newFrame.size = newSize
+ newFrame.origin.y -= heightDifference // Keep top edge in place
+
+ // Animate the window frame change
+ NSAnimationContext.runAnimationGroup({ context in
+ context.duration = 0.25
+ context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
+ window.animator().setFrame(newFrame, display: true)
+ }, completionHandler: {
+ self.isAnimating = false
+ })
+ }
+ }
+
+ func makeCoordinator() -> Coordinator {
+ Coordinator()
+ }
+
+ func makeNSView(context: Context) -> NSView {
+ let view = NSView()
+ view.wantsLayer = true
+ view.layer?.backgroundColor = NSColor.clear.cgColor
+
+ // Use async to ensure window is available
+ DispatchQueue.main.async {
+ if let window = view.window {
+ context.coordinator.window = window
+ // Disable automatic window resizing
+ window.styleMask.remove(.resizable)
+ // Set initial size
+ context.coordinator.lastSize = window.frame.size
+ }
+ }
+
+ return view
+ }
+
+ func updateNSView(_ nsView: NSView, context: Context) {
+ // Ensure we have a window
+ if context.coordinator.window == nil, let window = nsView.window {
+ context.coordinator.window = window
+ window.styleMask.remove(.resizable)
+ context.coordinator.lastSize = window.frame.size
+ }
+
+ // Animate to the new size
+ context.coordinator.animateWindowResize(to: targetSize)
+ }
+}
+
+/// Extension to make it easy to use with any SwiftUI view
+extension View {
+ func animatedWindowContainer(size: CGSize) -> some View {
+ background(
+ AnimatedWindowContainer(
+ content: Color.clear,
+ targetSize: size
+ )
+ .allowsHitTesting(false)
+ )
+ }
+}
diff --git a/VibeTunnel/Utilities/SettingsWindowDelegate.swift b/VibeTunnel/Utilities/SettingsWindowDelegate.swift
deleted file mode 100644
index 1bca565a..00000000
--- a/VibeTunnel/Utilities/SettingsWindowDelegate.swift
+++ /dev/null
@@ -1,71 +0,0 @@
-import AppKit
-import SwiftUI
-
-/// A window delegate that handles animated resizing of the settings window
-class SettingsWindowDelegate: NSObject, NSWindowDelegate {
- static let shared = SettingsWindowDelegate()
-
- private override init() {
- super.init()
- }
-
- /// Animates the window to a new size
- func animateWindowResize(to newSize: CGSize, duration: TimeInterval = 0.3) {
- guard let window = NSApp.windows.first(where: { $0.title.contains("Settings") || $0.title.contains("Preferences") }) else {
- return
- }
-
- // Calculate the new frame maintaining the window's top-left position
- var newFrame = window.frame
- let heightDifference = newSize.height - newFrame.height
- newFrame.size = newSize
- newFrame.origin.y -= heightDifference // Keep top edge in place
-
- // Animate the frame change
- NSAnimationContext.runAnimationGroup { context in
- context.duration = duration
- context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
- window.animator().setFrame(newFrame, display: true)
- }
- }
-
- func windowWillClose(_ notification: Notification) {
- // Clean up if needed
- }
-}
-
-/// A view modifier that sets up the window delegate for animated resizing
-struct AnimatedWindowResizing: ViewModifier {
- let size: CGSize
-
- func body(content: Content) -> some View {
- content
- .onAppear {
- setupWindowDelegate()
- // Initial resize without animation
- SettingsWindowDelegate.shared.animateWindowResize(to: size, duration: 0)
- }
- .onChange(of: size) { _, newSize in
- SettingsWindowDelegate.shared.animateWindowResize(to: newSize)
- }
- }
-
- private func setupWindowDelegate() {
- Task { @MainActor in
- // Small delay to ensure window is created
- try? await Task.sleep(for: .milliseconds(100))
-
- if let window = NSApp.windows.first(where: { $0.title.contains("Settings") || $0.title.contains("Preferences") }) {
- window.delegate = SettingsWindowDelegate.shared
- // Disable window resizing by user
- window.styleMask.remove(.resizable)
- }
- }
- }
-}
-
-extension View {
- func animatedWindowResizing(size: CGSize) -> some View {
- modifier(AnimatedWindowResizing(size: size))
- }
-}
\ No newline at end of file
diff --git a/VibeTunnel/VibeTunnel.entitlements b/VibeTunnel/VibeTunnel.entitlements
index c1756644..85a5438d 100644
--- a/VibeTunnel/VibeTunnel.entitlements
+++ b/VibeTunnel/VibeTunnel.entitlements
@@ -3,12 +3,6 @@
com.apple.security.app-sandbox
-
- com.apple.security.network.client
-
- com.apple.security.network.server
-
- com.apple.security.files.user-selected.read-write
-
+
\ No newline at end of file
diff --git a/scripts/build.sh b/scripts/build.sh
index 876bbe60..a8854d9f 100755
--- a/scripts/build.sh
+++ b/scripts/build.sh
@@ -27,7 +27,6 @@
# DEPENDENCIES:
# - Xcode and command line tools
# - xcbeautify (optional, for prettier output)
-# - Generated Xcode project (run generate-xcproj.sh first)
#
# EXAMPLES:
# ./scripts/build.sh # Release build
@@ -73,6 +72,28 @@ echo "Code signing: $SIGN_APP"
# Clean build directory only if it doesn't exist
mkdir -p "$BUILD_DIR"
+# Build tty-fwd universal binary
+echo "🔨 Building tty-fwd universal binary..."
+if [[ -x "$PROJECT_DIR/tty-fwd/build-universal.sh" ]]; then
+ cd "$PROJECT_DIR/tty-fwd"
+ ./build-universal.sh
+
+ # Verify the binary was built
+ if [[ -f "$PROJECT_DIR/tty-fwd/target/release/tty-fwd-universal" ]]; then
+ echo "✓ tty-fwd universal binary built successfully"
+ # Copy to Resources folder for inclusion in app bundle
+ cp "$PROJECT_DIR/tty-fwd/target/release/tty-fwd-universal" "$PROJECT_DIR/VibeTunnel/Resources/tty-fwd"
+ chmod +x "$PROJECT_DIR/VibeTunnel/Resources/tty-fwd"
+ echo "✓ Copied tty-fwd universal binary to Resources folder"
+ else
+ echo "Error: Failed to build tty-fwd universal binary"
+ exit 1
+ fi
+else
+ echo "Error: tty-fwd build script not found at $PROJECT_DIR/tty-fwd/build-universal.sh"
+ exit 1
+fi
+
# Build the app
cd "$PROJECT_DIR"
diff --git a/scripts/release.sh b/scripts/release.sh
index 69ae0eba..7c421fa6 100755
--- a/scripts/release.sh
+++ b/scripts/release.sh
@@ -32,7 +32,7 @@
#
# DEPENDENCIES:
# - preflight-check.sh (validates release readiness)
-# - generate-xcproj.sh (Tuist project generation)
+# - Xcode workspace and project files
# - build.sh (application building)
# - sign-and-notarize.sh (code signing and notarization)
# - create-dmg.sh (DMG creation)
@@ -142,10 +142,9 @@ echo " Build: $BUILD_NUMBER"
echo " Tag: $TAG_NAME"
echo ""
-# Step 2: Clean and generate project
-echo -e "${BLUE}📋 Step 2/7: Generating Xcode project...${NC}"
+# Step 2: Clean build directory
+echo -e "${BLUE}📋 Step 2/7: Cleaning build directory...${NC}"
rm -rf "$PROJECT_ROOT/build"
-"$SCRIPT_DIR/generate-xcproj.sh"
# Check if Xcode project was modified and commit if needed
if ! git diff --quiet "$PROJECT_ROOT/VibeTunnel.xcodeproj/project.pbxproj"; then