蜂鸟Pro v2.0.1 - 基础框架版本 (待完善)
## 当前状态 - 插件界面已完成重命名 (cursorpro → hummingbird) - 双账号池 UI 已实现 (Auto/Pro 卡片) - 后端已切换到 MySQL 数据库 - 添加了 Cursor 官方用量 API 文档 ## 已知问题 (待修复) 1. 激活时检查账号导致无账号时激活失败 2. 未启用无感换号时不应获取账号 3. 账号用量模块不显示 (seamless 未启用时应隐藏) 4. 积分显示为 0 (后端未正确返回) 5. Auto/Pro 双密钥逻辑混乱,状态不同步 6. 账号添加后无自动分析功能 ## 下一版本计划 - 重构数据模型,优化账号状态管理 - 实现 Cursor API 自动分析账号 - 修复激活流程,不依赖账号 - 启用无感时才分配账号 - 完善账号用量实时显示 ## 文件说明 - docs/系统设计文档.md - 完整架构设计 - cursor 官方用量接口.md - Cursor API 文档 - 参考计费/ - Vibeviewer 开源项目参考 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
26
参考计费/Packages/VibeviewerLoginUI/Package.swift
Normal file
26
参考计费/Packages/VibeviewerLoginUI/Package.swift
Normal file
@@ -0,0 +1,26 @@
|
||||
// swift-tools-version:5.10
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "VibeviewerLoginUI",
|
||||
platforms: [
|
||||
.macOS(.v14)
|
||||
],
|
||||
products: [
|
||||
.library(name: "VibeviewerLoginUI", targets: ["VibeviewerLoginUI"]),
|
||||
],
|
||||
dependencies: [
|
||||
.package(path: "../VibeviewerShareUI")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "VibeviewerLoginUI",
|
||||
dependencies: [
|
||||
"VibeviewerShareUI"
|
||||
]
|
||||
),
|
||||
.testTarget(name: "VibeviewerLoginUITests", dependencies: ["VibeviewerLoginUI"])
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import SwiftUI
|
||||
import WebKit
|
||||
|
||||
struct CookieWebView: NSViewRepresentable {
|
||||
let onCookieCaptured: (String) -> Void
|
||||
|
||||
func makeNSView(context: Context) -> WKWebView {
|
||||
let config = WKWebViewConfiguration()
|
||||
let webView = WKWebView(frame: .zero, configuration: config)
|
||||
webView.navigationDelegate = context.coordinator
|
||||
if let url =
|
||||
URL(
|
||||
string: "https://authenticator.cursor.sh/"
|
||||
)
|
||||
{
|
||||
webView.load(URLRequest(url: url))
|
||||
}
|
||||
return webView
|
||||
}
|
||||
|
||||
func updateNSView(_ nsView: WKWebView, context: Context) {}
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(onCookieCaptured: self.onCookieCaptured)
|
||||
}
|
||||
|
||||
final class Coordinator: NSObject, WKNavigationDelegate {
|
||||
let onCookieCaptured: (String) -> Void
|
||||
|
||||
init(onCookieCaptured: @escaping (String) -> Void) {
|
||||
self.onCookieCaptured = onCookieCaptured
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
||||
if webView.url?.absoluteString.hasSuffix("/dashboard") == true {
|
||||
self.captureCursorCookies(from: webView)
|
||||
}
|
||||
}
|
||||
|
||||
private func captureCursorCookies(from webView: WKWebView) {
|
||||
webView.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in
|
||||
let relevant = cookies.filter { cookie in
|
||||
let domain = cookie.domain.lowercased()
|
||||
return domain.contains("cursor.com")
|
||||
}
|
||||
guard !relevant.isEmpty else { return }
|
||||
let headerString = relevant.map { "\($0.name)=\($0.value)" }.joined(separator: "; ")
|
||||
self.onCookieCaptured(headerString)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import SwiftUI
|
||||
|
||||
private struct LoginWindowManagerKey: EnvironmentKey {
|
||||
static let defaultValue: LoginWindowManager = .shared
|
||||
}
|
||||
|
||||
public extension EnvironmentValues {
|
||||
var loginWindowManager: LoginWindowManager {
|
||||
get { self[LoginWindowManagerKey.self] }
|
||||
set { self[LoginWindowManagerKey.self] = newValue }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import AppKit
|
||||
import SwiftUI
|
||||
|
||||
public final class LoginWindowManager {
|
||||
public static let shared = LoginWindowManager()
|
||||
private var controller: LoginWindowController?
|
||||
|
||||
public func show(onCookieCaptured: @escaping (String) -> Void) {
|
||||
if let controller {
|
||||
controller.showWindow(nil)
|
||||
controller.window?.makeKeyAndOrderFront(nil)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
return
|
||||
}
|
||||
let controller = LoginWindowController(onCookieCaptured: { [weak self] cookie in
|
||||
onCookieCaptured(cookie)
|
||||
self?.close()
|
||||
})
|
||||
self.controller = controller
|
||||
controller.window?.center()
|
||||
controller.showWindow(nil)
|
||||
controller.window?.makeKeyAndOrderFront(nil)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
if let hosting = controller.contentViewController as? NSHostingController<CursorLoginView> {
|
||||
hosting.rootView = CursorLoginView(onCookieCaptured: { cookie in
|
||||
onCookieCaptured(cookie)
|
||||
self.close()
|
||||
}, onClose: { [weak self] in
|
||||
self?.close()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public func close() {
|
||||
self.controller?.close()
|
||||
self.controller = nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import AppKit
|
||||
import SwiftUI
|
||||
|
||||
final class LoginWindowController: NSWindowController, NSWindowDelegate {
|
||||
private var onCookieCaptured: ((String) -> Void)?
|
||||
|
||||
convenience init(onCookieCaptured: @escaping (String) -> Void) {
|
||||
let vc = NSHostingController(rootView: CursorLoginView(onCookieCaptured: { cookie in
|
||||
onCookieCaptured(cookie)
|
||||
}, onClose: {}))
|
||||
let window = NSWindow(contentViewController: vc)
|
||||
window.title = "Cursor 登录"
|
||||
window.setContentSize(NSSize(width: 900, height: 680))
|
||||
window.styleMask = [.titled, .closable, .miniaturizable, .resizable]
|
||||
window.isReleasedWhenClosed = false
|
||||
self.init(window: window)
|
||||
self.onCookieCaptured = onCookieCaptured
|
||||
self.window?.delegate = self
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
struct CursorLoginView: View {
|
||||
let onCookieCaptured: (String) -> Void
|
||||
let onClose: () -> Void
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
CookieWebView(onCookieCaptured: { cookie in
|
||||
self.onCookieCaptured(cookie)
|
||||
self.onClose()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
@testable import VibeviewerLoginUI
|
||||
import XCTest
|
||||
|
||||
final class VibeviewerLoginUITests: XCTestCase {
|
||||
func testExample() {
|
||||
XCTAssertTrue(true)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user