Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added AppIcon.icon/Assets/wallet.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 36 additions & 0 deletions AppIcon.icon/icon.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"fill" : {
"automatic-gradient" : "extended-srgb:0.00000,0.47843,1.00000,1.00000"
},
"groups" : [
{
"layers" : [
{
"fill-specializations" : [
{
"appearance" : "dark",
"value" : "automatic"
}
],
"glass" : false,
"image-name" : "wallet.png",
"name" : "wallet"
}
],
"shadow" : {
"kind" : "neutral",
"opacity" : 0.5
},
"translucency" : {
"enabled" : true,
"value" : 0.5
}
}
],
"supported-platforms" : {
"circles" : [
"watchOS"
],
"squares" : "shared"
}
}
173 changes: 173 additions & 0 deletions COMPARISON.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# OpenRouter Credit MenuBar - Comparison with Original

This document outlines the key differences between the current implementation and the original [OpenRouterCreditMenuBar](https://github.com/kittizz/OpenRouterCreditMenuBar) repository.

## 📋 Feature Comparison

### 1. **Connection Testing** 🔗

**Original Implementation**: Basic connection testing with single endpoint

**Current Implementation**: Comprehensive multi-endpoint testing with real-time progress

```swift
// Sequential endpoint testing with progress updates
@MainActor func testConnection() async {
connectionTestProgress = "Testing OpenRouter API server..."
var result = await testAuthEndpoint()

if result == nil {
connectionTestProgress = "Testing credits endpoint..."
result = await testCreditsEndpoint()
}

if result == nil {
connectionTestProgress = "Testing models endpoint..."
result = await testModelsEndpoint()
}
}
```

**Key Differences**:
- Sequential endpoint testing (Auth → Credits → Models)
- Real-time progress updates for each test
- Detailed success/failure feedback
- Automatic fallback between endpoints
- Comprehensive error messages with recovery suggestions

### 2. **Settings Window** ⚙️

**Original Implementation**: Basic settings interface

**Current Implementation**: Polished UI with proper macOS integration

**Key Differences**:
- Proper window focus management
- Taller window (400×450) for better usability
- No automatic launch on startup
- App continues running when Settings window closes
- Immediate focus with active UI elements
- Eliminated duplicate "Refresh Interval" text

### 3. **API Key Management** 🔐

**Original Implementation**: Basic keychain storage

**Current Implementation**: Enhanced security with caching

```swift
// API key caching implementation
private var cachedAPIKey: String? = nil

var apiKey: String {
get {
if let cached = cachedAPIKey {
return cached
}
let key = SimpleSecureStorage.retrieveAPIKeySecurely() ?? ""
cachedAPIKey = key
return key
}
set {
cachedAPIKey = nil
if !newValue.isEmpty {
_ = SimpleSecureStorage.storeAPIKeySecurely(newValue)
}
}
}
```

**Key Differences**:
- Single keychain access per session
- API key caching prevents redundant keychain access
- Proper cache invalidation when key changes
- Maintains same security level with better UX

### 4. **Visual Feedback** 🎨

**Original Implementation**: Basic loading indicators

**Current Implementation**: Comprehensive visual feedback system

**Key Differences**:
- Loading orb that disappears after tests complete
- Green checkmark for success, red cross for failure
- Real-time progress messages
- Proper loading state management
- No persistent loading indicators

### 5. **Error Handling** ⚠️

**Original Implementation**: Basic error messages

**Current Implementation**: Comprehensive error system

**Key Differences**:
- Specific error messages for each failure type
- Recovery suggestions for common issues
- User-friendly error categorization
- Proper error state management
- Clear visual error indicators

### 6. **Code Quality** 🧹

**Original Implementation**: Functional with some warnings

**Current Implementation**: Clean, warning-free codebase

**Key Differences**:
- All compiler warnings fixed
- Unused variables eliminated
- Proper Swift naming conventions
- Consistent code style
- Comprehensive comments

### 7. **Documentation** 📚

**Original Implementation**: Basic documentation

**Current Implementation**: Comprehensive professional documentation

**Key Differences**:
- Complete feature documentation
- Detailed setup and usage instructions
- Professional screenshots and descriptions
- Clear security and privacy information
- Contribution guidelines

## 📊 Feature Comparison Table

| Feature | Original Implementation | Current Implementation |
|---------|------------------------|-------------------|
| Connection Testing | Basic single endpoint | Multi-endpoint with progress updates |
| Settings Window | Basic UI | Professional with focus management |
| API Key Storage | Multiple keychain prompts | Single access with caching |
| Visual Feedback | Basic indicators | Comprehensive system |
| Error Handling | Basic messages | User-friendly with recovery |
| Code Quality | Functional with warnings | Warning-free, clean |
| Documentation | Basic | Comprehensive, professional |

## 🎯 Summary

The current implementation maintains the core concept of monitoring OpenRouter API credits while introducing significant improvements:

1. **Enhanced Functionality**: Multi-endpoint testing, better error handling, comprehensive feedback
2. **Improved User Experience**: Professional UI, proper focus management, polished interactions
3. **Optimized Performance**: Single keychain access, API key caching, efficient resource management
4. **Higher Code Quality**: Warning-free implementation, clean architecture
5. **Comprehensive Documentation**: Professional guides for users and developers

## 🤝 Acknowledgments

The original [OpenRouterCreditMenuBar](https://github.com/kittizz/OpenRouterCreditMenuBar) project provided the initial foundation and inspiration for credit monitoring functionality. The current implementation builds upon that foundation with numerous technical and user experience improvements.

## 🔧 Technical Improvements

- Eliminated multiple keychain access prompts through API key caching
- Fixed Settings window focus and lifecycle management issues
- Removed duplicate UI text and improved interface clarity
- Resolved all compiler warnings for cleaner codebase
- Enhanced error handling robustness and user feedback
- Improved visual feedback consistency and professional appearance

The result is a polished macOS menu bar application that provides comprehensive OpenRouter credit monitoring with excellent user experience and technical quality.
64 changes: 64 additions & 0 deletions OpenRouterCreditMenuBar/KeychainService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// KeychainService.swift
// OpenRouterCreditMenuBar
//
// Secure storage for API keys using macOS Keychain
//

import Foundation
import Security

class KeychainService {
static let shared = KeychainService()

private let service = "com.openrouter.creditor"

func set(key: String, value: String) -> Bool {
guard let data = value.data(using: .utf8) else { return false }

// Delete existing item if it exists
delete(key: key)

let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: key,
kSecValueData as String: data,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked
]

let status = SecItemAdd(query as CFDictionary, nil)
return status == errSecSuccess
}

func get(key: String) -> String? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: key,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]

var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)

guard status == errSecSuccess,
let data = result as? Data,
let string = String(data: data, encoding: .utf8) else {
return nil
}

return string
}

func delete(key: String) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: key
]

SecItemDelete(query as CFDictionary)
}
}
34 changes: 27 additions & 7 deletions OpenRouterCreditMenuBar/MenuBarView.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import SwiftUI

struct MenuBarView: View {

private func getRelativeTime(for date: Date) -> String {
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .abbreviated
return formatter.localizedString(for: date, relativeTo: Date())
}

@EnvironmentObject var creditManager: OpenRouterCreditManager

var body: some View {
Expand All @@ -23,13 +30,26 @@ struct MenuBarView: View {
.font(.caption)
}
} else if let credit = creditManager.currentCredit {
VStack(spacing: 4) {
Text("Available Credit")
.font(.caption)
.foregroundColor(.secondary)
VStack(spacing: 2) {
// Row 1: Credit Amount
Text("$\(String(format: "%.4f", credit))")
.font(.title2)
.fontWeight(.semibold)
.font(.headline)
.fontWeight(.regular)
.frame(maxWidth: .infinity)

if let usage = creditManager.usageCost {
Text("Usage: $\(String(format: "%.4f", usage))")
.font(.caption2)
.foregroundColor(.secondary)
.padding(.top, 2)
}

if let lastUpdated = creditManager.lastUpdated {
Text("Updated \(getRelativeTime(for: lastUpdated))")
.font(.system(size: 8))
.foregroundColor(.secondary.opacity(0.7))
.padding(.top, 4)
}
}
} else if let error = creditManager.errorMessage {
VStack(spacing: 4) {
Expand Down Expand Up @@ -75,4 +95,4 @@ struct MenuBarView: View {
.padding()
.frame(width: 200)
}
}
}
Loading