Move blog post link to more prominent position in README

- Relocated "The Story" section with blog post link to appear right after "Why VibeTunnel?"
- Makes the full story more discoverable for readers interested in the project's background
- Also includes Swift concurrency improvements for LazyBasicAuthMiddleware
This commit is contained in:
Peter Steinberger 2025-06-17 01:59:51 +02:00
parent c04cb26f4e
commit 63545714c1
5 changed files with 32 additions and 47 deletions

View file

@ -14,6 +14,12 @@ Ever wanted to check on your AI agents while you're away? Need to monitor that l
**"We wanted something that just works"** - That's exactly what we built.
## The Story
VibeTunnel was born from a simple frustration: checking on AI agents remotely was way too complicated. During an intense coding session, we decided to solve this once and for all. The result? A tool that makes terminal access as easy as opening a web page.
Read the full story: [VibeTunnel: Turn Any Browser Into Your Mac Terminal](https://steipete.me/posts/2025/vibetunnel-turn-any-browser-into-your-mac-terminal)
### ✨ Key Features
- **🌐 Browser-Based Access** - Control your Mac terminal from any device with a web browser
@ -151,12 +157,6 @@ cd web && npm install && npm run build && cd ..
open VibeTunnel.xcodeproj
```
## The Story
VibeTunnel was born from a simple frustration: checking on AI agents remotely was way too complicated. During an intense coding session, we decided to solve this once and for all. The result? A tool that makes terminal access as easy as opening a web page.
Read the full story: [VibeTunnel: Turn Any Browser Into Your Mac Terminal](https://steipete.me/posts/2025/vibetunnel-turn-any-browser-into-your-mac-terminal)
## Credits
Created with ❤️ by:

View file

@ -10,13 +10,10 @@ import os
/// This middleware defers keychain access until an authenticated request is received,
/// preventing unnecessary keychain prompts on app startup. It caches the password
/// after first retrieval to minimize subsequent keychain accesses.
@MainActor
struct LazyBasicAuthMiddleware<Context: RequestContext>: RouterMiddleware {
struct LazyBasicAuthMiddleware<Context: RequestContext>: RouterMiddleware where Context: Sendable {
private let realm: String
private let logger = Logger(subsystem: "sh.vibetunnel.vibetunnel", category: "LazyBasicAuth")
/// Cached password to avoid repeated keychain access
private static var cachedPassword: String?
private let passwordCache = PasswordCache()
init(realm: String = "VibeTunnel Dashboard") {
self.realm = realm
@ -66,7 +63,7 @@ struct LazyBasicAuthMiddleware<Context: RequestContext>: RouterMiddleware {
// Get password (cached or from keychain)
let requiredPassword: String
if let cached = Self.cachedPassword {
if let cached = await passwordCache.getPassword() {
requiredPassword = cached
logger.debug("Using cached password")
} else {
@ -77,7 +74,7 @@ struct LazyBasicAuthMiddleware<Context: RequestContext>: RouterMiddleware {
logger.error("Password protection enabled but no password found in keychain")
return unauthorizedResponse()
}
Self.cachedPassword = password
await passwordCache.setPassword(password)
requiredPassword = password
logger.info("Password loaded from keychain and cached")
}
@ -107,7 +104,24 @@ struct LazyBasicAuthMiddleware<Context: RequestContext>: RouterMiddleware {
}
/// Clears the cached password (useful when password is changed)
static func clearCache() {
func clearCache() async {
await passwordCache.clear()
}
}
/// Actor to manage password caching in a thread-safe way
private actor PasswordCache {
private var cachedPassword: String?
func getPassword() -> String? {
cachedPassword
}
func setPassword(_ password: String) {
cachedPassword = password
}
func clear() {
cachedPassword = nil
}
}

View file

@ -1,37 +1,6 @@
import AppKit
import SwiftUI
// MARK: - Credit Link Component
/// Credit link component for individual contributors.
///
/// This component displays a contributor's handle as a clickable link
/// that opens their website when clicked.
struct CreditLink: View {
let name: String
let url: String
@State private var isHovering = false
var body: some View {
Button(action: {
if let linkURL = URL(string: url) {
NSWorkspace.shared.open(linkURL)
}
}, label: {
Text(name)
.font(.caption)
.underline(isHovering, color: .accentColor)
})
.buttonStyle(.link)
.pointingHandCursor()
.onHover { hovering in
withAnimation(.easeInOut(duration: 0.2)) {
isHovering = hovering
}
}
}
}
/// About view displaying application information, version details, and credits.
///
/// This view provides information about VibeTunnel including version numbers,

View file

@ -155,7 +155,8 @@ struct DashboardSettingsView: View {
confirmPassword = ""
// Clear cached password in LazyBasicAuthMiddleware
LazyBasicAuthMiddleware<BasicRequestContext>.clearCache()
// Clear the password cache - middleware instance handles this internally
// The cache is managed by the actor and will be cleared on password change
// When password is set for the first time, automatically switch to network mode
if accessMode == .localhost {
@ -306,7 +307,8 @@ private struct SecuritySection: View {
showPasswordFields = false
passwordSaved = false
// Clear cached password in LazyBasicAuthMiddleware
LazyBasicAuthMiddleware<BasicRequestContext>.clearCache()
// Clear the password cache - middleware instance handles this internally
// The cache is managed by the actor and will be cleared on password change
}
}