diff --git a/Sources/ClaudeUsageMonitor/App/BundleConfiguration.swift b/Sources/ClaudeUsageMonitor/App/BundleConfiguration.swift index bbb5fae..931ce24 100644 --- a/Sources/ClaudeUsageMonitor/App/BundleConfiguration.swift +++ b/Sources/ClaudeUsageMonitor/App/BundleConfiguration.swift @@ -18,6 +18,16 @@ enum BundleConfiguration { } static var ccusageVersion: String { - return Bundle.main.object(forInfoDictionaryKey: "CcusageVersion") as? String ?? "" + let version = Bundle.main.object(forInfoDictionaryKey: "CcusageVersion") as? String ?? "" + let fallbackVersion = "15.3.0" + + // Debug時にInfo.plistが更新されていない場合のフォールバック + if version.isEmpty { + print("⚠️ [BundleConfiguration] CcusageVersion not found in Info.plist, using fallback: \(fallbackVersion)") + return fallbackVersion + } + + print("📦 [BundleConfiguration] Using ccusage version: \(version)") + return version } } diff --git a/Sources/ClaudeUsageMonitor/Services/CommandExecutor.swift b/Sources/ClaudeUsageMonitor/Services/CommandExecutor.swift index a8c745d..5340e66 100644 --- a/Sources/ClaudeUsageMonitor/Services/CommandExecutor.swift +++ b/Sources/ClaudeUsageMonitor/Services/CommandExecutor.swift @@ -74,18 +74,23 @@ class CommandExecutor { func executeCcusageCommand(subcommand: String? = nil, additionalArgs: [String] = []) async throws -> String { var command: String var ccusageArgs: [String] = [] + + let ccusageVersion = BundleConfiguration.ccusageVersion + guard !ccusageVersion.isEmpty else { + throw CommandError.commandNotFound("ccusage version not configured") + } // Try bunx first if let bunxPath = try await findCommand("bunx") { print("[CommandExecutor] Found bunx at: \(bunxPath)") command = bunxPath - ccusageArgs.append("ccusage@\(BundleConfiguration.ccusageVersion)") + ccusageArgs.append("ccusage@\(ccusageVersion)") } // Fall back to npx else if let npxPath = try await findCommand("npx") { print("[CommandExecutor] Found npx at: \(npxPath)") command = npxPath - ccusageArgs.append("ccusage@\(BundleConfiguration.ccusageVersion)") + ccusageArgs.append("ccusage@\(ccusageVersion)") } // Neither found else { @@ -103,6 +108,7 @@ class CommandExecutor { ccusageArgs.append(contentsOf: additionalArgs) print("[CommandExecutor] Executing: \(command) \(ccusageArgs.joined(separator: " "))") + print("[CommandExecutor] Using ccusage version: \(ccusageVersion)") // Execute the command let task = Process() diff --git a/Sources/ClaudeUsageMonitor/ViewModels/SessionViewModel.swift b/Sources/ClaudeUsageMonitor/ViewModels/SessionViewModel.swift index 46b0c49..27a6b20 100644 --- a/Sources/ClaudeUsageMonitor/ViewModels/SessionViewModel.swift +++ b/Sources/ClaudeUsageMonitor/ViewModels/SessionViewModel.swift @@ -26,13 +26,17 @@ class SessionViewModel: ObservableObject { } session = data - remainingTokens = monitor.usageData.sessionTokenLimit - data.totalTokens + remainingTokens = max(0, monitor.usageData.sessionTokenLimit - data.totalTokens) percentage = monitor.usageData.sessionUsagePercentage burnRate = Double(monitor.usageData.sessionBurnRate) ?? 0 remainingTime = monitor.usageData.sessionRemainingTime cost = data.costUSD planDescription = monitor.usageData.planDescription resetTime = Date.formatTime(from: data.endTime) + + // デバッグ用ログ + print("🔍 [SessionViewModel] Total: \(data.totalTokens), Limit: \(monitor.usageData.sessionTokenLimit), Remaining: \(remainingTokens)") + print("🔍 [SessionViewModel] Plan: \(planDescription), Percentage: \(String(format: "%.1f", percentage))%") // 通知機能は初回リリースでは無効化 /* diff --git a/Sources/ClaudeUsageMonitor/Views/Tabs/SettingsTabView.swift b/Sources/ClaudeUsageMonitor/Views/Tabs/SettingsTabView.swift index 5399b9d..55667c5 100644 --- a/Sources/ClaudeUsageMonitor/Views/Tabs/SettingsTabView.swift +++ b/Sources/ClaudeUsageMonitor/Views/Tabs/SettingsTabView.swift @@ -580,10 +580,15 @@ struct SettingsTabView: View { private func selectUpdateChannel(_ channel: UpdateChannel) { UserDefaults.standard.updateChannel = channel + #if canImport(Sparkle) // Notify AppDelegate to update Sparkle configuration if let appDelegate = NSApplication.shared.delegate as? AppDelegate { appDelegate.updateChannelChanged(to: channel) } + #endif + + // Re-fetch latest versions when channel changes + fetchLatestVersions() } private func checkLatestVersion() { @@ -628,10 +633,13 @@ struct SettingsTabView: View { } private func fetchLatestVersions() { + print("🔄 Starting to fetch latest versions...") + // Fetch stable version Task { await fetchLatestVersion(for: .stable) { version in self.latestStableVersion = version + print("📦 Stable version set to: \(version ?? "nil")") } } @@ -639,28 +647,55 @@ struct SettingsTabView: View { Task { await fetchLatestVersion(for: .dev) { version in self.latestDevVersion = version + print("📦 Dev version set to: \(version ?? "nil")") } } } private func fetchLatestVersion(for channel: UpdateChannel, completion: @escaping (String?) -> Void) async { guard let url = URL(string: channel.appcastURL) else { + print("❌ Invalid URL for channel \(channel): \(channel.appcastURL)") completion(nil) return } do { - let (data, _) = try await URLSession.shared.data(from: url) + let (data, response) = try await URLSession.shared.data(from: url) + + if let httpResponse = response as? HTTPURLResponse { + print("📡 Fetching \(channel) version from: \(url)") + print("📡 Response status: \(httpResponse.statusCode)") + + guard httpResponse.statusCode == 200 else { + print("❌ HTTP error: \(httpResponse.statusCode)") + await MainActor.run { + completion(nil) + } + return + } + } + let parser = XMLParser(data: data) let delegate = AppcastParserDelegate() parser.delegate = delegate if parser.parse(), let version = delegate.latestVersion { + print("✅ Found \(channel) version: \(version)") await MainActor.run { completion(version) } + } else { + print("❌ Failed to parse XML for \(channel)") + // デバッグ用: XMLの最初の部分を表示 + if let xmlString = String(data: data.prefix(500), encoding: .utf8) { + print("XML preview: \(xmlString)") + } + await MainActor.run { + completion(nil) + } } } catch { + print("❌ Error fetching \(channel) version: \(error)") await MainActor.run { completion(nil) }